diff --git a/src/mfm/toString.ts b/src/mfm/toString.ts
new file mode 100644
index 0000000000..5b53105b32
--- /dev/null
+++ b/src/mfm/toString.ts
@@ -0,0 +1,112 @@
+import { MfmForest, MfmTree } from './prelude';
+import { nyaize } from '../misc/nyaize';
+
+export type RestoreOptions = {
+ doNyaize?: boolean;
+};
+
+export function toString(tokens: MfmForest | null, opts?: RestoreOptions): string {
+
+ if (tokens === null) return '';
+
+ function appendChildren(children: MfmForest, opts?: RestoreOptions): string {
+ return children.map(t => handlers[t.node.type](t, opts)).join('');
+ }
+
+ const handlers: { [key: string]: (token: MfmTree, opts?: RestoreOptions) => string } = {
+ bold(token, opts) {
+ return `**${appendChildren(token.children, opts)}**`;
+ },
+
+ big(token, opts) {
+ return `***${appendChildren(token.children, opts)}***`;
+ },
+
+ small(token, opts) {
+ return `${appendChildren(token.children, opts)}`;
+ },
+
+ strike(token, opts) {
+ return `~~${appendChildren(token.children, opts)}~~`;
+ },
+
+ italic(token, opts) {
+ return `${appendChildren(token.children, opts)}`;
+ },
+
+ motion(token, opts) {
+ return `${appendChildren(token.children, opts)}`;
+ },
+
+ spin(token, opts) {
+ return `${appendChildren(token.children, opts)}`;
+ },
+
+ jump(token, opts) {
+ return `${appendChildren(token.children, opts)}`;
+ },
+
+ flip(token, opts) {
+ return `${appendChildren(token.children, opts)}`;
+ },
+
+ blockCode(token) {
+ return `\`\`\`${token.node.props.lang || ''}\n${token.node.props.code}\n\`\`\`\n`;
+ },
+
+ center(token, opts) {
+ return `
${appendChildren(token.children, opts)}`;
+ },
+
+ emoji(token) {
+ return (token.node.props.emoji ? token.node.props.emoji : `:${token.node.props.name}:`);
+ },
+
+ hashtag(token) {
+ return `#${token.node.props.hashtag}`;
+ },
+
+ inlineCode(token) {
+ return `\`${token.node.props.code}\``;
+ },
+
+ mathInline(token) {
+ return `\\(${token.node.props.formula}\\)`;
+ },
+
+ mathBlock(token) {
+ return `\\[${token.node.props.formula}\\]`;
+ },
+
+ link(token, opts) {
+ return `[${appendChildren(token.children, opts)}](${token.node.props.url})`;
+ },
+
+ mention(token) {
+ return token.node.props.canonical;
+ },
+
+ quote(token) {
+ return `${appendChildren(token.children, {doNyaize: false}).replace(/^/gm,'>').trim()}\n`;
+ },
+
+ title(token, opts) {
+ return `[${appendChildren(token.children, opts)}]\n`;
+ },
+
+ text(token, opts) {
+ return (opts && opts.doNyaize) ? nyaize(token.node.props.text) : token.node.props.text;
+ },
+
+ url(token) {
+ return `<${token.node.props.url}>`;
+ },
+
+ search(token, opts) {
+ const query = token.node.props.query;
+ return `${(opts && opts.doNyaize ? nyaize(query) : query)} [search]\n`;
+ }
+ };
+
+ return appendChildren(tokens, { doNyaize: (opts && opts.doNyaize) || false }).trim();
+}
diff --git a/src/misc/nyaize.ts b/src/misc/nyaize.ts
index 9fbfc8b500..6ee3b68477 100644
--- a/src/misc/nyaize.ts
+++ b/src/misc/nyaize.ts
@@ -1,8 +1,5 @@
-import rndstr from 'rndstr';
-
export function nyaize(text: string): string {
- const [toNyaize, exclusionMap] = exclude(text);
- const nyaized = toNyaize
+ return text
// ja-JP
.replace(/な/g, 'にゃ').replace(/ナ/g, 'ニャ').replace(/ナ/g, 'ニャ')
// en-US
@@ -13,34 +10,4 @@ export function nyaize(text: string): string {
))
.replace(/(다$)|(다(?=\.))|(다(?= ))|(다(?=!))|(다(?=\?))/gm, '다냥')
.replace(/(야(?=\?))|(야$)|(야(?= ))/gm, '냥');
- return replaceExceptions(nyaized, exclusionMap);
-}
-
-function exclude(text: string): [string, Record] {
- const map: Record = {};
- function substitute(match: string): string {
- let randomstr: string;
- do {
- randomstr = rndstr({ length: 16, chars: '🀀-🀫' });
- } while(Object.prototype.hasOwnProperty.call(map, randomstr));
- map[randomstr] = match;
- return randomstr;
- }
- const replaced = text
- .replace(/```(.+?)?\n([\s\S]+?)```(\n|$)/gm, match => substitute(match)) // code block
- .replace(/`([^`\n]+?)`/g, match => substitute(match)) // inline code
- .replace(/(https?:\/\/.*?)(?= |$)/gm, match => substitute(match)) // URL
- .replace(/:([a-z0-9_+-]+):/gim, match => substitute(match)) // emoji
- .replace(/#([^\s.,!?'"#:\/\[\]【】]+)/gm, match => substitute(match)) // hashtag
- .replace(/@\w([\w-]*\w)?(?:@[\w.\-]+\w)?/gm, match => substitute(match)); // mention
- return [replaced, map];
-}
-
-function replaceExceptions(text: string, map: Record): string {
- for (const rule in map) {
- if (Object.prototype.hasOwnProperty.call(map, rule)) {
- text = text.replace(rule, map[rule]);
- }
- }
- return text;
}
diff --git a/src/models/repositories/note.ts b/src/models/repositories/note.ts
index cc856f2ba1..73d7ad86eb 100644
--- a/src/models/repositories/note.ts
+++ b/src/models/repositories/note.ts
@@ -1,12 +1,13 @@
import { EntityRepository, Repository, In } from 'typeorm';
import { Note } from '../entities/note';
import { User } from '../entities/user';
-import { nyaize } from '../../misc/nyaize';
import { Emojis, Users, PollVotes, DriveFiles, NoteReactions, Followings, Polls } from '..';
import { ensure } from '../../prelude/ensure';
import { SchemaType } from '../../misc/schema';
import { awaitAll } from '../../prelude/await-all';
import { convertLegacyReaction, convertLegacyReactions } from '../../misc/reaction-lib';
+import { toString } from '../../mfm/toString';
+import { parse } from '../../mfm/parse';
export type PackedNote = SchemaType;
@@ -217,7 +218,8 @@ export class NoteRepository extends Repository {
});
if (packed.user.isCat && packed.text) {
- packed.text = nyaize(packed.text);
+ const tokens = packed.text ? parse(packed.text) : [];
+ packed.text = toString(tokens, { doNyaize: true });
}
if (!opts.skipHide) {