From 38df79335e52708b54fbf19cfb61f1d5561f6d8d Mon Sep 17 00:00:00 2001 From: Sorunome <mail@sorunome.de> Date: Mon, 5 Mar 2018 16:21:02 +0100 Subject: [PATCH] added external and animated emoji support --- src/bot.ts | 23 +++++++++++----------- src/db/dbdataemoji.ts | 24 +++++++++++------------ src/db/schema/v7.ts | 37 +++++++++++++++++++++++++++++++++++ src/messageprocessor.ts | 15 ++++++++------ src/store.ts | 2 +- test/test_messageprocessor.ts | 2 +- test/test_store.ts | 24 +++++++++++------------ 7 files changed, 83 insertions(+), 44 deletions(-) create mode 100644 src/db/schema/v7.ts diff --git a/src/bot.ts b/src/bot.ts index 8ec1ad8..68185bc 100644 --- a/src/bot.ts +++ b/src/bot.ts @@ -1,7 +1,7 @@ import { DiscordBridgeConfig } from "./config"; import { DiscordClientFactory } from "./clientfactory"; import { DiscordStore } from "./store"; -import { DbGuildEmoji } from "./db/dbdataemoji"; +import { DbEmoji } from "./db/dbdataemoji"; import { DbEvent } from "./db/dbdataevent"; import { MatrixUser, RemoteUser, Bridge, Entry } from "matrix-appservice-bridge"; import { Util } from "./util"; @@ -338,19 +338,18 @@ export class DiscordBot { }); } - public async GetGuildEmoji(guild: Discord.Guild, id: string): Promise<string> { - const dbEmoji: DbGuildEmoji = await this.store.Get(DbGuildEmoji, {emoji_id: id}); + public async GetEmoji(name: string, animated: boolean, id: string): Promise<string> { + if (!id.match(/^\d+$/)) { + throw new Error("Non-numerical ID"); + } + const dbEmoji: DbEmoji = await this.store.Get(DbEmoji, {emoji_id: id}); if (!dbEmoji.Result) { - // Fetch the emoji - if (!guild.emojis.has(id)) { - throw new Error("The guild does not contain the emoji"); - } - const emoji: Discord.Emoji = guild.emojis.get(id); + const url = "https://cdn.discordapp.com/emojis/" + id + (animated ? ".gif" : ".png"); const intent = this.bridge.getIntent(); - const mxcUrl = (await Util.UploadContentFromUrl(emoji.url, intent, emoji.name)).mxcUrl; - dbEmoji.EmojiId = emoji.id; - dbEmoji.GuildId = guild.id; - dbEmoji.Name = emoji.name; + const mxcUrl = (await Util.UploadContentFromUrl(url, intent, name)).mxcUrl; + dbEmoji.EmojiId = id; + dbEmoji.Name = name; + dbEmoji.Animated = animated; dbEmoji.MxcUrl = mxcUrl; await this.store.Insert(dbEmoji); } diff --git a/src/db/dbdataemoji.ts b/src/db/dbdataemoji.ts index 46d8b79..d0534e7 100644 --- a/src/db/dbdataemoji.ts +++ b/src/db/dbdataemoji.ts @@ -2,10 +2,10 @@ import { DiscordStore } from "../store"; import { IDbData } from "./dbdatainterface"; import * as log from "npmlog"; -export class DbGuildEmoji implements IDbData { +export class DbEmoji implements IDbData { public EmojiId: string; - public GuildId: string; public Name: string; + public Animated: boolean; public MxcUrl: string; public CreatedAt: number; public UpdatedAt: number; @@ -14,15 +14,15 @@ export class DbGuildEmoji implements IDbData { public RunQuery(store: DiscordStore, params: any): Promise<null> { return store.db.getAsync(` SELECT * - FROM guild_emoji + FROM emoji WHERE emoji_id = $id`, { $id: params.emoji_id, }).then((row) => { this.Result = row !== undefined; if (this.Result) { this.EmojiId = row.emoji_id; - this.GuildId = row.guild_id; this.Name = row.name; + this.Animated = row.animated; this.MxcUrl = row.mxc_url; this.CreatedAt = row.created_at; this.UpdatedAt = row.updated_at; @@ -34,12 +34,12 @@ export class DbGuildEmoji implements IDbData { this.CreatedAt = new Date().getTime(); this.UpdatedAt = this.CreatedAt; return store.db.runAsync(` - INSERT INTO guild_emoji - (emoji_id,guild_id,name,mxc_url,created_at,updated_at) - VALUES ($emoji_id,$guild_id,$name,$mxc_url,$created_at,$updated_at);`, { + INSERT INTO emoji + (emoji_id,name,animated,mxc_url,created_at,updated_at) + VALUES ($emoji_id,$name,$animated,$mxc_url,$created_at,$updated_at);`, { $emoji_id: this.EmojiId, - $guild_id: this.GuildId, $name: this.Name, + $animated: this.Animated, $mxc_url: this.MxcUrl, $created_at: this.CreatedAt, $updated_at: this.UpdatedAt, @@ -50,16 +50,16 @@ export class DbGuildEmoji implements IDbData { // Ensure this has incremented by 1 for Insert+Update operations. this.UpdatedAt = new Date().getTime() + 1; return store.db.runAsync(` - UPDATE guild_emoji + UPDATE emoji SET name = $name, + animated = $animated, mxc_url = $mxc_url, updated_at = $updated_at WHERE - emoji_id = $emoji_id - AND guild_id = $guild_id`, { + emoji_id = $emoji_id`, { $emoji_id: this.EmojiId, - $guild_id: this.GuildId, $name: this.Name, + $animated: this.Animated, $mxc_url: this.MxcUrl, $updated_at: this.UpdatedAt, }); diff --git a/src/db/schema/v7.ts b/src/db/schema/v7.ts new file mode 100644 index 0000000..5bb8b70 --- /dev/null +++ b/src/db/schema/v7.ts @@ -0,0 +1,37 @@ +import {IDbSchema} from "./dbschema"; +import {DiscordStore} from "../../store"; +import {DiscordClientFactory} from "../../clientfactory"; +import * as log from "npmlog"; +import * as Bluebird from "bluebird"; + +export class Schema implements IDbSchema { + public description = "create guild emoji table"; + public run(store: DiscordStore): Promise<null> { + return store.create_table(` + CREATE TABLE emoji ( + emoji_id TEXT NOT NULL, + name TEXT NOT NULL, + animated INTEGER NOT NULL, + mxc_url TEXT NOT NULL, + created_at INTEGER NOT NULL, + updated_at INTEGER NOT NULL, + PRIMARY KEY(emoji_id) + );`, "emoji").then(() => { + // migrate existing emoji + return store.db.execAsync(` + INSERT INTO emoji + (emoji_id, name, animated, mxc_url, created_at, updated_at) + SELECT emoji_id, name, 0 AS animated, mxc_url, created_at, updated_at FROM guild_emoji; + `).error(() => { + // ignore errors + log.warning("DiscordSchema", "Failed to migrate old data to new table"); + }); + }); + } + + public rollBack(store: DiscordStore): Promise <null> { + return store.db.execAsync( + `DROP TABLE IF EXISTS emoji;`, + ); + } +} diff --git a/src/messageprocessor.ts b/src/messageprocessor.ts index 5ba895a..a65ab97 100644 --- a/src/messageprocessor.ts +++ b/src/messageprocessor.ts @@ -10,12 +10,13 @@ const USER_REGEX_POSTMARK = /<@!?([0-9]*)>/g; const CHANNEL_REGEX = /<#?([0-9]*)>/g; const CHANNEL_REGEX_POSTMARK = /<#?([0-9]*)>/g; const EMOJI_SIZE = "1em"; -const EMOJI_REGEX = /<:(\w+):([0-9]*)>/g; -const EMOJI_REGEX_POSTMARK = /<:(\w+):([0-9]*)>/g; +const EMOJI_REGEX = /<(a?):(\w+):([0-9]*)>/g; +const EMOJI_REGEX_POSTMARK = /<(a?):(\w+):([0-9]*)>/g; const MATRIX_TO_LINK = "https://matrix.to/#/"; -const NAME_EMOJI_REGEX_GROUP = 1; -const ID_EMOJI_REGEX_GROUP = 2; +const ANIMATED_EMOJI_REGEX_GROUP = 1; +const NAME_EMOJI_REGEX_GROUP = 2; +const ID_EMOJI_REGEX_GROUP = 3; marked.setOptions({ sanitize: true, @@ -143,11 +144,12 @@ export class MessageProcessor { public async ReplaceEmoji(content: string, msg: Discord.Message): Promise<string> { let results = EMOJI_REGEX.exec(content); while (results !== null) { + const animated = results[ANIMATED_EMOJI_REGEX_GROUP] === "a"; const name = results[NAME_EMOJI_REGEX_GROUP]; const id = results[ID_EMOJI_REGEX_GROUP]; try { // we still fetch the mxcUrl to check if the emoji is valid - const mxcUrl = await this.bot.GetGuildEmoji(msg.guild, id); + const mxcUrl = await this.bot.GetEmoji(name, animated, id); content = content.replace(results[0], `:${name}:`); } catch (ex) { log.warn("MessageProcessor", @@ -162,10 +164,11 @@ export class MessageProcessor { public async ReplaceEmojiPostmark(content: string, msg: Discord.Message): Promise<string> { let results = EMOJI_REGEX_POSTMARK.exec(content); while (results !== null) { + const animated = results[ANIMATED_EMOJI_REGEX_GROUP] === "a"; const name = escapeHtml(results[NAME_EMOJI_REGEX_GROUP]); const id = results[ID_EMOJI_REGEX_GROUP]; try { - const mxcUrl = await this.bot.GetGuildEmoji(msg.guild, id); + const mxcUrl = await this.bot.GetEmoji(name, animated, id); content = content.replace(results[0], `<img alt="${name}" src="${mxcUrl}" style="height: ${EMOJI_SIZE};"/>`); } catch (ex) { diff --git a/src/store.ts b/src/store.ts index 37ffee1..0218e29 100644 --- a/src/store.ts +++ b/src/store.ts @@ -4,7 +4,7 @@ import * as Bluebird from "bluebird"; import * as fs from "fs"; import { IDbSchema } from "./db/schema/dbschema"; import { IDbData} from "./db/dbdatainterface"; -const CURRENT_SCHEMA = 6; +const CURRENT_SCHEMA = 7; /** * Stores data for specific users and data not specific to rooms. */ diff --git a/test/test_messageprocessor.ts b/test/test_messageprocessor.ts index 2dc07b8..3383a8b 100644 --- a/test/test_messageprocessor.ts +++ b/test/test_messageprocessor.ts @@ -16,7 +16,7 @@ log.level = "silly"; // const assert = Chai.assert; const bot = { - GetGuildEmoji: (guild: Discord.Guild, id: string): Promise<string> => { + GetEmoji: (name: string, animated: boolean, id: string): Promise<string> => { if (id === "3333333") { return Promise.resolve("mxc://image"); } else { diff --git a/test/test_store.ts b/test/test_store.ts index 4d6c2bd..5ba015a 100644 --- a/test/test_store.ts +++ b/test/test_store.ts @@ -3,7 +3,7 @@ import * as ChaiAsPromised from "chai-as-promised"; // import * as Proxyquire from "proxyquire"; import { DiscordStore } from "../src/store"; import * as log from "npmlog"; -import { DbGuildEmoji } from "../src/db/dbdataemoji"; +import { DbEmoji } from "../src/db/dbdataemoji"; import { DbEvent } from "../src/db/dbdataevent"; Chai.use(ChaiAsPromised); @@ -29,13 +29,13 @@ describe("DiscordStore", () => { })).to.eventually.be.fulfilled; }); }); - describe("Get|Insert|Update<DbGuildEmoji>", () => { + describe("Get|Insert|Update<DbEmoji>", () => { it("should insert successfully", () => { const store = new DiscordStore(":memory:"); return expect(store.init().then(() => { - const emoji = new DbGuildEmoji(); + const emoji = new DbEmoji(); emoji.EmojiId = "123"; - emoji.GuildId = "456"; + emoji.Animated = false; emoji.Name = "TestEmoji"; emoji.MxcUrl = "TestUrl"; return store.Insert(emoji); @@ -44,37 +44,37 @@ describe("DiscordStore", () => { it("should get successfully", async () => { const store = new DiscordStore(":memory:"); await store.init(); - const insertEmoji = new DbGuildEmoji(); + const insertEmoji = new DbEmoji(); insertEmoji.EmojiId = "123"; - insertEmoji.GuildId = "456"; + insertEmoji.Animated = false; insertEmoji.Name = "TestEmoji"; insertEmoji.MxcUrl = "TestUrl"; await store.Insert(insertEmoji); - const getEmoji = await store.Get(DbGuildEmoji, {emoji_id: "123"}); + const getEmoji = await store.Get(DbEmoji, {emoji_id: "123"}); Chai.assert.equal(getEmoji.Name, "TestEmoji"); Chai.assert.equal(getEmoji.MxcUrl, "TestUrl"); }); it("should not return nonexistant emoji", async () => { const store = new DiscordStore(":memory:"); await store.init(); - const getEmoji = await store.Get(DbGuildEmoji, {emoji_id: "123"}); + const getEmoji = await store.Get(DbEmoji, {emoji_id: "123"}); Chai.assert.isFalse(getEmoji.Result); }); it("should update successfully", async () => { const store = new DiscordStore(":memory:"); await store.init(); - const insertEmoji = new DbGuildEmoji(); + const insertEmoji = new DbEmoji(); insertEmoji.EmojiId = "123"; - insertEmoji.GuildId = "456"; + insertEmoji.Animated = false; insertEmoji.Name = "TestEmoji"; insertEmoji.MxcUrl = "TestUrl"; await store.Insert(insertEmoji); insertEmoji.EmojiId = "123"; - insertEmoji.GuildId = "456"; + insertEmoji.Animated = false; insertEmoji.Name = "TestEmoji2"; insertEmoji.MxcUrl = "NewURL"; await store.Update(insertEmoji); - const getEmoji = await store.Get(DbGuildEmoji, {emoji_id: "123"}); + const getEmoji = await store.Get(DbEmoji, {emoji_id: "123"}); Chai.assert.equal(getEmoji.Name, "TestEmoji2"); Chai.assert.equal(getEmoji.MxcUrl, "NewURL"); Chai.assert.notEqual(getEmoji.CreatedAt, getEmoji.UpdatedAt); -- GitLab