APメンションはaudienceじゃなくてtagを参照するなど (#6128)
* APメンションはaudienceじゃなくてtagを参照するなど * AP/tag/Mentionではurlじゃなくてuriを提示する * createPersonでaliasが入力された場合に対応 * AP HTMLパースでMention/Hashtag判定にtagを使うように * fix * indent * use hashtag name * fix * URLエンコード不要だったら<>を使わないの条件が消えたたのを修正
This commit is contained in:
		
							parent
							
								
									8bb311df51
								
							
						
					
					
						commit
						99fc77b678
					
				
					 8 changed files with 128 additions and 64 deletions
				
			
		| 
						 | 
					@ -1,7 +1,7 @@
 | 
				
			||||||
import { parseFragment, DefaultTreeDocumentFragment } from 'parse5';
 | 
					import { parseFragment, DefaultTreeDocumentFragment } from 'parse5';
 | 
				
			||||||
import { urlRegex } from './prelude';
 | 
					import { urlRegex } from './prelude';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function fromHtml(html: string): string {
 | 
					export function fromHtml(html: string, hashtagNames?: string[]): string {
 | 
				
			||||||
	const dom = parseFragment(html) as DefaultTreeDocumentFragment;
 | 
						const dom = parseFragment(html) as DefaultTreeDocumentFragment;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	let text = '';
 | 
						let text = '';
 | 
				
			||||||
| 
						 | 
					@ -36,12 +36,10 @@ export function fromHtml(html: string): string {
 | 
				
			||||||
				const txt = getText(node);
 | 
									const txt = getText(node);
 | 
				
			||||||
				const rel = node.attrs.find((x: any) => x.name == 'rel');
 | 
									const rel = node.attrs.find((x: any) => x.name == 'rel');
 | 
				
			||||||
				const href = node.attrs.find((x: any) => x.name == 'href');
 | 
									const href = node.attrs.find((x: any) => x.name == 'href');
 | 
				
			||||||
				const _class = node.attrs.find((x: any) => x.name == 'class');
 | 
					 | 
				
			||||||
				const isHashtag = rel?.value?.match('tag') || _class?.value?.match('hashtag');
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
				// ハッシュタグ / hrefがない / txtがURL
 | 
									// ハッシュタグ
 | 
				
			||||||
				if (isHashtag || !href || href.value == txt) {
 | 
									if (hashtagNames && href && hashtagNames.map(x => x.toLowerCase()).includes(txt.toLowerCase())) {
 | 
				
			||||||
					text += isHashtag || txt.match(urlRegex) ? txt : `<${txt}>`;
 | 
										text += txt;
 | 
				
			||||||
				// メンション
 | 
									// メンション
 | 
				
			||||||
				} else if (txt.startsWith('@') && !(rel && rel.value.match(/^me /))) {
 | 
									} else if (txt.startsWith('@') && !(rel && rel.value.match(/^me /))) {
 | 
				
			||||||
					const part = txt.split('@');
 | 
										const part = txt.split('@');
 | 
				
			||||||
| 
						 | 
					@ -56,7 +54,7 @@ export function fromHtml(html: string): string {
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
				// その他
 | 
									// その他
 | 
				
			||||||
				} else {
 | 
									} else {
 | 
				
			||||||
					text += `[${txt}](${href.value})`;
 | 
										text += (!href || (txt === href.value && txt.match(urlRegex))) ? txt : `[${txt}](${href.value})`;
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
				break;
 | 
									break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										9
									
								
								src/remote/activitypub/misc/html-to-mfm.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/remote/activitypub/misc/html-to-mfm.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,9 @@
 | 
				
			||||||
 | 
					import { IObject } from '../type';
 | 
				
			||||||
 | 
					import { extractApHashtagObjects } from '../models/tag';
 | 
				
			||||||
 | 
					import { fromHtml } from '../../../mfm/fromHtml';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function htmlToMfm(html: string, tag?: IObject | IObject[]) {
 | 
				
			||||||
 | 
						const hashtagNames = extractApHashtagObjects(tag).map(x => x.name).filter((x): x is string => x != null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return fromHtml(html, hashtagNames);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										24
									
								
								src/remote/activitypub/models/mention.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								src/remote/activitypub/models/mention.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,24 @@
 | 
				
			||||||
 | 
					import { toArray, unique } from '../../../prelude/array';
 | 
				
			||||||
 | 
					import { IObject, isMention, IApMention } from '../type';
 | 
				
			||||||
 | 
					import { resolvePerson } from './person';
 | 
				
			||||||
 | 
					import * as promiseLimit from 'promise-limit';
 | 
				
			||||||
 | 
					import Resolver from '../resolver';
 | 
				
			||||||
 | 
					import { User } from '../../../models/entities/user';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function extractApMentions(tags: IObject | IObject[] | null | undefined) {
 | 
				
			||||||
 | 
						const hrefs = unique(extractApMentionObjects(tags).map(x => x.href as string));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const resolver = new Resolver();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const limit = promiseLimit<User | null>(2);
 | 
				
			||||||
 | 
						const mentionedUsers = (await Promise.all(
 | 
				
			||||||
 | 
							hrefs.map(x => limit(() => resolvePerson(x, resolver).catch(() => null)))
 | 
				
			||||||
 | 
						)).filter((x): x is User => x != null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return mentionedUsers;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function extractApMentionObjects(tags: IObject | IObject[] | null | undefined): IApMention[] {
 | 
				
			||||||
 | 
						if (tags == null) return [];
 | 
				
			||||||
 | 
						return toArray(tags).filter(isMention);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -6,9 +6,9 @@ import post from '../../../services/note/create';
 | 
				
			||||||
import { resolvePerson, updatePerson } from './person';
 | 
					import { resolvePerson, updatePerson } from './person';
 | 
				
			||||||
import { resolveImage } from './image';
 | 
					import { resolveImage } from './image';
 | 
				
			||||||
import { IRemoteUser } from '../../../models/entities/user';
 | 
					import { IRemoteUser } from '../../../models/entities/user';
 | 
				
			||||||
import { fromHtml } from '../../../mfm/fromHtml';
 | 
					import { htmlToMfm } from '../misc/html-to-mfm';
 | 
				
			||||||
import { ITag, extractHashtags } from './tag';
 | 
					import { extractApHashtags } from './tag';
 | 
				
			||||||
import { unique } from '../../../prelude/array';
 | 
					import { unique, toArray, toSingle } from '../../../prelude/array';
 | 
				
			||||||
import { extractPollFromQuestion } from './question';
 | 
					import { extractPollFromQuestion } from './question';
 | 
				
			||||||
import vote from '../../../services/note/polls/vote';
 | 
					import vote from '../../../services/note/polls/vote';
 | 
				
			||||||
import { apLogger } from '../logger';
 | 
					import { apLogger } from '../logger';
 | 
				
			||||||
| 
						 | 
					@ -17,7 +17,7 @@ import { deliverQuestionUpdate } from '../../../services/note/polls/update';
 | 
				
			||||||
import { extractDbHost, toPuny } from '../../../misc/convert-host';
 | 
					import { extractDbHost, toPuny } from '../../../misc/convert-host';
 | 
				
			||||||
import { Notes, Emojis, Polls, MessagingMessages } from '../../../models';
 | 
					import { Notes, Emojis, Polls, MessagingMessages } from '../../../models';
 | 
				
			||||||
import { Note } from '../../../models/entities/note';
 | 
					import { Note } from '../../../models/entities/note';
 | 
				
			||||||
import { IObject, getOneApId, getApId, validPost, IPost } from '../type';
 | 
					import { IObject, getOneApId, getApId, validPost, IPost, isEmoji } from '../type';
 | 
				
			||||||
import { Emoji } from '../../../models/entities/emoji';
 | 
					import { Emoji } from '../../../models/entities/emoji';
 | 
				
			||||||
import { genId } from '../../../misc/gen-id';
 | 
					import { genId } from '../../../misc/gen-id';
 | 
				
			||||||
import { fetchMeta } from '../../../misc/fetch-meta';
 | 
					import { fetchMeta } from '../../../misc/fetch-meta';
 | 
				
			||||||
| 
						 | 
					@ -25,6 +25,7 @@ import { ensure } from '../../../prelude/ensure';
 | 
				
			||||||
import { getApLock } from '../../../misc/app-lock';
 | 
					import { getApLock } from '../../../misc/app-lock';
 | 
				
			||||||
import { createMessage } from '../../../services/messages/create';
 | 
					import { createMessage } from '../../../services/messages/create';
 | 
				
			||||||
import { parseAudience } from '../audience';
 | 
					import { parseAudience } from '../audience';
 | 
				
			||||||
 | 
					import { extractApMentions } from './mention';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const logger = apLogger;
 | 
					const logger = apLogger;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -113,7 +114,6 @@ export async function createNote(value: string | IObject, resolver?: Resolver, s
 | 
				
			||||||
	const noteAudience = await parseAudience(actor, note.to, note.cc);
 | 
						const noteAudience = await parseAudience(actor, note.to, note.cc);
 | 
				
			||||||
	let visibility = noteAudience.visibility;
 | 
						let visibility = noteAudience.visibility;
 | 
				
			||||||
	const visibleUsers = noteAudience.visibleUsers;
 | 
						const visibleUsers = noteAudience.visibleUsers;
 | 
				
			||||||
	const apMentions = noteAudience.mentionedUsers;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Audience (to, cc) が指定されてなかった場合
 | 
						// Audience (to, cc) が指定されてなかった場合
 | 
				
			||||||
	if (visibility === 'specified' && visibleUsers.length === 0) {
 | 
						if (visibility === 'specified' && visibleUsers.length === 0) {
 | 
				
			||||||
| 
						 | 
					@ -125,7 +125,8 @@ export async function createNote(value: string | IObject, resolver?: Resolver, s
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	let isTalk = note._misskey_talk && visibility === 'specified';
 | 
						let isTalk = note._misskey_talk && visibility === 'specified';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const apHashtags = await extractHashtags(note.tag);
 | 
						const apMentions = await extractApMentions(note.tag);
 | 
				
			||||||
 | 
						const apHashtags = await extractApHashtags(note.tag);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 添付ファイル
 | 
						// 添付ファイル
 | 
				
			||||||
	// TODO: attachmentは必ずしもImageではない
 | 
						// TODO: attachmentは必ずしもImageではない
 | 
				
			||||||
| 
						 | 
					@ -210,7 +211,7 @@ export async function createNote(value: string | IObject, resolver?: Resolver, s
 | 
				
			||||||
	const cw = note.summary === '' ? null : note.summary;
 | 
						const cw = note.summary === '' ? null : note.summary;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// テキストのパース
 | 
						// テキストのパース
 | 
				
			||||||
	const text = note._misskey_content || (note.content ? fromHtml(note.content) : null);
 | 
						const text = note._misskey_content || (note.content ? htmlToMfm(note.content, note.tag) : null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// vote
 | 
						// vote
 | 
				
			||||||
	if (reply && reply.hasPoll) {
 | 
						if (reply && reply.hasPoll) {
 | 
				
			||||||
| 
						 | 
					@ -319,15 +320,16 @@ export async function resolveNote(value: string | IObject, resolver?: Resolver):
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export async function extractEmojis(tags: ITag[], host: string): Promise<Emoji[]> {
 | 
					export async function extractEmojis(tags: IObject | IObject[], host: string): Promise<Emoji[]> {
 | 
				
			||||||
	host = toPuny(host);
 | 
						host = toPuny(host);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (!tags) return [];
 | 
						if (!tags) return [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const eomjiTags = tags.filter(tag => tag.type === 'Emoji' && tag.icon && tag.icon.url && tag.name);
 | 
						const eomjiTags = toArray(tags).filter(isEmoji);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return await Promise.all(eomjiTags.map(async tag => {
 | 
						return await Promise.all(eomjiTags.map(async tag => {
 | 
				
			||||||
		const name = tag.name!.replace(/^:/, '').replace(/:$/, '');
 | 
							const name = tag.name!.replace(/^:/, '').replace(/:$/, '');
 | 
				
			||||||
 | 
							tag.icon = toSingle(tag.icon);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		const exists = await Emojis.findOne({
 | 
							const exists = await Emojis.findOne({
 | 
				
			||||||
			host,
 | 
								host,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,12 +3,12 @@ import * as promiseLimit from 'promise-limit';
 | 
				
			||||||
import config from '../../../config';
 | 
					import config from '../../../config';
 | 
				
			||||||
import Resolver from '../resolver';
 | 
					import Resolver from '../resolver';
 | 
				
			||||||
import { resolveImage } from './image';
 | 
					import { resolveImage } from './image';
 | 
				
			||||||
import { isCollectionOrOrderedCollection, isCollection, IPerson, getApId } from '../type';
 | 
					import { isCollectionOrOrderedCollection, isCollection, IPerson, getApId, IObject, isPropertyValue, IApPropertyValue } from '../type';
 | 
				
			||||||
import { fromHtml } from '../../../mfm/fromHtml';
 | 
					import { fromHtml } from '../../../mfm/fromHtml';
 | 
				
			||||||
 | 
					import { htmlToMfm } from '../misc/html-to-mfm';
 | 
				
			||||||
import { resolveNote, extractEmojis } from './note';
 | 
					import { resolveNote, extractEmojis } from './note';
 | 
				
			||||||
import { registerOrFetchInstanceDoc } from '../../../services/register-or-fetch-instance-doc';
 | 
					import { registerOrFetchInstanceDoc } from '../../../services/register-or-fetch-instance-doc';
 | 
				
			||||||
import { ITag, extractHashtags } from './tag';
 | 
					import { extractApHashtags } from './tag';
 | 
				
			||||||
import { IIdentifier } from './identifier';
 | 
					 | 
				
			||||||
import { apLogger } from '../logger';
 | 
					import { apLogger } from '../logger';
 | 
				
			||||||
import { Note } from '../../../models/entities/note';
 | 
					import { Note } from '../../../models/entities/note';
 | 
				
			||||||
import { updateUsertags } from '../../../services/update-hashtag';
 | 
					import { updateUsertags } from '../../../services/update-hashtag';
 | 
				
			||||||
| 
						 | 
					@ -134,7 +134,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<Us
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const { fields } = analyzeAttachments(person.attachment || []);
 | 
						const { fields } = analyzeAttachments(person.attachment || []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const tags = extractHashtags(person.tag).map(tag => tag.toLowerCase()).splice(0, 32);
 | 
						const tags = extractApHashtags(person.tag).map(tag => tag.toLowerCase()).splice(0, 32);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const isBot = object.type == 'Service';
 | 
						const isBot = object.type == 'Service';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -165,7 +165,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<Us
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			await transactionalEntityManager.save(new UserProfile({
 | 
								await transactionalEntityManager.save(new UserProfile({
 | 
				
			||||||
				userId: user.id,
 | 
									userId: user.id,
 | 
				
			||||||
				description: person.summary ? fromHtml(person.summary) : null,
 | 
									description: person.summary ? htmlToMfm(person.summary, person.tag) : null,
 | 
				
			||||||
				url: person.url,
 | 
									url: person.url,
 | 
				
			||||||
				fields,
 | 
									fields,
 | 
				
			||||||
				userHost: host
 | 
									userHost: host
 | 
				
			||||||
| 
						 | 
					@ -180,11 +180,20 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<Us
 | 
				
			||||||
	} catch (e) {
 | 
						} catch (e) {
 | 
				
			||||||
		// duplicate key error
 | 
							// duplicate key error
 | 
				
			||||||
		if (isDuplicateKeyValueError(e)) {
 | 
							if (isDuplicateKeyValueError(e)) {
 | 
				
			||||||
			throw new Error('already registered');
 | 
								// /users/@a => /users/:id のように入力がaliasなときにエラーになることがあるのを対応
 | 
				
			||||||
		}
 | 
								const u = await Users.findOne({
 | 
				
			||||||
 | 
									uri: person.id
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		logger.error(e);
 | 
								if (u) {
 | 
				
			||||||
		throw e;
 | 
									user = u as IRemoteUser;
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									throw new Error('already registered');
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								logger.error(e);
 | 
				
			||||||
 | 
								throw e;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Register host
 | 
						// Register host
 | 
				
			||||||
| 
						 | 
					@ -308,7 +317,7 @@ export async function updatePerson(uri: string, resolver?: Resolver | null, hint
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const { fields } = analyzeAttachments(person.attachment || []);
 | 
						const { fields } = analyzeAttachments(person.attachment || []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const tags = extractHashtags(person.tag).map(tag => tag.toLowerCase()).splice(0, 32);
 | 
						const tags = extractApHashtags(person.tag).map(tag => tag.toLowerCase()).splice(0, 32);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const updates = {
 | 
						const updates = {
 | 
				
			||||||
		lastFetchedAt: new Date(),
 | 
							lastFetchedAt: new Date(),
 | 
				
			||||||
| 
						 | 
					@ -346,7 +355,7 @@ export async function updatePerson(uri: string, resolver?: Resolver | null, hint
 | 
				
			||||||
	await UserProfiles.update({ userId: exist.id }, {
 | 
						await UserProfiles.update({ userId: exist.id }, {
 | 
				
			||||||
		url: person.url,
 | 
							url: person.url,
 | 
				
			||||||
		fields,
 | 
							fields,
 | 
				
			||||||
		description: person.summary ? fromHtml(person.summary) : null,
 | 
							description: person.summary ? htmlToMfm(person.summary, person.tag) : null,
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// ハッシュタグ更新
 | 
						// ハッシュタグ更新
 | 
				
			||||||
| 
						 | 
					@ -384,16 +393,6 @@ export async function resolvePerson(uri: string, resolver?: Resolver): Promise<U
 | 
				
			||||||
	return await createPerson(uri, resolver);
 | 
						return await createPerson(uri, resolver);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const isPropertyValue = (x: {
 | 
					 | 
				
			||||||
		type: string,
 | 
					 | 
				
			||||||
		name?: string,
 | 
					 | 
				
			||||||
		value?: string
 | 
					 | 
				
			||||||
	}) =>
 | 
					 | 
				
			||||||
		x &&
 | 
					 | 
				
			||||||
		x.type === 'PropertyValue' &&
 | 
					 | 
				
			||||||
		typeof x.name === 'string' &&
 | 
					 | 
				
			||||||
		typeof x.value === 'string';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const services: {
 | 
					const services: {
 | 
				
			||||||
		[x: string]: (id: string, username: string) => any
 | 
							[x: string]: (id: string, username: string) => any
 | 
				
			||||||
	} = {
 | 
						} = {
 | 
				
			||||||
| 
						 | 
					@ -409,7 +408,7 @@ const $discord = (id: string, name: string) => {
 | 
				
			||||||
	return { id, username, discriminator };
 | 
						return { id, username, discriminator };
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function addService(target: { [x: string]: any }, source: IIdentifier) {
 | 
					function addService(target: { [x: string]: any }, source: IApPropertyValue) {
 | 
				
			||||||
	const service = services[source.name];
 | 
						const service = services[source.name];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (typeof source.value !== 'string')
 | 
						if (typeof source.value !== 'string')
 | 
				
			||||||
| 
						 | 
					@ -421,7 +420,7 @@ function addService(target: { [x: string]: any }, source: IIdentifier) {
 | 
				
			||||||
		target[source.name.split(':')[2]] = service(id, username);
 | 
							target[source.name.split(':')[2]] = service(id, username);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function analyzeAttachments(attachments: ITag[]) {
 | 
					export function analyzeAttachments(attachments: IObject | IObject[] | undefined) {
 | 
				
			||||||
	const fields: {
 | 
						const fields: {
 | 
				
			||||||
		name: string,
 | 
							name: string,
 | 
				
			||||||
		value: string
 | 
							value: string
 | 
				
			||||||
| 
						 | 
					@ -430,12 +429,12 @@ export function analyzeAttachments(attachments: ITag[]) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (Array.isArray(attachments)) {
 | 
						if (Array.isArray(attachments)) {
 | 
				
			||||||
		for (const attachment of attachments.filter(isPropertyValue)) {
 | 
							for (const attachment of attachments.filter(isPropertyValue)) {
 | 
				
			||||||
			if (isPropertyValue(attachment.identifier!)) {
 | 
								if (isPropertyValue(attachment.identifier)) {
 | 
				
			||||||
				addService(services, attachment.identifier!);
 | 
									addService(services, attachment.identifier);
 | 
				
			||||||
			} else {
 | 
								} else {
 | 
				
			||||||
				fields.push({
 | 
									fields.push({
 | 
				
			||||||
					name: attachment.name!,
 | 
										name: attachment.name,
 | 
				
			||||||
					value: fromHtml(attachment.value!)
 | 
										value: fromHtml(attachment.value)
 | 
				
			||||||
				});
 | 
									});
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,26 +1,18 @@
 | 
				
			||||||
import { IIcon } from './icon';
 | 
					import { toArray } from '../../../prelude/array';
 | 
				
			||||||
import { IIdentifier } from './identifier';
 | 
					import { IObject, isHashtag, IApHashtag } from '../type';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/***
 | 
					export function extractApHashtags(tags: IObject | IObject[] | null | undefined) {
 | 
				
			||||||
 * tag (ActivityPub)
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export type ITag = {
 | 
					 | 
				
			||||||
	id: string;
 | 
					 | 
				
			||||||
	type: string;
 | 
					 | 
				
			||||||
	name?: string;
 | 
					 | 
				
			||||||
	value?: string;
 | 
					 | 
				
			||||||
	updated?: Date;
 | 
					 | 
				
			||||||
	icon?: IIcon;
 | 
					 | 
				
			||||||
	identifier?: IIdentifier;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function extractHashtags(tags: ITag[] | null | undefined): string[] {
 | 
					 | 
				
			||||||
	if (tags == null) return [];
 | 
						if (tags == null) return [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const hashtags = tags.filter(tag => tag.type === 'Hashtag' && typeof tag.name == 'string');
 | 
						const hashtags = extractApHashtagObjects(tags);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return hashtags.map(tag => {
 | 
						return hashtags.map(tag => {
 | 
				
			||||||
		const m = tag.name ? tag.name.match(/^#(.+)/) : null;
 | 
							const m = tag.name.match(/^#(.+)/);
 | 
				
			||||||
		return m ? m[1] : null;
 | 
							return m ? m[1] : null;
 | 
				
			||||||
	}).filter(x => x != null) as string[];
 | 
						}).filter((x): x is string => x != null);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function extractApHashtagObjects(tags: IObject | IObject[] | null | undefined): IApHashtag[] {
 | 
				
			||||||
 | 
						if (tags == null) return [];
 | 
				
			||||||
 | 
						return toArray(tags).filter(isHashtag);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,6 +4,6 @@ import { Users } from '../../../models';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default (mention: User) => ({
 | 
					export default (mention: User) => ({
 | 
				
			||||||
	type: 'Mention',
 | 
						type: 'Mention',
 | 
				
			||||||
	href: Users.isRemoteUser(mention) ? mention.uri : `${config.url}/@${(mention as ILocalUser).username}`,
 | 
						href: Users.isRemoteUser(mention) ? mention.uri : `${config.url}/users/${(mention as ILocalUser).id}`,
 | 
				
			||||||
	name: Users.isRemoteUser(mention) ? `@${mention.username}@${mention.host}` : `@${(mention as ILocalUser).username}`,
 | 
						name: Users.isRemoteUser(mention) ? `@${mention.username}@${mention.host}` : `@${(mention as ILocalUser).username}`,
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -20,7 +20,8 @@ export interface IObject {
 | 
				
			||||||
	icon?: any;
 | 
						icon?: any;
 | 
				
			||||||
	image?: any;
 | 
						image?: any;
 | 
				
			||||||
	url?: string;
 | 
						url?: string;
 | 
				
			||||||
	tag?: any[];
 | 
						href?: string;
 | 
				
			||||||
 | 
						tag?: IObject | IObject[];
 | 
				
			||||||
	sensitive?: boolean;
 | 
						sensitive?: boolean;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -130,6 +131,45 @@ export const isOrderedCollection = (object: IObject): object is IOrderedCollecti
 | 
				
			||||||
export const isCollectionOrOrderedCollection = (object: IObject): object is ICollection | IOrderedCollection =>
 | 
					export const isCollectionOrOrderedCollection = (object: IObject): object is ICollection | IOrderedCollection =>
 | 
				
			||||||
	isCollection(object) || isOrderedCollection(object);
 | 
						isCollection(object) || isOrderedCollection(object);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface IApPropertyValue extends IObject {
 | 
				
			||||||
 | 
						type: 'PropertyValue';
 | 
				
			||||||
 | 
						identifier: IApPropertyValue;
 | 
				
			||||||
 | 
						name: string;
 | 
				
			||||||
 | 
						value: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const isPropertyValue = (object: IObject): object is IApPropertyValue =>
 | 
				
			||||||
 | 
						object &&
 | 
				
			||||||
 | 
						object.type === 'PropertyValue' &&
 | 
				
			||||||
 | 
						typeof object.name === 'string' &&
 | 
				
			||||||
 | 
						typeof (object as any).value === 'string';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface IApMention extends IObject {
 | 
				
			||||||
 | 
						type: 'Mention';
 | 
				
			||||||
 | 
						href: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const isMention = (object: IObject): object is IApMention=>
 | 
				
			||||||
 | 
						object.type === 'Mention' &&
 | 
				
			||||||
 | 
						typeof object.href === 'string';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface IApHashtag extends IObject {
 | 
				
			||||||
 | 
						type: 'Hashtag';
 | 
				
			||||||
 | 
						name: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const isHashtag = (object: IObject): object is IApHashtag =>
 | 
				
			||||||
 | 
						object.type === 'Hashtag' &&
 | 
				
			||||||
 | 
						typeof object.name === 'string';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface IApEmoji extends IObject {
 | 
				
			||||||
 | 
						type: 'Emoji';
 | 
				
			||||||
 | 
						updated: Date;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const isEmoji = (object: IObject): object is IApEmoji =>
 | 
				
			||||||
 | 
						object.type === 'Emoji' && !Array.isArray(object.icon) && object.icon.url != null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface ICreate extends IActivity {
 | 
					export interface ICreate extends IActivity {
 | 
				
			||||||
	type: 'Create';
 | 
						type: 'Create';
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue