mirror of
https://github.com/status-im/sourcecred.git
synced 2025-02-27 03:30:26 +00:00
Discord: add Mirror class (#1732)
Adds a class to persist a local mirror of data from a Discord Guild. Implements create and read functionality. Adds a function to `models` which converts an Emoji string-reference into an Emoji object. Test plan: Unit tests added. Paired with: @beanow
This commit is contained in:
parent
28115bba96
commit
898282becc
@ -101,6 +101,17 @@ export function emojiToRef({id, name}: Emoji): EmojiRef {
|
||||
return `${name}:${id}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an Emoji object based on a string reference in the form:
|
||||
* `${name}:${id}`
|
||||
*/
|
||||
export function refToEmoji(ref: EmojiRef): Emoji {
|
||||
// TODO: Test that ref is in correct form
|
||||
const [name, id] = ref.split(":");
|
||||
if (!id) return {id: null, name};
|
||||
return {id, name};
|
||||
}
|
||||
|
||||
// Determines whether the message was created by a webhook or a Discord User
|
||||
export function isAuthoredByNonUser(rawMessage: {
|
||||
+webhook_id?: Snowflake,
|
||||
|
@ -1,19 +1,23 @@
|
||||
// @flow
|
||||
|
||||
import {emojiToRef, isAuthoredByNonUser} from "./models";
|
||||
import {emojiToRef, refToEmoji, isAuthoredByNonUser} from "./models";
|
||||
|
||||
describe("plugins/discord/models", () => {
|
||||
describe("model helper functions", () => {
|
||||
describe("emojiToRef", () => {
|
||||
it("returns name if id is null", () => {
|
||||
expect(emojiToRef({id: null, name: "testEmojiName"})).toBe(
|
||||
"testEmojiName"
|
||||
);
|
||||
expect(emojiToRef({id: null, name: "🐙"})).toBe("🐙");
|
||||
});
|
||||
it("returns name and id if id is not null", () => {
|
||||
expect(emojiToRef({id: "testEmojiId", name: "testEmojiName"})).toBe(
|
||||
"testEmojiName:testEmojiId"
|
||||
);
|
||||
expect(emojiToRef({id: "id", name: "name"})).toBe("name:id");
|
||||
});
|
||||
});
|
||||
describe("refToEmoji", () => {
|
||||
it("returns correct object if id is null", () => {
|
||||
expect(refToEmoji("🐙")).toEqual({name: "🐙", id: null});
|
||||
});
|
||||
it("returns correct object if id is not null", () => {
|
||||
expect(refToEmoji("name:id")).toEqual({name: "name", id: "id"});
|
||||
});
|
||||
});
|
||||
describe("isAuthoredByNonUser", () => {
|
||||
|
500
src/plugins/discord/sqliteMirror.js
Normal file
500
src/plugins/discord/sqliteMirror.js
Normal file
@ -0,0 +1,500 @@
|
||||
// @flow
|
||||
|
||||
import type Database from "better-sqlite3";
|
||||
import stringify from "json-stable-stringify";
|
||||
import * as Model from "./models";
|
||||
import dedent from "../../util/dedent";
|
||||
import * as NullUtil from "../../util/null";
|
||||
|
||||
const VERSION = "DISCORD_MIRROR_v1";
|
||||
|
||||
/**
|
||||
* Persists a local copy of data from a Discord Guild.
|
||||
* Implements create and read functionality.
|
||||
*
|
||||
* Each Mirror instance is tied to a particular Guild. Trying to use a mirror
|
||||
* for multiple Discord Guilds is not permitted; use separate Mirrors.
|
||||
*
|
||||
* Note that Mirror persists separate Tables for Users and Guild Members.
|
||||
* Members are distinguished by membership in the Guild. Non-Member
|
||||
* Discord Users can participate in a Guild's activity by leaving comments,
|
||||
* reactions, etc. In our model, Members have a property, User, which
|
||||
* represents a full User object. Because of this, we save the User in the
|
||||
* AddMember method.
|
||||
*/
|
||||
export class SqliteMirror {
|
||||
+_db: Database;
|
||||
|
||||
/**
|
||||
* Construct a new SqliteMirror instance.
|
||||
*
|
||||
* Takes a Database and GuildId.
|
||||
*/
|
||||
constructor(db: Database, guildId: Model.Snowflake) {
|
||||
this._db = db;
|
||||
this._transaction(() => {
|
||||
this._initialize(guildId);
|
||||
});
|
||||
}
|
||||
|
||||
_transaction(queries: () => void) {
|
||||
const db = this._db;
|
||||
if (db.inTransaction) {
|
||||
throw new Error("already in transaction");
|
||||
}
|
||||
try {
|
||||
db.prepare("BEGIN").run();
|
||||
queries();
|
||||
if (db.inTransaction) {
|
||||
db.prepare("COMMIT").run();
|
||||
}
|
||||
} finally {
|
||||
if (db.inTransaction) {
|
||||
db.prepare("ROLLBACK").run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_initialize(guild: Model.Snowflake) {
|
||||
const db = this._db;
|
||||
// We store the config in a singleton table `meta`, whose unique row
|
||||
// has PRIMARY KEY `0`. Only the first ever insert will succeed; we
|
||||
// are locked into the first config.
|
||||
db.prepare(
|
||||
dedent`\
|
||||
CREATE TABLE IF NOT EXISTS meta (
|
||||
zero INTEGER PRIMARY KEY NOT NULL,
|
||||
config TEXT NOT NULL
|
||||
)
|
||||
`
|
||||
).run();
|
||||
|
||||
const config = stringify({
|
||||
version: VERSION,
|
||||
guild,
|
||||
});
|
||||
|
||||
const existingConfig: string | void = db
|
||||
.prepare("SELECT config FROM meta")
|
||||
.pluck()
|
||||
.get();
|
||||
if (existingConfig === config) {
|
||||
// Already set up; nothing to do.
|
||||
return;
|
||||
} else if (existingConfig !== undefined) {
|
||||
throw new Error(
|
||||
"Database already populated with incompatible server or version"
|
||||
);
|
||||
}
|
||||
db.prepare("INSERT INTO meta (zero, config) VALUES (0, ?)").run(config);
|
||||
|
||||
const tables = [
|
||||
dedent`\
|
||||
CREATE TABLE channels (
|
||||
id TEXT PRIMARY KEY NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
name TEXT NOT NULL
|
||||
)
|
||||
`,
|
||||
dedent`\
|
||||
CREATE TABLE users (
|
||||
id TEXT PRIMARY KEY NOT NULL,
|
||||
username TEXT NOT NULL,
|
||||
discriminator TEXT NOT NULL,
|
||||
bot INTEGER NOT NULL
|
||||
)
|
||||
`,
|
||||
dedent`\
|
||||
CREATE TABLE members (
|
||||
user_id TEXT PRIMARY KEY NOT NULL,
|
||||
nick TEXT,
|
||||
FOREIGN KEY(user_id) REFERENCES users(id)
|
||||
)
|
||||
`,
|
||||
dedent`\
|
||||
CREATE TABLE messages (
|
||||
id TEXT PRIMARY KEY NOT NULL,
|
||||
channel_id TEXT NOT NULL,
|
||||
author_id TEXT NOT NULL,
|
||||
non_user_author INTEGER NOT NULL,
|
||||
timestamp_ms INTEGER NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
FOREIGN KEY(channel_id) REFERENCES channels(id)
|
||||
)
|
||||
`,
|
||||
dedent`
|
||||
CREATE INDEX messages__chanel_id
|
||||
ON messages (channel_id)
|
||||
`,
|
||||
dedent`\
|
||||
CREATE TABLE message_reactions (
|
||||
channel_id TEXT NOT NULL,
|
||||
message_id TEXT NOT NULL,
|
||||
author_id TEXT NOT NULL,
|
||||
emoji TEXT NOT NULL,
|
||||
FOREIGN KEY(channel_id) REFERENCES channels(id),
|
||||
FOREIGN KEY(message_id) REFERENCES messages(id),
|
||||
FOREIGN KEY(author_id) REFERENCES users(id),
|
||||
CONSTRAINT value_object PRIMARY KEY (channel_id, message_id, author_id, emoji)
|
||||
)
|
||||
`,
|
||||
dedent`\
|
||||
CREATE INDEX message_reactions__channel_id__message_id
|
||||
ON message_reactions (channel_id, message_id)
|
||||
`,
|
||||
dedent`\
|
||||
CREATE TABLE message_mentions (
|
||||
channel_id TEXT NOT NULL,
|
||||
message_id TEXT NOT NULL,
|
||||
user_id TEXT NOT NULL,
|
||||
FOREIGN KEY(channel_id) REFERENCES channels(id),
|
||||
FOREIGN KEY(message_id) REFERENCES messages(id),
|
||||
FOREIGN KEY(user_id) REFERENCES users(id),
|
||||
CONSTRAINT value_object PRIMARY KEY (channel_id, message_id, user_id)
|
||||
)
|
||||
`,
|
||||
dedent`\
|
||||
CREATE INDEX message_mentions__channel_id__message_id
|
||||
ON message_mentions (channel_id, message_id)
|
||||
`,
|
||||
];
|
||||
for (const sql of tables) {
|
||||
db.prepare(sql).run();
|
||||
}
|
||||
}
|
||||
|
||||
users(): $ReadOnlyArray<Model.User> {
|
||||
return this._db
|
||||
.prepare(
|
||||
dedent`\
|
||||
SELECT
|
||||
id,
|
||||
username,
|
||||
discriminator,
|
||||
bot
|
||||
FROM users`
|
||||
)
|
||||
.all()
|
||||
.map((x) => ({
|
||||
id: x.id,
|
||||
username: x.username,
|
||||
discriminator: x.discriminator,
|
||||
bot: x.bot === 1,
|
||||
}));
|
||||
}
|
||||
|
||||
user(id: Model.Snowflake): ?Model.User {
|
||||
const user = this._db
|
||||
.prepare(
|
||||
dedent`\
|
||||
SELECT
|
||||
id,
|
||||
username,
|
||||
discriminator,
|
||||
bot
|
||||
FROM users
|
||||
WHERE id = :id
|
||||
`
|
||||
)
|
||||
.get({id: id});
|
||||
|
||||
if (!user) {
|
||||
return null;
|
||||
} else {
|
||||
return {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
discriminator: user.discriminator,
|
||||
bot: user.bot === 1,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
members(): $ReadOnlyArray<Model.GuildMember> {
|
||||
return this._db
|
||||
.prepare(
|
||||
dedent`\
|
||||
SELECT
|
||||
user_id,
|
||||
nick
|
||||
FROM members`
|
||||
)
|
||||
.all()
|
||||
.map((x) => ({
|
||||
user: NullUtil.get(
|
||||
this.user(x.user_id),
|
||||
`No user_id found for ${x.user_id}`
|
||||
),
|
||||
nick: x.nick,
|
||||
}));
|
||||
}
|
||||
|
||||
member(userId: Model.Snowflake): ?Model.GuildMember {
|
||||
const member = this._db
|
||||
.prepare(
|
||||
dedent`\
|
||||
SELECT
|
||||
user_id,
|
||||
nick
|
||||
FROM members
|
||||
WHERE user_id = :user_id
|
||||
`
|
||||
)
|
||||
.get({user_id: userId});
|
||||
|
||||
if (!member) {
|
||||
return null;
|
||||
} else {
|
||||
return {
|
||||
user: NullUtil.get(this.user(userId), `No user_id found for ${userId}`),
|
||||
nick: member.nick,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
channels(): $ReadOnlyArray<Model.Channel> {
|
||||
return this._db
|
||||
.prepare(
|
||||
dedent`\
|
||||
SELECT
|
||||
id,
|
||||
type,
|
||||
name
|
||||
FROM channels`
|
||||
)
|
||||
.all();
|
||||
}
|
||||
|
||||
messages(channel: Model.Snowflake): $ReadOnlyArray<Model.Message> {
|
||||
return this._db
|
||||
.prepare(
|
||||
dedent`\
|
||||
SELECT
|
||||
id,
|
||||
channel_id,
|
||||
author_id,
|
||||
non_user_author,
|
||||
timestamp_ms,
|
||||
content
|
||||
FROM messages
|
||||
WHERE channel_id = :channel_id`
|
||||
)
|
||||
.all({channel_id: channel})
|
||||
.map((m) => ({
|
||||
id: m.id,
|
||||
channelId: m.channel_id,
|
||||
authorId: m.author_id,
|
||||
nonUserAuthor: m.non_user_author === 1,
|
||||
timestampMs: m.timestamp_ms,
|
||||
content: m.content,
|
||||
reactionEmoji: this.reactionEmoji(m.channel_id, m.id),
|
||||
mentions: this.mentions(m.channel_id, m.id),
|
||||
}));
|
||||
}
|
||||
|
||||
reactions(
|
||||
channel: Model.Snowflake,
|
||||
message: Model.Snowflake
|
||||
): $ReadOnlyArray<Model.Reaction> {
|
||||
return this._db
|
||||
.prepare(
|
||||
dedent`\
|
||||
SELECT
|
||||
channel_id,
|
||||
message_id,
|
||||
author_id,
|
||||
emoji
|
||||
FROM message_reactions
|
||||
WHERE channel_id = :channel_id
|
||||
AND message_id = :message_id`
|
||||
)
|
||||
.all({channel_id: channel, message_id: message})
|
||||
.map((r) => ({
|
||||
channelId: r.channel_id,
|
||||
messageId: r.message_id,
|
||||
authorId: r.author_id,
|
||||
emoji: Model.refToEmoji(r.emoji),
|
||||
}));
|
||||
}
|
||||
|
||||
mentions(
|
||||
channel: Model.Snowflake,
|
||||
message: Model.Snowflake
|
||||
): $ReadOnlyArray<Model.Snowflake> {
|
||||
return this._db
|
||||
.prepare(
|
||||
dedent`\
|
||||
SELECT user_id
|
||||
FROM message_mentions
|
||||
WHERE channel_id = :channel_id
|
||||
AND message_id = :message_id`
|
||||
)
|
||||
.all({channel_id: channel, message_id: message})
|
||||
.map((res) => res.user_id);
|
||||
}
|
||||
|
||||
addUser(user: Model.User) {
|
||||
this._db
|
||||
.prepare(
|
||||
dedent`\
|
||||
REPLACE INTO users (
|
||||
id,
|
||||
username,
|
||||
discriminator,
|
||||
bot
|
||||
) VALUES (
|
||||
:id,
|
||||
:username,
|
||||
:discriminator,
|
||||
:bot
|
||||
)
|
||||
`
|
||||
)
|
||||
.run({
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
discriminator: user.discriminator,
|
||||
bot: Number(user.bot),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Because a User is represented in a Member object, we save the User in
|
||||
* `addMember`.
|
||||
*/
|
||||
addMember(member: Model.GuildMember) {
|
||||
this.addUser(member.user);
|
||||
this._db
|
||||
.prepare(
|
||||
dedent`\
|
||||
REPLACE INTO members (
|
||||
user_id,
|
||||
nick
|
||||
) VALUES (
|
||||
:user_id,
|
||||
:nick
|
||||
)
|
||||
`
|
||||
)
|
||||
.run({
|
||||
user_id: member.user.id,
|
||||
nick: member.nick,
|
||||
});
|
||||
}
|
||||
|
||||
addChannel(channel: Model.Channel) {
|
||||
this._db
|
||||
.prepare(
|
||||
dedent`\
|
||||
REPLACE INTO channels (
|
||||
id,
|
||||
type,
|
||||
name
|
||||
) VALUES (
|
||||
:id,
|
||||
:type,
|
||||
:name
|
||||
)
|
||||
`
|
||||
)
|
||||
.run({
|
||||
id: channel.id,
|
||||
type: channel.type,
|
||||
name: channel.name,
|
||||
});
|
||||
}
|
||||
|
||||
addMessage(message: Model.Message) {
|
||||
this._db
|
||||
.prepare(
|
||||
dedent`\
|
||||
REPLACE INTO messages (
|
||||
id,
|
||||
channel_id,
|
||||
author_id,
|
||||
non_user_author,
|
||||
timestamp_ms,
|
||||
content
|
||||
) VALUES (
|
||||
:id,
|
||||
:channel_id,
|
||||
:author_id,
|
||||
:non_user_author,
|
||||
:timestamp_ms,
|
||||
:content
|
||||
)
|
||||
`
|
||||
)
|
||||
.run({
|
||||
id: message.id,
|
||||
channel_id: message.channelId,
|
||||
author_id: message.authorId,
|
||||
non_user_author: Number(message.nonUserAuthor),
|
||||
timestamp_ms: message.timestampMs,
|
||||
content: message.content,
|
||||
});
|
||||
}
|
||||
|
||||
addReaction(reaction: Model.Reaction) {
|
||||
this._db
|
||||
.prepare(
|
||||
dedent`\
|
||||
REPLACE INTO message_reactions (
|
||||
channel_id,
|
||||
message_id,
|
||||
author_id,
|
||||
emoji
|
||||
) VALUES (
|
||||
:channel_id,
|
||||
:message_id,
|
||||
:author_id,
|
||||
:emoji
|
||||
)`
|
||||
)
|
||||
.run({
|
||||
channel_id: reaction.channelId,
|
||||
message_id: reaction.messageId,
|
||||
author_id: reaction.authorId,
|
||||
emoji: Model.emojiToRef(reaction.emoji),
|
||||
});
|
||||
}
|
||||
|
||||
addMention(message: Model.Message, user: Model.Snowflake) {
|
||||
this._db
|
||||
.prepare(
|
||||
dedent`\
|
||||
REPLACE INTO message_mentions (
|
||||
channel_id,
|
||||
message_id,
|
||||
user_id
|
||||
) VALUES (
|
||||
:channel_id,
|
||||
:message_id,
|
||||
:user_id
|
||||
)
|
||||
`
|
||||
)
|
||||
.run({
|
||||
channel_id: message.channelId,
|
||||
message_id: message.id,
|
||||
user_id: user,
|
||||
});
|
||||
}
|
||||
|
||||
reactionEmoji(
|
||||
channel: Model.Snowflake,
|
||||
message: Model.Snowflake
|
||||
): $ReadOnlyArray<Model.Emoji> {
|
||||
return this._db
|
||||
.prepare(
|
||||
dedent`\
|
||||
SELECT DISTINCT
|
||||
emoji
|
||||
FROM message_reactions
|
||||
WHERE channel_id = :channel_id
|
||||
AND message_id = :message_id`
|
||||
)
|
||||
.all({channel_id: channel, message_id: message})
|
||||
.map((e) => Model.refToEmoji(e.emoji));
|
||||
}
|
||||
}
|
462
src/plugins/discord/sqliteMirror.test.js
Normal file
462
src/plugins/discord/sqliteMirror.test.js
Normal file
@ -0,0 +1,462 @@
|
||||
// @flow
|
||||
|
||||
import Database from "better-sqlite3";
|
||||
import {SqliteMirror} from "./sqliteMirror";
|
||||
import dedent from "../../util/dedent";
|
||||
import {
|
||||
type Channel,
|
||||
type Message,
|
||||
type GuildMember,
|
||||
type Snowflake,
|
||||
type Emoji,
|
||||
type User,
|
||||
} from "./models";
|
||||
|
||||
describe("plugins/discord/sqliteMirror", () => {
|
||||
const customEmoji = (): Emoji => ({id: "id", name: "name"});
|
||||
const genericEmoji = (): Emoji => ({id: null, name: "🐙"});
|
||||
|
||||
const testChannel = (id: Snowflake): Channel => ({
|
||||
id: id,
|
||||
name: "testChannelName",
|
||||
type: "GUILD_TEXT",
|
||||
});
|
||||
|
||||
const testMessage = (
|
||||
id: Snowflake,
|
||||
channelId: Snowflake,
|
||||
authorId: Snowflake
|
||||
): Message => ({
|
||||
id: id,
|
||||
channelId: channelId,
|
||||
authorId: authorId,
|
||||
timestampMs: Date.parse("2020-03-03T23:35:10.615000+00:00"),
|
||||
content: "Just going to drop this here",
|
||||
reactionEmoji: [customEmoji()],
|
||||
nonUserAuthor: false,
|
||||
mentions: ["1", "23"],
|
||||
});
|
||||
|
||||
const testUser = (id: Snowflake): User => ({
|
||||
id: id,
|
||||
username: "username",
|
||||
discriminator: "disc",
|
||||
bot: true,
|
||||
});
|
||||
|
||||
const testMember = (userId: Snowflake): GuildMember => ({
|
||||
user: testUser(userId),
|
||||
nick: "nickname",
|
||||
});
|
||||
|
||||
describe("constructor", () => {
|
||||
it("initializes a new database succsessfully", () => {
|
||||
const db = new Database(":memory:");
|
||||
expect(() => new SqliteMirror(db, "0")).not.toThrow();
|
||||
});
|
||||
|
||||
it("rejects a different config", () => {
|
||||
const db = new Database(":memory:");
|
||||
const _ = new SqliteMirror(db, "0");
|
||||
expect(() => new SqliteMirror(db, "1")).toThrow(
|
||||
"Database already populated with incompatible server or version"
|
||||
);
|
||||
});
|
||||
|
||||
it("creates the right set of tables", () => {
|
||||
const db = new Database(":memory:");
|
||||
new SqliteMirror(db, "0");
|
||||
const tables = db
|
||||
.prepare("SELECT name FROM sqlite_master WHERE type = 'table'")
|
||||
.pluck()
|
||||
.all();
|
||||
expect(tables.sort()).toEqual(
|
||||
[
|
||||
"meta",
|
||||
"channels",
|
||||
"users",
|
||||
"members",
|
||||
"messages",
|
||||
"message_reactions",
|
||||
"message_mentions",
|
||||
].sort()
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("users", () => {
|
||||
it("inserts users", async () => {
|
||||
const user = testUser("1");
|
||||
const db = new Database(":memory:");
|
||||
const sqliteMirror = new SqliteMirror(db, "0");
|
||||
sqliteMirror.addUser(user);
|
||||
const result = db
|
||||
.prepare(
|
||||
dedent`\
|
||||
SELECT
|
||||
id,
|
||||
username,
|
||||
discriminator,
|
||||
bot
|
||||
FROM users
|
||||
`
|
||||
)
|
||||
.get();
|
||||
expect(result).toEqual({
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
discriminator: user.discriminator,
|
||||
bot: 1,
|
||||
});
|
||||
});
|
||||
|
||||
describe("user", () => {
|
||||
it("retrieves user", () => {
|
||||
const user: User = testUser("1");
|
||||
const db = new Database(":memory:");
|
||||
const sqliteMirror = new SqliteMirror(db, "0");
|
||||
sqliteMirror.addUser(user);
|
||||
const result = sqliteMirror.user(user.id);
|
||||
expect(result).toEqual(user);
|
||||
});
|
||||
|
||||
it("returns null if user not in table", () => {
|
||||
const db = new Database(":memory:");
|
||||
const sqliteMirror = new SqliteMirror(db, "0");
|
||||
const result = sqliteMirror.user("1");
|
||||
expect(result).toEqual(null);
|
||||
});
|
||||
});
|
||||
|
||||
it("retrieves users", async () => {
|
||||
const user1: User = testUser("1");
|
||||
const user2: User = testUser("2");
|
||||
const db = new Database(":memory:");
|
||||
const sqliteMirror = new SqliteMirror(db, "0");
|
||||
sqliteMirror.addUser(user1);
|
||||
sqliteMirror.addUser(user2);
|
||||
const result = sqliteMirror.users();
|
||||
expect(result).toEqual([user1, user2]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("members", () => {
|
||||
it("inserts members", async () => {
|
||||
const member = testMember("1");
|
||||
const db = new Database(":memory:");
|
||||
const sqliteMirror = new SqliteMirror(db, "0");
|
||||
sqliteMirror.addMember(member);
|
||||
const result = db
|
||||
.prepare(
|
||||
dedent`\
|
||||
SELECT
|
||||
user_id,
|
||||
nick
|
||||
FROM members`
|
||||
)
|
||||
.get();
|
||||
expect(result).toEqual({
|
||||
user_id: member.user.id,
|
||||
nick: member.nick,
|
||||
});
|
||||
});
|
||||
|
||||
it("inserts user", () => {
|
||||
const member = testMember("1");
|
||||
const db = new Database(":memory:");
|
||||
const sqliteMirror = new SqliteMirror(db, "0");
|
||||
sqliteMirror.addMember(member);
|
||||
expect(sqliteMirror.user(member.user.id)).toEqual(member.user);
|
||||
});
|
||||
|
||||
it("retrieves members", async () => {
|
||||
const mem1: GuildMember = testMember("1");
|
||||
const mem2: GuildMember = testMember("2");
|
||||
const db = new Database(":memory:");
|
||||
const sqliteMirror = new SqliteMirror(db, "0");
|
||||
sqliteMirror.addMember(mem1);
|
||||
sqliteMirror.addMember(mem2);
|
||||
const result = sqliteMirror.members();
|
||||
expect(result).toEqual([mem1, mem2]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("member", () => {
|
||||
it("retrieves member", () => {
|
||||
const member: GuildMember = testMember("1");
|
||||
const db = new Database(":memory:");
|
||||
const sqliteMirror = new SqliteMirror(db, "0");
|
||||
sqliteMirror.addMember(member);
|
||||
const result = sqliteMirror.member(member.user.id);
|
||||
expect(result).toEqual(member);
|
||||
});
|
||||
|
||||
it("returns null if user not in table", () => {
|
||||
const db = new Database(":memory:");
|
||||
const sqliteMirror = new SqliteMirror(db, "0");
|
||||
const result = sqliteMirror.member("1");
|
||||
expect(result).toEqual(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe("channels", () => {
|
||||
it("inserts channels", async () => {
|
||||
const channel = testChannel("1");
|
||||
const db = new Database(":memory:");
|
||||
const sqliteMirror = new SqliteMirror(db, "0");
|
||||
sqliteMirror.addChannel(channel);
|
||||
const result = await db
|
||||
.prepare("SELECT id, type, name FROM channels")
|
||||
.get();
|
||||
expect(result).toEqual(channel);
|
||||
});
|
||||
|
||||
it("retrieves channels", () => {
|
||||
const ch1: Channel = testChannel("1");
|
||||
const ch2: Channel = testChannel("2");
|
||||
const db = new Database(":memory:");
|
||||
const sqliteMirror = new SqliteMirror(db, "0");
|
||||
sqliteMirror.addChannel(ch1);
|
||||
sqliteMirror.addChannel(ch2);
|
||||
const result = sqliteMirror.channels();
|
||||
expect(result).toEqual([ch1, ch2]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("messages", () => {
|
||||
it("inserts messages", async () => {
|
||||
const messageId = "1";
|
||||
const channelId = "2";
|
||||
const authorId = "3";
|
||||
const db = new Database(":memory:");
|
||||
const sqliteMirror = new SqliteMirror(db, "0");
|
||||
sqliteMirror.addUser(testUser(authorId));
|
||||
sqliteMirror.addChannel(testChannel(channelId));
|
||||
sqliteMirror.addMessage(testMessage(messageId, channelId, authorId));
|
||||
const result = await db
|
||||
.prepare(
|
||||
dedent`\
|
||||
SELECT
|
||||
id,
|
||||
channel_id,
|
||||
author_id,
|
||||
non_user_author,
|
||||
timestamp_ms,
|
||||
content
|
||||
FROM messages`
|
||||
)
|
||||
.get();
|
||||
expect(result).toEqual({
|
||||
id: "1",
|
||||
channel_id: "2",
|
||||
author_id: "3",
|
||||
non_user_author: 0,
|
||||
timestamp_ms: 1583278510615,
|
||||
content: "Just going to drop this here",
|
||||
});
|
||||
});
|
||||
|
||||
it("retrieves messages", () => {
|
||||
const messageId1 = "1";
|
||||
const messageId2 = "2";
|
||||
const channelId = "3";
|
||||
const authorId = "4";
|
||||
const mes1: Message = testMessage(messageId1, channelId, authorId);
|
||||
const mes2: Message = testMessage(messageId2, channelId, authorId);
|
||||
|
||||
const db = new Database(":memory:");
|
||||
const sqliteMirror = new SqliteMirror(db, "0");
|
||||
sqliteMirror.addUser(testUser(authorId));
|
||||
sqliteMirror.addChannel(testChannel(channelId));
|
||||
sqliteMirror.addMessage(mes1);
|
||||
sqliteMirror.addMessage(mes2);
|
||||
|
||||
for (const user of mes1.mentions) {
|
||||
sqliteMirror.addUser(testUser(user));
|
||||
sqliteMirror.addMention(mes1, user);
|
||||
}
|
||||
|
||||
for (const user of mes2.mentions) {
|
||||
sqliteMirror.addUser(testUser(user));
|
||||
sqliteMirror.addMention(mes2, user);
|
||||
}
|
||||
|
||||
for (const emoji of mes1.reactionEmoji) {
|
||||
sqliteMirror.addReaction({
|
||||
emoji,
|
||||
channelId: mes1.channelId,
|
||||
messageId: mes1.id,
|
||||
authorId: mes1.authorId,
|
||||
});
|
||||
}
|
||||
|
||||
for (const emoji of mes2.reactionEmoji) {
|
||||
sqliteMirror.addReaction({
|
||||
emoji,
|
||||
channelId: mes2.channelId,
|
||||
messageId: mes2.id,
|
||||
authorId: mes2.authorId,
|
||||
});
|
||||
}
|
||||
|
||||
const result = sqliteMirror.messages(channelId);
|
||||
expect(result).toEqual([mes1, mes2]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("reactions", () => {
|
||||
it("inserts reactions", async () => {
|
||||
const channelId: Snowflake = "2";
|
||||
const messageId: Snowflake = "3";
|
||||
const authorId: Snowflake = "4";
|
||||
const reaction = {
|
||||
emoji: customEmoji(),
|
||||
channelId: channelId,
|
||||
messageId: messageId,
|
||||
authorId: authorId,
|
||||
};
|
||||
const db = new Database(":memory:");
|
||||
const sqliteMirror = new SqliteMirror(db, "0");
|
||||
sqliteMirror.addUser(testUser(authorId));
|
||||
sqliteMirror.addChannel(testChannel(channelId));
|
||||
sqliteMirror.addMessage(testMessage(messageId, channelId, authorId));
|
||||
sqliteMirror.addReaction(reaction);
|
||||
const result = await db
|
||||
.prepare(
|
||||
dedent`\
|
||||
SELECT
|
||||
channel_id,
|
||||
author_id,
|
||||
message_id,
|
||||
emoji
|
||||
FROM message_reactions`
|
||||
)
|
||||
.get();
|
||||
expect(result).toEqual({
|
||||
emoji: "name:id",
|
||||
channel_id: channelId,
|
||||
message_id: messageId,
|
||||
author_id: authorId,
|
||||
});
|
||||
});
|
||||
it("retrieves reactions", () => {
|
||||
const channelId: Snowflake = "1";
|
||||
const messageId: Snowflake = "2";
|
||||
const authorId1 = "3";
|
||||
const authorId2 = "4";
|
||||
|
||||
const reaction1 = {
|
||||
emoji: customEmoji(),
|
||||
channelId: channelId,
|
||||
messageId: messageId,
|
||||
authorId: authorId1,
|
||||
};
|
||||
|
||||
const reaction2 = {
|
||||
emoji: genericEmoji(),
|
||||
channelId: channelId,
|
||||
messageId: messageId,
|
||||
authorId: authorId2,
|
||||
};
|
||||
|
||||
const db = new Database(":memory:");
|
||||
const sqliteMirror = new SqliteMirror(db, "0");
|
||||
sqliteMirror.addUser(testUser(authorId1));
|
||||
sqliteMirror.addUser(testUser(authorId2));
|
||||
sqliteMirror.addChannel(testChannel(channelId));
|
||||
sqliteMirror.addMessage(testMessage(messageId, channelId, authorId1));
|
||||
sqliteMirror.addReaction(reaction1);
|
||||
sqliteMirror.addReaction(reaction2);
|
||||
const result = sqliteMirror.reactions(channelId, messageId);
|
||||
expect(result).toEqual([reaction1, reaction2]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("mentions", () => {
|
||||
it("inserts mentions", async () => {
|
||||
const messageId: Snowflake = "1";
|
||||
const channelId: Snowflake = "2";
|
||||
const authorId: Snowflake = "3";
|
||||
const message: Message = testMessage(messageId, channelId, authorId);
|
||||
const userId: Snowflake = "45";
|
||||
const db = new Database(":memory:");
|
||||
const sqliteMirror = new SqliteMirror(db, "0");
|
||||
sqliteMirror.addUser(testUser(userId));
|
||||
sqliteMirror.addUser(testUser(authorId));
|
||||
sqliteMirror.addChannel(testChannel(channelId));
|
||||
sqliteMirror.addMessage(testMessage(messageId, channelId, authorId));
|
||||
sqliteMirror.addMention(message, userId);
|
||||
const result = await db
|
||||
.prepare(
|
||||
`\
|
||||
SELECT
|
||||
channel_id,
|
||||
message_id,
|
||||
user_id
|
||||
FROM message_mentions`
|
||||
)
|
||||
.get();
|
||||
expect(result).toEqual({
|
||||
channel_id: channelId,
|
||||
message_id: messageId,
|
||||
user_id: userId,
|
||||
});
|
||||
});
|
||||
|
||||
it("retrieves mentions", () => {
|
||||
const messageId: Snowflake = "1";
|
||||
const channelId = "2";
|
||||
const authorId = "3";
|
||||
const message: Message = testMessage(messageId, channelId, authorId);
|
||||
const [userId1, userId2] = message.mentions;
|
||||
const db = new Database(":memory:");
|
||||
const sqliteMirror = new SqliteMirror(db, "0");
|
||||
sqliteMirror.addUser(testUser(userId1));
|
||||
sqliteMirror.addUser(testUser(userId2));
|
||||
sqliteMirror.addUser(testUser(authorId));
|
||||
sqliteMirror.addChannel(testChannel(channelId));
|
||||
sqliteMirror.addMessage(testMessage(messageId, channelId, authorId));
|
||||
sqliteMirror.addMention(message, userId1);
|
||||
sqliteMirror.addMention(message, userId2);
|
||||
const result = sqliteMirror.mentions(message.channelId, messageId);
|
||||
expect(result).toEqual([userId1, userId2]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("reactionEmoji", () => {
|
||||
it("retrieves emojis", () => {
|
||||
const channelId: Snowflake = "1";
|
||||
const messageId: Snowflake = "2";
|
||||
const authorId1: Snowflake = "3";
|
||||
const authorId2: Snowflake = "4";
|
||||
const messageAuthorId: Snowflake = "5";
|
||||
|
||||
const reaction1 = {
|
||||
emoji: customEmoji(),
|
||||
channelId: channelId,
|
||||
messageId: messageId,
|
||||
authorId: authorId1,
|
||||
};
|
||||
|
||||
const reaction2 = {
|
||||
emoji: genericEmoji(),
|
||||
channelId: channelId,
|
||||
messageId: messageId,
|
||||
authorId: authorId2,
|
||||
};
|
||||
|
||||
const db = new Database(":memory:");
|
||||
const sqliteMirror = new SqliteMirror(db, "0");
|
||||
sqliteMirror.addUser(testUser(authorId1));
|
||||
sqliteMirror.addUser(testUser(authorId2));
|
||||
sqliteMirror.addChannel(testChannel(channelId));
|
||||
sqliteMirror.addMessage(
|
||||
testMessage(messageId, channelId, messageAuthorId)
|
||||
);
|
||||
sqliteMirror.addReaction(reaction1);
|
||||
sqliteMirror.addReaction(reaction2);
|
||||
const result = sqliteMirror.reactionEmoji(channelId, messageId);
|
||||
expect(result).toEqual([customEmoji(), genericEmoji()]);
|
||||
});
|
||||
});
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user