activitypub: hashtags no longer displaying as links

Some hashtags sent from Mastodon were erroneously displayed as links.
This is because Mastodon seems to mangle hashtags containing non-ASCII
codepoints (such as e.g. umlauts). This lead to the previous code which
depended on the list of hashtags to not recognize a hashtag. Instead,
the `rel="tag"` microformat is recognized instead.

This makes the `htmlToMfm` wrapper function unnecessary so it was removed.

Changelog: Fixed
This commit is contained in:
Johann150 2022-12-02 19:31:57 +01:00
parent b4080d788d
commit 194fff3603
Signed by untrusted user: Johann150
GPG key ID: 9EE6577A2A06F8F1
4 changed files with 6 additions and 16 deletions

View file

@ -7,7 +7,7 @@ const treeAdapter = parse5.defaultTreeAdapter;
const urlRegex = /^https?:\/\/[\w\/:%#@$&?!()\[\]~.,=+\-]+/; const urlRegex = /^https?:\/\/[\w\/:%#@$&?!()\[\]~.,=+\-]+/;
const urlRegexFull = /^https?:\/\/[\w\/:%#@$&?!()\[\]~.,=+\-]+$/; const urlRegexFull = /^https?:\/\/[\w\/:%#@$&?!()\[\]~.,=+\-]+$/;
export function fromHtml(html: string, hashtagNames?: string[], quoteUri?: string | null): string { export function fromHtml(html: string, quoteUri?: string | null): string {
const dom = parse5.parseFragment( const dom = parse5.parseFragment(
// some AP servers like Pixelfed use br tags as well as newlines // some AP servers like Pixelfed use br tags as well as newlines
html.replace(/<br\s?\/?>\r?\n/gi, '\n'), html.replace(/<br\s?\/?>\r?\n/gi, '\n'),
@ -63,7 +63,7 @@ export function fromHtml(html: string, hashtagNames?: string[], quoteUri?: strin
const href = node.attrs.find(x => x.name === 'href'); const href = node.attrs.find(x => x.name === 'href');
// hashtags // hashtags
if (hashtagNames && href && hashtagNames.map(x => x.toLowerCase()).includes(txt.toLowerCase())) { if (txt.startsWith('#') && href && /\btag\b/.test(rel?.value)) {
text += txt; text += txt;
// mentions // mentions
} else if (txt.startsWith('@') && !(rel && rel.value.match(/^me /))) { } else if (txt.startsWith('@') && !(rel && rel.value.match(/^me /))) {

View file

@ -1,9 +0,0 @@
import { IObject } from '../type.js';
import { extractApHashtagObjects } from '../models/tag.js';
import { fromHtml } from '@/mfm/from-html.js';
export function htmlToMfm(html: string, tag?: IObject | IObject[], quoteUri?: string | null) {
const hashtagNames = extractApHashtagObjects(tag).map(x => x.name).filter((x): x is string => x != null);
return fromHtml(html, hashtagNames, quoteUri);
}

View file

@ -16,11 +16,11 @@ import { fetchMeta } from '@/misc/fetch-meta.js';
import { getApLock } from '@/misc/app-lock.js'; import { getApLock } from '@/misc/app-lock.js';
import { createMessage } from '@/services/messages/create.js'; import { createMessage } from '@/services/messages/create.js';
import { StatusError } from '@/misc/fetch.js'; import { StatusError } from '@/misc/fetch.js';
import { fromHtml } from '@/mfm/from-html.js';
import { parseAudience } from '../audience.js'; import { parseAudience } from '../audience.js';
import { IObject, getOneApId, getApId, getOneApHrefNullable, validPost, IPost, isEmoji, getApType } from '../type.js'; import { IObject, getOneApId, getApId, getOneApHrefNullable, validPost, IPost, isEmoji, getApType } from '../type.js';
import DbResolver from '../db-resolver.js'; import DbResolver from '../db-resolver.js';
import Resolver from '../resolver.js'; import Resolver from '../resolver.js';
import { htmlToMfm } from '../misc/html-to-mfm.js';
import { apLogger } from '../logger.js'; import { apLogger } from '../logger.js';
import { resolvePerson } from './person.js'; import { resolvePerson } from './person.js';
import { resolveImage } from './image.js'; import { resolveImage } from './image.js';
@ -199,7 +199,7 @@ export async function createNote(value: string | IObject, resolver?: Resolver =
} else if (typeof note._misskey_content !== 'undefined') { } else if (typeof note._misskey_content !== 'undefined') {
text = note._misskey_content; text = note._misskey_content;
} else if (typeof note.content === 'string') { } else if (typeof note.content === 'string') {
text = htmlToMfm(note.content, note.tag, quote?.uri); text = fromHtml(note.content, quote?.uri);
} }
// vote // vote

View file

@ -24,7 +24,6 @@ import { uriPersonCache } from '@/services/user-cache.js';
import { publishInternalEvent } from '@/services/stream.js'; import { publishInternalEvent } from '@/services/stream.js';
import { db } from '@/db/postgre.js'; import { db } from '@/db/postgre.js';
import { apLogger } from '../logger.js'; import { apLogger } from '../logger.js';
import { htmlToMfm } from '../misc/html-to-mfm.js';
import { fromHtml } from '@/mfm/from-html.js'; import { fromHtml } from '@/mfm/from-html.js';
import { isCollectionOrOrderedCollection, isCollection, IActor, getApId, getOneApHrefNullable, IObject, isPropertyValue, IApPropertyValue, getApType, isActor } from '../type.js'; import { isCollectionOrOrderedCollection, isCollection, IActor, getApId, getOneApHrefNullable, IObject, isPropertyValue, IApPropertyValue, getApType, isActor } from '../type.js';
import Resolver from '../resolver.js'; import Resolver from '../resolver.js';
@ -185,7 +184,7 @@ export async function createPerson(uri: string, resolver?: Resolver = new Resolv
await transactionalEntityManager.save(new UserProfile({ await transactionalEntityManager.save(new UserProfile({
userId: user.id, userId: user.id,
description: person.summary ? htmlToMfm(truncate(person.summary, summaryLength), person.tag) : null, description: person.summary ? fromHtml(truncate(person.summary, summaryLength)) : null,
url: getOneApHrefNullable(person.url), url: getOneApHrefNullable(person.url),
fields, fields,
birthday: bday ? bday[0] : null, birthday: bday ? bday[0] : null,
@ -361,7 +360,7 @@ export async function updatePerson(uri: string, resolver?: Resolver = new Resolv
await UserProfiles.update({ userId: exist.id }, { await UserProfiles.update({ userId: exist.id }, {
url: getOneApHrefNullable(person.url), url: getOneApHrefNullable(person.url),
fields, fields,
description: person.summary ? htmlToMfm(truncate(person.summary, summaryLength), person.tag) : null, description: person.summary ? fromHtml(truncate(person.summary, summaryLength)) : null,
birthday: bday ? bday[0] : null, birthday: bday ? bday[0] : null,
location: person['vcard:Address'] || null, location: person['vcard:Address'] || null,
}); });