parent
c48cbd95f6
commit
80b5fda292
|
@ -24,6 +24,9 @@ export default Vue.component('misskey-flavored-markdown', {
|
||||||
i: {
|
i: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: null
|
default: null
|
||||||
|
},
|
||||||
|
customEmojis: {
|
||||||
|
required: false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -186,8 +189,8 @@ export default Vue.component('misskey-flavored-markdown', {
|
||||||
|
|
||||||
case 'emoji': {
|
case 'emoji': {
|
||||||
//#region カスタム絵文字
|
//#region カスタム絵文字
|
||||||
const customEmojis = (this.os.getMetaSync() || { emojis: [] }).emojis || [];
|
if (this.customEmojis != null) {
|
||||||
const customEmoji = customEmojis.find(e => e.name == token.emoji || (e.aliases || []).includes(token.emoji));
|
const customEmoji = this.customEmojis.find(e => e.name == token.emoji || (e.aliases || []).includes(token.emoji));
|
||||||
if (customEmoji) {
|
if (customEmoji) {
|
||||||
return [createElement('img', {
|
return [createElement('img', {
|
||||||
attrs: {
|
attrs: {
|
||||||
|
@ -198,6 +201,7 @@ export default Vue.component('misskey-flavored-markdown', {
|
||||||
}
|
}
|
||||||
})];
|
})];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
const emoji = emojilib.lib[token.emoji];
|
const emoji = emojilib.lib[token.emoji];
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<div class="text">
|
<div class="text">
|
||||||
<misskey-flavored-markdown v-if="note.text" :text="note.text"/>
|
<misskey-flavored-markdown v-if="note.text" :text="note.text" :customEmojis="p.emojis"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -45,7 +45,7 @@
|
||||||
<div class="text">
|
<div class="text">
|
||||||
<span v-if="p.isHidden" style="opacity: 0.5">%i18n:@private%</span>
|
<span v-if="p.isHidden" style="opacity: 0.5">%i18n:@private%</span>
|
||||||
<span v-if="p.deletedAt" style="opacity: 0.5">%i18n:@deleted%</span>
|
<span v-if="p.deletedAt" style="opacity: 0.5">%i18n:@deleted%</span>
|
||||||
<misskey-flavored-markdown v-if="p.text" :text="p.text" :i="$store.state.i"/>
|
<misskey-flavored-markdown v-if="p.text" :text="p.text" :i="$store.state.i" :customEmojis="p.emojis" />
|
||||||
</div>
|
</div>
|
||||||
<div class="files" v-if="p.files.length > 0">
|
<div class="files" v-if="p.files.length > 0">
|
||||||
<mk-media-list :media-list="p.files" :raw="true"/>
|
<mk-media-list :media-list="p.files" :raw="true"/>
|
||||||
|
|
|
@ -34,7 +34,7 @@
|
||||||
<div class="text">
|
<div class="text">
|
||||||
<span v-if="appearNote.isHidden" style="opacity: 0.5">%i18n:@private%</span>
|
<span v-if="appearNote.isHidden" style="opacity: 0.5">%i18n:@private%</span>
|
||||||
<a class="reply" v-if="appearNote.reply">%fa:reply%</a>
|
<a class="reply" v-if="appearNote.reply">%fa:reply%</a>
|
||||||
<misskey-flavored-markdown v-if="appearNote.text" :text="appearNote.text" :i="$store.state.i" :class="$style.text"/>
|
<misskey-flavored-markdown v-if="appearNote.text" :text="appearNote.text" :i="$store.state.i" :class="$style.text" :customEmojis="appearNote.emojis"/>
|
||||||
<a class="rp" v-if="appearNote.renote">RN:</a>
|
<a class="rp" v-if="appearNote.renote">RN:</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="files" v-if="appearNote.files.length > 0">
|
<div class="files" v-if="appearNote.files.length > 0">
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<span v-if="note.isHidden" style="opacity: 0.5">%i18n:@private%</span>
|
<span v-if="note.isHidden" style="opacity: 0.5">%i18n:@private%</span>
|
||||||
<span v-if="note.deletedAt" style="opacity: 0.5">%i18n:@deleted%</span>
|
<span v-if="note.deletedAt" style="opacity: 0.5">%i18n:@deleted%</span>
|
||||||
<a class="reply" v-if="note.replyId">%fa:reply%</a>
|
<a class="reply" v-if="note.replyId">%fa:reply%</a>
|
||||||
<misskey-flavored-markdown v-if="note.text" :text="note.text" :i="$store.state.i"/>
|
<misskey-flavored-markdown v-if="note.text" :text="note.text" :i="$store.state.i" :customEmojis="note.emojis"/>
|
||||||
<a class="rp" v-if="note.renoteId" :href="`/notes/${note.renoteId}`">RN: ...</a>
|
<a class="rp" v-if="note.renoteId" :href="`/notes/${note.renoteId}`">RN: ...</a>
|
||||||
</div>
|
</div>
|
||||||
<details v-if="note.files.length > 0">
|
<details v-if="note.files.length > 0">
|
||||||
|
|
|
@ -43,7 +43,7 @@
|
||||||
<div class="text">
|
<div class="text">
|
||||||
<span v-if="p.isHidden" style="opacity: 0.5">(%i18n:@private%)</span>
|
<span v-if="p.isHidden" style="opacity: 0.5">(%i18n:@private%)</span>
|
||||||
<span v-if="p.deletedAt" style="opacity: 0.5">(%i18n:@deleted%)</span>
|
<span v-if="p.deletedAt" style="opacity: 0.5">(%i18n:@deleted%)</span>
|
||||||
<misskey-flavored-markdown v-if="p.text" :text="p.text" :i="$store.state.i"/>
|
<misskey-flavored-markdown v-if="p.text" :text="p.text" :i="$store.state.i" :customEmojis="p.emojis"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="files" v-if="p.files.length > 0">
|
<div class="files" v-if="p.files.length > 0">
|
||||||
<mk-media-list :media-list="p.files" :raw="true"/>
|
<mk-media-list :media-list="p.files" :raw="true"/>
|
||||||
|
|
|
@ -30,7 +30,7 @@
|
||||||
<div class="text">
|
<div class="text">
|
||||||
<span v-if="appearNote.isHidden" style="opacity: 0.5">(%i18n:@private%)</span>
|
<span v-if="appearNote.isHidden" style="opacity: 0.5">(%i18n:@private%)</span>
|
||||||
<a class="reply" v-if="appearNote.reply">%fa:reply%</a>
|
<a class="reply" v-if="appearNote.reply">%fa:reply%</a>
|
||||||
<misskey-flavored-markdown v-if="appearNote.text" :text="appearNote.text" :i="$store.state.i" :class="$style.text"/>
|
<misskey-flavored-markdown v-if="appearNote.text" :text="appearNote.text" :i="$store.state.i" :class="$style.text" :customEmojis="appearNote.emojis"/>
|
||||||
<a class="rp" v-if="appearNote.renote != null">RN:</a>
|
<a class="rp" v-if="appearNote.renote != null">RN:</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="files" v-if="appearNote.files.length > 0">
|
<div class="files" v-if="appearNote.files.length > 0">
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<span v-if="note.isHidden" style="opacity: 0.5">(%i18n:@private%)</span>
|
<span v-if="note.isHidden" style="opacity: 0.5">(%i18n:@private%)</span>
|
||||||
<span v-if="note.deletedAt" style="opacity: 0.5">(%i18n:@deleted%)</span>
|
<span v-if="note.deletedAt" style="opacity: 0.5">(%i18n:@deleted%)</span>
|
||||||
<a class="reply" v-if="note.replyId">%fa:reply%</a>
|
<a class="reply" v-if="note.replyId">%fa:reply%</a>
|
||||||
<misskey-flavored-markdown v-if="note.text" :text="note.text" :i="$store.state.i"/>
|
<misskey-flavored-markdown v-if="note.text" :text="note.text" :i="$store.state.i" :customEmojis="note.emojis"/>
|
||||||
<a class="rp" v-if="note.renoteId">RN: ...</a>
|
<a class="rp" v-if="note.renoteId">RN: ...</a>
|
||||||
</div>
|
</div>
|
||||||
<details v-if="note.files.length > 0">
|
<details v-if="note.files.length > 0">
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
import db from '../db/mongodb';
|
||||||
|
|
||||||
|
const Emoji = db.get<IEmoji>('emoji');
|
||||||
|
|
||||||
|
Emoji.createIndex(['name', 'host'], { unique: true });
|
||||||
|
|
||||||
|
export default Emoji;
|
||||||
|
|
||||||
|
export type IEmoji = {
|
||||||
|
name: string;
|
||||||
|
host: string;
|
||||||
|
url: string;
|
||||||
|
aliases?: string[];
|
||||||
|
updatedAt?: Date;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const packEmojis = async (
|
||||||
|
host: string,
|
||||||
|
// MeiTODO: filter
|
||||||
|
) => {
|
||||||
|
return await Emoji.find({ host });
|
||||||
|
};
|
|
@ -12,6 +12,7 @@ import { packMany as packFileMany, IDriveFile } from './drive-file';
|
||||||
import Favorite from './favorite';
|
import Favorite from './favorite';
|
||||||
import Following from './following';
|
import Following from './following';
|
||||||
import config from '../config';
|
import config from '../config';
|
||||||
|
import { packEmojis } from './emoji';
|
||||||
|
|
||||||
const Note = db.get<INote>('notes');
|
const Note = db.get<INote>('notes');
|
||||||
Note.createIndex('uri', { sparse: true, unique: true });
|
Note.createIndex('uri', { sparse: true, unique: true });
|
||||||
|
@ -228,6 +229,11 @@ export const pack = async (
|
||||||
|
|
||||||
const id = _note._id;
|
const id = _note._id;
|
||||||
|
|
||||||
|
// _note._userを消す前か、_note.userを解決した後でないとホストがわからない
|
||||||
|
if (_note._user) {
|
||||||
|
_note.emojis = packEmojis(_note._user.host);
|
||||||
|
}
|
||||||
|
|
||||||
// Rename _id to id
|
// Rename _id to id
|
||||||
_note.id = _note._id;
|
_note.id = _note._id;
|
||||||
delete _note._id;
|
delete _note._id;
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
import parse from '../../../mfm/parse';
|
||||||
|
|
||||||
|
export default function(text: string) {
|
||||||
|
if (!text) return [];
|
||||||
|
return parse(text).filter(t => t.type === 'emoji').map(t => (t as any).emoji);
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
export type IIcon = {
|
||||||
|
type: string;
|
||||||
|
mediaType?: string;
|
||||||
|
url?: string;
|
||||||
|
};
|
|
@ -10,6 +10,9 @@ import { resolvePerson, updatePerson } from './person';
|
||||||
import { resolveImage } from './image';
|
import { resolveImage } from './image';
|
||||||
import { IRemoteUser, IUser } from '../../../models/user';
|
import { IRemoteUser, IUser } from '../../../models/user';
|
||||||
import htmlToMFM from '../../../mfm/html-to-mfm';
|
import htmlToMFM from '../../../mfm/html-to-mfm';
|
||||||
|
import Emoji from '../../../models/emoji';
|
||||||
|
import { ITag } from './tag';
|
||||||
|
import { toUnicode } from 'punycode';
|
||||||
|
|
||||||
const log = debug('misskey:activitypub');
|
const log = debug('misskey:activitypub');
|
||||||
|
|
||||||
|
@ -93,6 +96,10 @@ export async function createNote(value: any, resolver?: Resolver, silent = false
|
||||||
// テキストのパース
|
// テキストのパース
|
||||||
const text = note._misskey_content ? note._misskey_content : htmlToMFM(note.content);
|
const text = note._misskey_content ? note._misskey_content : htmlToMFM(note.content);
|
||||||
|
|
||||||
|
await extractEmojis(note.tag, actor.host).catch(e => {
|
||||||
|
console.log(`extractEmojis: ${e}`);
|
||||||
|
});
|
||||||
|
|
||||||
// ユーザーの情報が古かったらついでに更新しておく
|
// ユーザーの情報が古かったらついでに更新しておく
|
||||||
if (actor.updatedAt == null || Date.now() - actor.updatedAt.getTime() > 1000 * 60 * 60 * 24) {
|
if (actor.updatedAt == null || Date.now() - actor.updatedAt.getTime() > 1000 * 60 * 60 * 24) {
|
||||||
updatePerson(note.attributedTo);
|
updatePerson(note.attributedTo);
|
||||||
|
@ -135,3 +142,35 @@ export async function resolveNote(value: string | IObject, resolver?: Resolver):
|
||||||
// 添付されてきたNote Objectは偽装されている可能性があるため、常にuriを指定してサーバーフェッチを行う。
|
// 添付されてきたNote Objectは偽装されている可能性があるため、常にuriを指定してサーバーフェッチを行う。
|
||||||
return await createNote(uri, resolver);
|
return await createNote(uri, resolver);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function extractEmojis(tags: ITag[], host_: string) {
|
||||||
|
const host = toUnicode(host_.toLowerCase());
|
||||||
|
|
||||||
|
if (!tags) return [];
|
||||||
|
|
||||||
|
const eomjiTags = tags.filter(tag => tag.type === 'Emoji' && tag.icon && tag.icon.url);
|
||||||
|
|
||||||
|
return await Promise.all(
|
||||||
|
eomjiTags.map(async tag => {
|
||||||
|
const name = tag.name.replace(/^:/, '').replace(/:$/, '');
|
||||||
|
|
||||||
|
const exists = await Emoji.findOne({
|
||||||
|
host,
|
||||||
|
name
|
||||||
|
});
|
||||||
|
|
||||||
|
if (exists) {
|
||||||
|
return exists;
|
||||||
|
}
|
||||||
|
|
||||||
|
log(`register emoji host=${host}, name=${name}`);
|
||||||
|
|
||||||
|
return await Emoji.insert({
|
||||||
|
host,
|
||||||
|
name,
|
||||||
|
url: tag.icon.url,
|
||||||
|
aliases: [],
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { IIcon } from "./icon";
|
||||||
|
|
||||||
|
/***
|
||||||
|
* tag (ActivityPub)
|
||||||
|
*/
|
||||||
|
export type ITag = {
|
||||||
|
id: string;
|
||||||
|
type: string;
|
||||||
|
name?: string;
|
||||||
|
updated?: Date;
|
||||||
|
icon?: IIcon;
|
||||||
|
};
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { IEmoji } from '../../../models/emoji';
|
||||||
|
import config from '../../../config';
|
||||||
|
|
||||||
|
export default (emoji: IEmoji) => ({
|
||||||
|
id: `${config.url}/emojis/${emoji.name}`,
|
||||||
|
type: 'Emoji',
|
||||||
|
name: `:${emoji.name}:`,
|
||||||
|
updated: emoji.updatedAt != null ? emoji.updatedAt.toISOString() : new Date().toISOString,
|
||||||
|
icon: {
|
||||||
|
type: 'Image',
|
||||||
|
mediaType: 'image/png', //Mei-TODO
|
||||||
|
url: emoji.url
|
||||||
|
}
|
||||||
|
});
|
|
@ -1,12 +1,16 @@
|
||||||
import renderDocument from './document';
|
import renderDocument from './document';
|
||||||
import renderHashtag from './hashtag';
|
import renderHashtag from './hashtag';
|
||||||
import renderMention from './mention';
|
import renderMention from './mention';
|
||||||
|
import renderEmoji from './emoji';
|
||||||
import config from '../../../config';
|
import config from '../../../config';
|
||||||
import DriveFile, { IDriveFile } from '../../../models/drive-file';
|
import DriveFile, { IDriveFile } from '../../../models/drive-file';
|
||||||
import Note, { INote } from '../../../models/note';
|
import Note, { INote } from '../../../models/note';
|
||||||
import User from '../../../models/user';
|
import User from '../../../models/user';
|
||||||
import toHtml from '../misc/get-note-html';
|
import toHtml from '../misc/get-note-html';
|
||||||
import parseMfm from '../../../mfm/parse';
|
import parseMfm from '../../../mfm/parse';
|
||||||
|
import getEmojiNames from '../misc/get-emoji-names';
|
||||||
|
import Emoji, { IEmoji } from '../../../models/emoji';
|
||||||
|
import { unique } from '../../../prelude/array';
|
||||||
|
|
||||||
export default async function renderNote(note: INote, dive = true): Promise<any> {
|
export default async function renderNote(note: INote, dive = true): Promise<any> {
|
||||||
const promisedFiles: Promise<IDriveFile[]> = note.fileIds
|
const promisedFiles: Promise<IDriveFile[]> = note.fileIds
|
||||||
|
@ -75,10 +79,6 @@ export default async function renderNote(note: INote, dive = true): Promise<any>
|
||||||
|
|
||||||
const hashtagTags = (note.tags || []).map(tag => renderHashtag(tag));
|
const hashtagTags = (note.tags || []).map(tag => renderHashtag(tag));
|
||||||
const mentionTags = mentionedUsers.map(u => renderMention(u));
|
const mentionTags = mentionedUsers.map(u => renderMention(u));
|
||||||
const tag = [
|
|
||||||
...hashtagTags,
|
|
||||||
...mentionTags,
|
|
||||||
];
|
|
||||||
|
|
||||||
const files = await promisedFiles;
|
const files = await promisedFiles;
|
||||||
|
|
||||||
|
@ -108,12 +108,24 @@ export default async function renderNote(note: INote, dive = true): Promise<any>
|
||||||
}).join('');
|
}).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const content = toHtml(Object.assign({}, note, { text }));
|
||||||
|
|
||||||
|
const emojiNames = unique(getEmojiNames(content));
|
||||||
|
const emojis = await getEmojis(emojiNames);
|
||||||
|
const apemojis = emojis.map(emoji => renderEmoji(emoji));
|
||||||
|
|
||||||
|
const tag = [
|
||||||
|
...hashtagTags,
|
||||||
|
...mentionTags,
|
||||||
|
...apemojis,
|
||||||
|
];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: `${config.url}/notes/${note._id}`,
|
id: `${config.url}/notes/${note._id}`,
|
||||||
type: 'Note',
|
type: 'Note',
|
||||||
attributedTo,
|
attributedTo,
|
||||||
summary: note.cw,
|
summary: note.cw,
|
||||||
content: toHtml(Object.assign({}, note, { text })),
|
content,
|
||||||
_misskey_content: text,
|
_misskey_content: text,
|
||||||
published: note.createdAt.toISOString(),
|
published: note.createdAt.toISOString(),
|
||||||
to,
|
to,
|
||||||
|
@ -124,3 +136,18 @@ export default async function renderNote(note: INote, dive = true): Promise<any>
|
||||||
tag
|
tag
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getEmojis(names: string[]): Promise<IEmoji[]> {
|
||||||
|
if (names == null || names.length < 1) return [];
|
||||||
|
|
||||||
|
const emojis = await Promise.all(
|
||||||
|
names.map(async name => {
|
||||||
|
return await Emoji.findOne({
|
||||||
|
name,
|
||||||
|
host: null
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return emojis.filter(emoji => emoji != null);
|
||||||
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ import * as os from 'os';
|
||||||
import config from '../../../config';
|
import config from '../../../config';
|
||||||
import Meta from '../../../models/meta';
|
import Meta from '../../../models/meta';
|
||||||
import { ILocalUser } from '../../../models/user';
|
import { ILocalUser } from '../../../models/user';
|
||||||
|
import Emoji from '../../../models/emoji';
|
||||||
|
|
||||||
const pkg = require('../../../../package.json');
|
const pkg = require('../../../../package.json');
|
||||||
const client = require('../../../../built/client/meta.json');
|
const client = require('../../../../built/client/meta.json');
|
||||||
|
@ -22,6 +23,8 @@ export const meta = {
|
||||||
export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => {
|
export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => {
|
||||||
const meta: any = (await Meta.findOne()) || {};
|
const meta: any = (await Meta.findOne()) || {};
|
||||||
|
|
||||||
|
const emojis = await Emoji.find({ host: null });
|
||||||
|
|
||||||
res({
|
res({
|
||||||
maintainer: config.maintainer,
|
maintainer: config.maintainer,
|
||||||
|
|
||||||
|
@ -50,7 +53,7 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) =>
|
||||||
hidedTags: (me && me.isAdmin) ? meta.hidedTags : undefined,
|
hidedTags: (me && me.isAdmin) ? meta.hidedTags : undefined,
|
||||||
bannerUrl: meta.bannerUrl,
|
bannerUrl: meta.bannerUrl,
|
||||||
maxNoteTextLength: config.maxNoteTextLength,
|
maxNoteTextLength: config.maxNoteTextLength,
|
||||||
emojis: meta.emojis,
|
emojis: emojis,
|
||||||
|
|
||||||
features: {
|
features: {
|
||||||
registration: !meta.disableRegistration,
|
registration: !meta.disableRegistration,
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
import * as debug from 'debug';
|
||||||
|
import Emoji from "../models/emoji";
|
||||||
|
|
||||||
|
debug.enable('*');
|
||||||
|
|
||||||
|
async function main(name: string, url: string, alias?: string): Promise<any> {
|
||||||
|
const aliases = alias != null ? [ alias ] : [];
|
||||||
|
|
||||||
|
await Emoji.insert({
|
||||||
|
host: null,
|
||||||
|
name,
|
||||||
|
url,
|
||||||
|
aliases,
|
||||||
|
updatedAt: new Date()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
const name = args[0];
|
||||||
|
const url = args[1];
|
||||||
|
|
||||||
|
if (!name) throw 'require name';
|
||||||
|
if (!url) throw 'require url';
|
||||||
|
|
||||||
|
main(name, url).then(() => {
|
||||||
|
console.log('success');
|
||||||
|
process.exit(0);
|
||||||
|
}).catch(e => {
|
||||||
|
console.warn(e);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
Loading…
Reference in New Issue