Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

引用でFEP-e232形式をサポート #4843

Draft
wants to merge 4 commits into
base: mei-m544
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions src/misc/get-quote.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { removeNull, toArray } from '../prelude/array';
import { IObject, IPost, isLink } from '../remote/activitypub/type';

/**
* rels treated as quote
*/
const relQuotes = new Set<string>([
'https://misskey-hub.net/ns#_misskey_quote',
'http://fedibird.com/ns#quoteUri',
]);

/**
* Misskey like quote
*/
type Quote = {
/** Target AP object ID */
href: string;
/** Fallback text */
name?: string;
};

/**
* Get one Misskey like quote
*/
export function getQuote(post: IPost): Quote | null {
// Misskey
if (typeof post._misskey_quote === 'string') return { href: post._misskey_quote }
// Fedibird
if (typeof post.quoteUri === 'string') return { href: post.quoteUri }

// FEP-e232: Object Links
// https://codeberg.org/fediverse/fep/src/branch/main/fep/e232/fep-e232.md
const fepe232Tags = parseFepE232Tags(toArray(post.tag));
const fepe232Quote = fepe232Tags.filter(x => x.rel && relQuotes.has(x.rel))[0];
if (fepe232Quote) {
return {
href: fepe232Quote.href,
name: fepe232Quote.name,
};
}

return null;
}

/**
* Get AP links (experimental)
*/
export function getApLinks(post: IPost) {
const fepe232Tags = parseFepE232Tags(toArray(post.tag));

// other attachements?

return fepe232Tags;
}

//#region FEP-e232
type FepE232Tag = {
type: 'Link';
mediaType: string;
href: string;
name?: string;
rel?: string;
};

function parseFepE232Tags(tags: IObject[]): FepE232Tag[] {
return removeNull(tags.map(x => parseFepE232Tag(x)));
}

function parseFepE232Tag(tag: IObject): FepE232Tag | null {
if (!isLink(tag)) return null;
if (!validateContentType(tag.mediaType)) return null;
if (typeof tag.href !== 'string') return null;

return {
type: tag.type,
mediaType: tag.mediaType,
href: tag.href,
name: typeof tag.name === 'string' ? tag.name : undefined,
rel: typeof tag.rel === 'string' ? tag.rel : undefined,
};
}

function validateContentType(contentType: unknown): contentType is string {
if (contentType == null) return false;
if (typeof contentType !== 'string') return false;

const parts = contentType.split(/\s*;\s*/);
if (parts[0] === 'application/activity+json') return true;
if (parts[0] !== 'application/ld+json') return false;
return parts.slice(1).some(part => part.trim() === 'profile="https://www.w3.org/ns/activitystreams"');
}
//#endregion
7 changes: 5 additions & 2 deletions src/remote/activitypub/models/note.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { parseAudience } from '../audience';
import DbResolver from '../db-resolver';
import { parseDate, parseDateWithLimit } from '../misc/date';
import { StatusError } from '../../../misc/fetch';
import { getQuote } from '../../../misc/get-quote';

const logger = apLogger;

Expand Down Expand Up @@ -113,8 +114,8 @@ export async function createNote(value: string | IObject, resolver?: Resolver |
const reply = note.inReplyTo ? await resolveNote(getOneApId(note.inReplyTo), resolver) : null;

// 引用
const q = note._misskey_quote || note.quoteUri || note.quoteUrl;
const quote = q ? await resolveNote(q, resolver) : null;
const quoteInfo = getQuote(note);
const quote = quoteInfo?.href ? await resolveNote(quoteInfo?.href, resolver) : null;

// 参照
const references = await fetchReferences(note, resolver).catch(() => []);
Expand All @@ -127,6 +128,8 @@ export async function createNote(value: string | IObject, resolver?: Resolver |
: note.content ? htmlToMfm(note.content, note.tag)
: null;

if (text && quoteInfo?.name) text.replace(quoteInfo.name, '');

// 投票
if (reply && reply.poll) {
const tryCreateVote = async (name: string, index: number): Promise<null> => {
Expand Down
2 changes: 1 addition & 1 deletion src/remote/activitypub/models/person.ts
Original file line number Diff line number Diff line change
Expand Up @@ -607,7 +607,7 @@ export async function fetchOutbox(user: IUser) {
// Note
if (object.inReplyTo) {
// skip reply
} else if (object._misskey_quote || object.quoteUri || object.quoteUrl) {
} else if (object._misskey_quote || object.quoteUri) {
// skip quote
} else {
if (++itemCount > 10) break;
Expand Down
9 changes: 9 additions & 0 deletions src/remote/activitypub/renderer/note.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,19 @@ export default async function renderNote(note: INote, dive = true): Promise<any>
const emojis = await getEmojis(note.emojis);
const apemojis = emojis.map(emoji => renderEmoji(emoji));

const fepE232Quote = {
type: 'Link',
mediaType: 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
rel: 'https://misskey-hub.net/ns#_misskey_quote',
href: quote,
name: `RE: ${quote}`,
};

const tag = [
...hashtagTags,
...mentionTags,
...apemojis,
fepE232Quote,
];

const {
Expand Down
12 changes: 10 additions & 2 deletions src/remote/activitypub/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,6 @@ export interface IPost extends IObject {
mediaType?: string;
};
_misskey_quote?: string;
quoteUrl?: string;
quoteUri?: string;
references: string | ICollection;
}
Expand All @@ -168,7 +167,6 @@ export interface IQuestion extends IObject {
mediaType?: string;
};
_misskey_quote?: string;
quoteUrl?: string;
quoteUri?: string;
oneOf?: IQuestionChoice[];
anyOf?: IQuestionChoice[];
Expand Down Expand Up @@ -230,6 +228,16 @@ export const isHashtag = (object: IObject): object is IApHashtag =>
getApType(object) === 'Hashtag' &&
typeof object.name === 'string';

export interface IApLink extends IObject {
type: 'Link';
href?: string;
rel?: string;
mediaType?: string;
name?: string;
};

export const isLink = (object: IObject): object is IApLink => getApType(object) === 'Link';

export interface IActor extends IObject {
type: 'Person' | 'Service' | 'Organization' | 'Group' | 'Application';
name?: string;
Expand Down
95 changes: 95 additions & 0 deletions test/get-quote.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import * as assert from 'assert';
import { getQuote } from '../src/misc/get-quote';

describe('getQuote', () => {
it('_misskey_quote', () => {
assert.deepStrictEqual(getQuote({
_misskey_quote: 'https://example.com/notes/quote',
} as any), {
href: 'https://example.com/notes/quote',
});
});

it('quoteUri', () => {
assert.deepStrictEqual(getQuote({
quoteUri: 'https://example.com/notes/quote',
} as any), {
href: 'https://example.com/notes/quote',
});
});

it('FEP-e232', () => {
assert.deepStrictEqual(getQuote({
tag: [
{
type: 'Link',
mediaType: 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
rel: 'https://misskey-hub.net/ns#_misskey_quote',
href: 'https://example.com/notes/quote',
name: 'RE: https://example.com/html/quote',
}
],
} as any), {
href: 'https://example.com/notes/quote',
name: 'RE: https://example.com/html/quote',
});
});

it('FEP-e232 application/activity+json', () => {
assert.deepStrictEqual(getQuote({
tag: [
{
type: 'Link',
mediaType: 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
rel: 'https://misskey-hub.net/ns#_misskey_quote',
href: 'https://example.com/notes/quote',
name: 'RE: https://example.com/html/quote',
}
],
} as any), {
href: 'https://example.com/notes/quote',
name: 'RE: https://example.com/html/quote',
});
});

it('FEP-e232 invalid mediaType', () => {
assert.deepStrictEqual(getQuote({
tag: [
{
type: 'Link',
mediaType: 'text/html',
rel: 'https://misskey-hub.net/ns#_misskey_quote',
href: 'https://example.com/notes/quote',
name: 'RE: https://example.com/html/quote',
}
],
} as any), null);
});

it('FEP-e232 no Link', () => {
assert.deepStrictEqual(getQuote({
tag: [
{
type: 'Link2',
mediaType: 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
rel: 'https://misskey-hub.net/ns#_misskey_quote',
href: 'https://example.com/notes/quote',
name: 'RE: https://example.com/html/quote',
}
],
} as any), null);
});

it('FEP-e232 no match rel', () => {
assert.deepStrictEqual(getQuote({
tag: [
{
type: 'Link',
mediaType: 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
href: 'https://example.com/notes/quote',
name: 'RE: https://example.com/html/quote',
}
],
} as any), null);
});
});
Loading