server: parse quote tag syntax
Some checks failed
ci/woodpecker/push/lint-foundkey-js Pipeline was successful
ci/woodpecker/push/lint-backend Pipeline was successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/push/lint-client Pipeline was successful
ci/woodpecker/push/lint-sw Pipeline was successful
ci/woodpecker/push/test Pipeline was successful
ci/woodpecker/pr/lint-foundkey-js Pipeline was successful
ci/woodpecker/pr/lint-client Pipeline failed
ci/woodpecker/pr/lint-backend Pipeline failed
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/lint-sw Pipeline failed
ci/woodpecker/pr/test Pipeline failed

Ref: FEP-e232
This commit is contained in:
Johann150 2023-01-05 22:19:34 +01:00
parent dd16f75cae
commit 524352ae87
Signed by: Johann150
GPG key ID: 9EE6577A2A06F8F1
3 changed files with 65 additions and 8 deletions

View file

@ -24,7 +24,7 @@ import { DbResolver } from '../db-resolver.js';
import { apLogger } from '../logger.js';
import { resolvePerson } from './person.js';
import { resolveImage } from './image.js';
import { extractApHashtags } from './tag.js';
import { extractApHashtags, extractQuoteUrl } from './tag.js';
import { extractPollFromQuestion } from './question.js';
import { extractApMentions } from './mention.js';
@ -154,10 +154,10 @@ export async function createNote(value: string | IObject, resolver: Resolver, si
})
: null;
// 引用
let quote: Note | undefined | null;
const quoteUrl = extractQuoteUrl(note.tag);
if (note._misskey_quote || note.quoteUri) {
if (quoteUrl || note._misskey_quote || note.quoteUri) {
const tryResolveNote = async (uri: string): Promise<{
status: 'ok';
res: Note | null;
@ -184,10 +184,16 @@ export async function createNote(value: string | IObject, resolver: Resolver, si
}
};
const uris = unique([note._misskey_quote, note.quoteUri].filter((x): x is string => typeof x === 'string'));
const results = await Promise.all(uris.map(uri => tryResolveNote(uri)));
quote = results.filter((x): x is { status: 'ok', res: Note | null } => x.status === 'ok').map(x => x.res).find(x => x);
const uris = unique([quoteUrl, note._misskey_quote, note.quoteUri].filter((x): x is string => typeof x === 'string'));
// check the urls sequentially and abort early to not do unnecessary HTTP requests
// picks the first one that works
for (const uri in uris) {
const res = await tryResolveNote(uri);
if (res.status === 'ok') {
quote = res.res;
break;
}
}
if (!quote) {
if (results.some(x => x.status === 'temperror')) {
throw new Error('quote resolve failed');

View file

@ -1,5 +1,5 @@
import { toArray } from '@/prelude/array.js';
import { IObject, isHashtag, IApHashtag } from '../type.js';
import { IObject, isHashtag, IApHashtag, isLink, ILink } from '../type.js';
export function extractApHashtags(tags: IObject | IObject[] | null | undefined) {
if (tags == null) return [];
@ -16,3 +16,39 @@ export function extractApHashtagObjects(tags: IObject | IObject[] | null | undef
if (tags == null) return [];
return toArray(tags).filter(isHashtag);
}
// implements FEP-e232: Object Links (2022-12-23 version)
export function extractQuoteUrl(tags: IObject | IObject[] | null | undefined): string | null {
if (tags == null) return null;
// filter out correct links
let quotes: ILink[] = toArray(tags)
.filter(isLink)
.filter(link =>
[
'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
'application/activity+json'
].includes(link.mediaType?.toLowerCase())
);
// sort quotes with the right rel first
function hasRel(link: ILink): boolean {
link.rel != null
&&
toArray(link.rel)
.includes('https://misskey-hub.net/ns#_misskey_quote')
}
quotes.sort((a, b) => {
return hasRel(b) - hasRel(a);
});
// deduplicate by href
quotes = quotes.filter((x, i, arr) => arr.findIndex(y => x.href === y.href) === i);
if (quotes.length === 0) return null;
// If there is more than one quote, we just pick the first/a random one.
// Note that links with the correct `rel` were sorted to the front above
// so they will be preferred.
return quotes[0];
}

View file

@ -279,6 +279,13 @@ export interface IFlag extends IActivity {
type: 'Flag';
}
export interface ILink extends IObject {
type: 'Link';
href: string;
rel?: string | string[];
mediaType?: string;
}
export const isCreate = (object: IObject): object is ICreate => getApType(object) === 'Create';
export const isDelete = (object: IObject): object is IDelete => getApType(object) === 'Delete';
export const isUpdate = (object: IObject): object is IUpdate => getApType(object) === 'Update';
@ -293,3 +300,11 @@ export const isLike = (object: IObject): object is ILike => getApType(object) ==
export const isAnnounce = (object: IObject): object is IAnnounce => getApType(object) === 'Announce';
export const isBlock = (object: IObject): object is IBlock => getApType(object) === 'Block';
export const isFlag = (object: IObject): object is IFlag => getApType(object) === 'Flag';
export const isLink = (object: IObject): object is ILink => getApType(object) === 'Link'
&& typeof object.href === 'string'
&& (
object.rel == undefined
|| typeof object.rel === 'string'
|| (Array.isArray(object.rel) && object.rel.every(x => typeof x === 'string'))
)
&& (object.mediaType == undefined || typeof object.mediaType === 'string');