summaryrefslogtreecommitdiff
path: root/src/misc
diff options
context:
space:
mode:
authorMeiMei <30769358+mei23@users.noreply.github.com>2021-03-22 00:44:38 +0900
committerGitHub <noreply@github.com>2021-03-22 00:44:38 +0900
commitd1efe1d2085dbae14f85ab6a993e755926067446 (patch)
tree9f98ade6eb5d9c7a67d1fa429b1adf815b943835 /src/misc
parentfix bug (diff)
downloadsharkey-d1efe1d2085dbae14f85ab6a993e755926067446.tar.gz
sharkey-d1efe1d2085dbae14f85ab6a993e755926067446.tar.bz2
sharkey-d1efe1d2085dbae14f85ab6a993e755926067446.zip
populateEmojisのリファクタと絵文字情報のキャッシュ (#7378)
* revert * Refactor populateEmojis, Cache emojis * ん * fix typo * コメント
Diffstat (limited to 'src/misc')
-rw-r--r--src/misc/cache.ts23
-rw-r--r--src/misc/populate-emojis.ts58
2 files changed, 78 insertions, 3 deletions
diff --git a/src/misc/cache.ts b/src/misc/cache.ts
index 5b7017a3b9..71fbbd8a4c 100644
--- a/src/misc/cache.ts
+++ b/src/misc/cache.ts
@@ -14,13 +14,30 @@ export class Cache<T> {
});
}
- public get(key: string | null): T | null {
+ public get(key: string | null): T | undefined {
const cached = this.cache.get(key);
- if (cached == null) return null;
+ if (cached == null) return undefined;
if ((Date.now() - cached.date) > this.lifetime) {
this.cache.delete(key);
- return null;
+ return undefined;
}
return cached.value;
}
+
+ public delete(key: string | null) {
+ this.cache.delete(key);
+ }
+
+ public async fetch(key: string | null, fetcher: () => Promise<T>): Promise<T> {
+ const cachedValue = this.get(key);
+ if (cachedValue !== undefined) {
+ // Cache HIT
+ return cachedValue;
+ }
+
+ // Cache MISS
+ const value = await fetcher();
+ this.set(key, value);
+ return value;
+ }
}
diff --git a/src/misc/populate-emojis.ts b/src/misc/populate-emojis.ts
new file mode 100644
index 0000000000..6300cfb95e
--- /dev/null
+++ b/src/misc/populate-emojis.ts
@@ -0,0 +1,58 @@
+import { Emojis } from '../models';
+import { Emoji } from '../models/entities/emoji';
+import { Cache } from './cache';
+import { isSelfHost, toPunyNullable } from './convert-host';
+
+const cache = new Cache<Emoji | null>(1000 * 60 * 60);
+
+/**
+ * 添付用絵文字情報
+ */
+type PopulatedEmoji = {
+ name: string;
+ url: string;
+};
+
+/**
+ * 添付用絵文字情報を解決する
+ * @param emojiName ノートやユーザープロフィールに添付された、またはリアクションのカスタム絵文字名 (:は含めない, リアクションでローカルホストの場合は@.を付ける (これはdecodeReactionで可能))
+ * @param noteUserHost ノートやユーザープロフィールの所有者
+ * @returns 絵文字情報, nullは未マッチを意味する
+ */
+export async function populateEmoji(emojiName: string, noteUserHost: string | null): Promise<PopulatedEmoji | null> {
+ const match = emojiName.match(/^(\w+)(?:@([\w.-]+))?$/);
+ if (!match) return null;
+
+ const name = match[1];
+
+ // クエリに使うホスト
+ let host = match[2] === '.' ? null // .はローカルホスト (ここがマッチするのはリアクションのみ)
+ : match[2] === undefined ? noteUserHost // ノートなどでホスト省略表記の場合はローカルホスト (ここがリアクションにマッチすることはない)
+ : isSelfHost(match[2]) ? null // 自ホスト指定
+ : (match[2] || noteUserHost); // 指定されたホスト || ノートなどの所有者のホスト (こっちがリアクションにマッチすることはない)
+
+ host = toPunyNullable(host);
+
+ const queryOrNull = async () => (await Emojis.findOne({
+ name,
+ host
+ })) || null;
+
+ const emoji = await cache.fetch(`${name} ${host}`, queryOrNull);
+
+ if (emoji == null) return null;
+
+ return {
+ name: emojiName,
+ url: emoji.url,
+ };
+}
+
+/**
+ * 複数の添付用絵文字情報を解決する (キャシュ付き, 存在しないものは結果から除外される)
+ */
+export async function populateEmojis(emojiNames: string[], noteUserHost: string | null): Promise<PopulatedEmoji[]> {
+ const emojis = await Promise.all(emojiNames.map(x => populateEmoji(x, noteUserHost)));
+ return emojis.filter((x): x is PopulatedEmoji => x != null);
+}
+