From 7fada5bf138d498649a920012f87acb1449953c9 Mon Sep 17 00:00:00 2001 From: Shigma Date: Wed, 12 Jun 2024 03:33:07 +0800 Subject: [PATCH] feat(database): implement bot mixin with minato relations --- packages/database/src/bot.ts | 114 +++++++++++++++ packages/database/src/index.ts | 245 ++++++++++++--------------------- packages/database/src/types.ts | 11 ++ 3 files changed, 214 insertions(+), 156 deletions(-) create mode 100644 packages/database/src/bot.ts diff --git a/packages/database/src/bot.ts b/packages/database/src/bot.ts new file mode 100644 index 0000000..b597c43 --- /dev/null +++ b/packages/database/src/bot.ts @@ -0,0 +1,114 @@ +import { Bot, pick, Universal } from '@satorijs/core' +import { Channel, Guild, Login } from './types' +import { SyncChannel } from './channel' +import SatoriDatabase from '.' + +interface CachedBot extends Bot { + sync: Login['sync'] +} + +class CachedBot { + constructor(private sd: SatoriDatabase) {} + + private get _query() { + return { + platform: this.platform, + 'user.id': this.user.id, + } + } + + async getGuildList() { + const self: this = this[Symbol.for('cordis.original')] + if (!this.sync) return self.getGuildList() + + if (this.sync.guildListAt >= this.sync.onlineAt) { + const data = await this.sd.ctx.database.get('satori.guild', { + syncs: { + $some: { + login: this._query, + }, + }, + }) + return { data } + } + const data: Partial[] = [] + for await (const guild of self.getGuildIter()) { + data.push({ platform: this.platform, ...pick(guild, ['id', 'name', 'avatar']) }) + } + await this.sd.ctx.database.set('satori.login', this._query, { + sync: { + guildListAt: Date.now(), + }, + guildSyncs: data.map((guild) => ({ guild })) as any, + }) + return { data } + } + + async getChannelList(guildId: string) { + const self: this = this[Symbol.for('cordis.original')] + if (!this.sync) return self.getChannelList(guildId) + + // FIXME sync maybe undefined + const [sync] = await this.sd.ctx.database.get('satori.guild.sync', { + login: { + platform: this.platform, + 'user.id': this.user.id, + }, + guild: { + id: guildId, + }, + }) + if (sync!.channelListAt >= this.sync.onlineAt) { + const data = await this.sd.ctx.database.get('satori.channel', { + guild: { id: guildId }, + syncs: { + $some: { + login: this._query, + }, + }, + }) + return { data } + } + const data: Partial[] = [] + for await (const channel of self.getChannelIter(guildId)) { + data.push({ platform: this.platform, ...pick(channel, ['id', 'name', 'type']) }) + } + await this.sd.ctx.database.set('satori.login', this._query, { + guildSyncs: { + $create: { + guild: { id: guildId }, + channelListAt: Date.now(), + }, + }, + channelSyncs: data.map((channel) => ({ channel })) as any, + }) + return { data } + } + + async getMessageList(channelId: string, id: string, dir?: Universal.Direction, limit?: number, order?: Universal.Order) { + if (!this.sync) { + const self: this = this[Symbol.for('cordis.original')] + return self.getMessageList(channelId, id, dir, limit, order) + } + const key = this.platform + '/' + channelId + this.sd._channels[key] ||= new SyncChannel(this.sd.ctx, this, channelId) + return await this.sd._channels[key].getMessageList(id, dir, limit, order) + } + + async updateSync() { + if (this.hidden) return + if (this.status !== Universal.Status.ONLINE) return + for (const channel of Object.values(this.sd._channels)) { + if (channel.bot.sid !== this.sid) continue + channel.hasLatest = false + } + const [login] = await this.ctx.database.get('satori.login', this._query) + this.sync = login?.sync || { onlineAt: Date.now() } + await this.ctx.database.upsert('satori.login', [{ + ...this._query, + sync: this.sync, + }]) + } +} + +export default CachedBot diff --git a/packages/database/src/index.ts b/packages/database/src/index.ts index 82cb0a1..5367bd4 100644 --- a/packages/database/src/index.ts +++ b/packages/database/src/index.ts @@ -1,8 +1,8 @@ import {} from 'minato' -import { Bot, Context, Dict, Schema, Service, Universal } from '@satorijs/core' +import { Context, Dict, Schema, Service } from '@satorijs/core' import { SyncChannel } from './channel' import { SyncGuild } from './guild' -import { Login } from './types' +import CachedBot from './bot' export * from './types' @@ -18,11 +18,11 @@ declare module '@satorijs/core' { } interface Bot { - sync: Login['sync'] + updateSync(): Promise } } -class SatoriDatabase extends Service { +class SatoriDatabase extends Service { static inject = ['model', 'database'] _guilds: Dict = {} @@ -30,123 +30,9 @@ class SatoriDatabase extends Service { stopped = false - constructor(ctx: Context, public config: SatoriDatabase.Config) { + constructor(public ctx: Context, public config: SatoriDatabase.Config) { super(ctx, 'satori.database', true) - const self = this - - ctx.accessor('bot.getGuildList', { - get: () => async function (this: Bot) { - if (this.sync.guildListAt >= this.sync.onlineAt) { - const data = await ctx.database.get('satori.guild', { - syncs: { - $some: { - login: { - platform: this.platform, - 'user.id': this.user.id, - }, - }, - }, - }) - return { data } - } - const data: Universal.Guild[] = [] - for await (const guild of this.self.getGuildIter()) { - data.push(guild) - } - await ctx.database.set('satori.login', { - platform: this.platform, - 'user.id': this.user.id, - }, { - sync: { - guildListAt: this.timestamp, - }, - guildSyncs: { - // ? - }, - }) - return { data } - }, - }) - - ctx.accessor('bot.getChannelList', { - get: () => async function (this: Bot, guildId: string) { - // FIXME sync maybe undefined - const [sync] = await ctx.database.get('satori.guild.sync', { - login: { - platform: this.platform, - 'user.id': this.user.id, - }, - guild: { - id: guildId, - }, - }) - if (sync!.channelListAt >= this.sync.onlineAt) { - const data = await ctx.database.get('satori.channel', { - guild: { - id: guildId, - }, - syncs: { - $some: { - login: { - platform: this.platform, - 'user.id': this.user.id, - }, - }, - }, - }) - return { data } - } - const data: Universal.Channel[] = [] - for await (const channel of this.self.getChannelIter(guildId)) { - data.push(channel) - } - await ctx.database.set('satori.guild.sync', { - login: { - platform: this.platform, - 'user.id': this.user.id, - }, - }, { - channelListAt: this.timestamp, - // ? - }) - return { data } - }, - }) - - ctx.accessor('bot.getMessageList', { - get: () => async function (this: Bot, channelId: string, id: string, dir?: Universal.Direction, limit?: number, order?: Universal.Order) { - const key = this.platform + '/' + channelId - self._channels[key] ||= new SyncChannel(ctx, this, channelId) - return await self._channels[key].getMessageList(id, dir, limit, order) - }, - }) - - ctx.model.extend('satori.message', { - 'uid': 'unsigned(8)', - 'sid': 'bigint', // int64 - 'id': 'char(255)', - 'platform': 'char(255)', - 'user.id': 'char(255)', - 'channel.id': 'char(255)', - 'guild.id': 'char(255)', - 'quote.id': 'char(255)', - 'content': 'text', - 'createdAt': 'unsigned(8)', - 'updatedAt': 'unsigned(8)', - 'flag': 'unsigned(1)', - 'deleted': 'boolean', - 'edited': 'boolean', - 'syncAt': 'unsigned(8)', - }, { - primary: 'uid', - autoInc: true, - unique: [ - ['id', 'channel.id', 'platform'], - ['sid', 'channel.id', 'platform'], - ], - }) - ctx.model.extend('satori.user', { 'id': 'char(255)', 'platform': 'char(255)', @@ -171,7 +57,6 @@ class SatoriDatabase extends Service { 'id': 'char(255)', 'platform': 'char(255)', 'name': 'char(255)', - 'syncAt': 'unsigned(8)', }, { primary: ['id', 'platform'], }) @@ -179,11 +64,27 @@ class SatoriDatabase extends Service { ctx.model.extend('satori.login', { 'platform': 'char(255)', 'user.id': 'char(255)', + 'sync.onlineAt': 'unsigned(8)', 'sync.guildListAt': 'unsigned(8)', }, { primary: ['platform', 'user.id'], }) + ctx.model.extend('satori.member', { + 'guild': { + type: 'manyToOne', + table: 'satori.guild', + target: 'members', + }, + 'user': { + type: 'manyToOne', + table: 'satori.user', + }, + 'name': 'char(255)', + }, { + primary: ['guild', 'user'], + }) + ctx.model.extend('satori.guild.sync', { 'guild': { type: 'manyToOne', @@ -193,10 +94,12 @@ class SatoriDatabase extends Service { 'login': { type: 'manyToOne', table: 'satori.login', - target: 'syncs', + target: 'guildSyncs', }, 'channelListAt': 'unsigned(8)', 'memberListAt': 'unsigned(8)', + }, { + primary: ['guild', 'login'], }) ctx.model.extend('satori.channel.sync', { @@ -208,25 +111,73 @@ class SatoriDatabase extends Service { 'login': { type: 'manyToOne', table: 'satori.login', - target: 'syncs', + target: 'channelSyncs', }, + }, { + primary: ['channel', 'login'], + }) + + ctx.model.extend('satori.message', { + 'uid': 'unsigned(8)', + 'sid': 'bigint', // int64 + 'id': 'char(255)', + 'platform': 'char(255)', + 'user.id': 'char(255)', + 'channel.id': 'char(255)', + 'guild.id': 'char(255)', + 'quote.id': 'char(255)', + 'content': 'text', + 'createdAt': 'unsigned(8)', + 'updatedAt': 'unsigned(8)', + 'flag': 'unsigned(1)', + 'deleted': 'boolean', + 'edited': 'boolean', + 'syncAt': 'unsigned(8)', + // 'guild': { + // type: 'manyToOne', + // table: 'satori.guild', + // }, + // 'channel': { + // type: 'manyToOne', + // table: 'satori.channel', + // }, + // 'member': { + // type: 'manyToOne', + // table: 'satori.member', + // }, + // 'user': { + // type: 'manyToOne', + // table: 'satori.user', + // }, + }, { + primary: 'uid', + autoInc: true, + unique: [ + ['id', 'channel.id', 'platform'], + ['sid', 'channel.id', 'platform'], + ], + }) + + ctx.mixin(new CachedBot(this), { + getGuildList: 'bot.getGuildList', + getChannelList: 'bot.getChannelList', + getMessageList: 'bot.getMessageList', + updateSync: 'bot.updateSync', }) - } - async start() { - this.ctx.on('message', (session) => { + ctx.on('message', (session) => { const { platform, channelId } = session if (session.bot.hidden) return const key = platform + '/' + channelId - this._channels[key] ||= new SyncChannel(this.ctx, session.bot, session.channelId) + this._channels[key] ||= new SyncChannel(ctx, session.bot, session.channelId) if (this._channels[key].bot === session.bot) { this._channels[key].queue(session) } }) - this.ctx.on('message-deleted', async (session) => { + ctx.on('message-deleted', async (session) => { // TODO update local message - await this.ctx.database.set('satori.message', { + await ctx.database.set('satori.message', { id: session.messageId, platform: session.platform, }, { @@ -235,9 +186,9 @@ class SatoriDatabase extends Service { }) }) - this.ctx.on('message-updated', async (session) => { + ctx.on('message-updated', async (session) => { // TODO update local message - await this.ctx.database.set('satori.message', { + await ctx.database.set('satori.message', { id: session.messageId, platform: session.platform, }, { @@ -246,40 +197,22 @@ class SatoriDatabase extends Service { }) }) - this.ctx.on('bot-status-updated', async (bot) => { - this.updateBot(bot) + ctx.on('login-added', async ({ bot }) => { + await bot.updateSync() + }) + + ctx.on('login-updated', async ({ bot }) => { + await bot.updateSync() }) - this.ctx.bots.forEach(async (bot) => { - this.updateBot(bot) + ctx.bots.forEach(async (bot) => { + await bot.updateSync() }) } async stop() { this.stopped = true } - - private async updateBot(bot: Bot) { - if (bot.hidden) return - if (!await bot.supports('message.list') || !await bot.supports('guild.list')) return - if (bot.status !== Universal.Status.ONLINE) { - for (const channel of Object.values(this._channels)) { - if (channel.bot !== bot) continue - channel.hasLatest = false - } - const query = { - platform: bot.platform, - 'user.id': bot.user.id, - } - const [login] = await this.ctx.database.get('satori.login', query) - bot.sync = login?.sync || { onlineAt: Date.now() } - await this.ctx.database.upsert('satori.login', [{ - ...query, - sync: bot.sync, - }]) - return - } - } } namespace SatoriDatabase { diff --git a/packages/database/src/types.ts b/packages/database/src/types.ts index c627aee..4662d30 100644 --- a/packages/database/src/types.ts +++ b/packages/database/src/types.ts @@ -6,6 +6,7 @@ declare module 'minato' { 'satori.login': Login 'satori.message': Message 'satori.user': User + 'satori.member': GuildMember 'satori.guild': Guild 'satori.guild.sync': GuildSync 'satori.channel': Channel @@ -39,6 +40,12 @@ export interface User extends Universal.User { syncAt?: number } +export interface GuildMember extends Universal.GuildMember { + guild: Guild + user: User + syncAt?: number +} + export interface Guild extends Universal.Guild { platform: string syncs: GuildSync[] @@ -71,6 +78,10 @@ export interface Message extends Universal.Message { deleted: boolean edited: boolean syncAt?: number + user: User + member: GuildMember + channel: Channel + guild: Guild } export namespace Message {