diff --git a/packages/bolt-guilded/mod.ts b/packages/bolt-guilded/mod.ts index 257bb95..00d79e8 100644 --- a/packages/bolt-guilded/mod.ts +++ b/packages/bolt-guilded/mod.ts @@ -11,7 +11,7 @@ import { tocore } from './messages.ts'; export class guilded_plugin extends plugin<{ token: string }> { bot: Client; name = 'bolt-guilded'; - version = '0.6.1'; + version = '0.7.0'; constructor(l: lightning, config: { token: string }) { super(l, config); diff --git a/packages/bolt-revolt/mod.ts b/packages/bolt-revolt/mod.ts index 0d266f0..4e1ecae 100644 --- a/packages/bolt-revolt/mod.ts +++ b/packages/bolt-revolt/mod.ts @@ -10,7 +10,7 @@ import { tocore, torevolt } from './messages.ts'; export class revolt_plugin extends plugin<{ token: string }> { bot: Client; name = 'bolt-revolt'; - version = '0.6.1'; + version = '0.7.0'; constructor(l: lightning, config: { token: string }) { super(l, config); diff --git a/packages/lightning/README.md b/packages/lightning/README.md index ea01d23..1122ae3 100644 --- a/packages/lightning/README.md +++ b/packages/lightning/README.md @@ -8,10 +8,10 @@ apps via plugins. ## example config ```ts -import { define_config } from 'jsr:@jersey/lightning@0.7.0'; +import type { config } from 'jsr:@jersey/lightning@0.7.0'; import { discord_plugin } from 'https://williamhorning.dev/bolt/x/bolt-discord/0.7.0/mod.ts'; -export default define_config({ +export default { redis_host: 'localhost', redis_port: 6379, plugins: [ @@ -19,5 +19,5 @@ export default define_config({ // ... }) ] -}); +} as config; ``` diff --git a/packages/lightning/deno.jsonc b/packages/lightning/deno.jsonc index de5be08..a74af5e 100644 --- a/packages/lightning/deno.jsonc +++ b/packages/lightning/deno.jsonc @@ -1,12 +1,7 @@ { "name": "@jersey/lightning", "version": "0.7.0", - "exports": { - ".": "./mod.ts", - "./plugins": "./src/plugins.ts", - "./types": "./src/types.ts", - "./utils": "./src/utils.ts" - }, + "exports": "./mod.ts", "publish": { "exclude": ["./src/tests/*"] }, diff --git a/packages/lightning/mod.ts b/packages/lightning/mod.ts index 4514a18..1573e34 100644 --- a/packages/lightning/mod.ts +++ b/packages/lightning/mod.ts @@ -4,7 +4,6 @@ * @module */ -export { bridges } from './src/bridges/mod.ts'; export { lightning } from './src/lightning.ts'; export { plugin } from './src/plugins.ts'; export * from './src/types.ts'; diff --git a/packages/lightning/src/bridges/command_functions.ts b/packages/lightning/src/bridges/cmd_internals.ts similarity index 56% rename from packages/lightning/src/bridges/command_functions.ts rename to packages/lightning/src/bridges/cmd_internals.ts index 66e0578..748f828 100644 --- a/packages/lightning/src/bridges/command_functions.ts +++ b/packages/lightning/src/bridges/cmd_internals.ts @@ -1,11 +1,16 @@ -import type { lightning } from '../lightning.ts'; import type { command_arguments } from '../types.ts'; +import { + del_key, + exists, + get_bridge, + get_channel_bridge, + set_bridge +} from './functions.ts'; export async function join( - opts: command_arguments, - l: lightning + opts: command_arguments ): Promise<[boolean, string]> { - if (await l.bridges.is_in_bridge(opts.channel)) { + if (await exists(opts.lightning, `lightning-bchannel-${opts.channel}`)) { return [ false, "To do this, you can't be in a bridge. Try leaving your bridge first." @@ -21,11 +26,9 @@ export async function join( ]; } - const plugin = l.plugins.get(opts.plugin); + const plugin = opts.lightning.plugins.get(opts.plugin); - const bridge = (await l.bridges.get_bridge({ - id - })) || { + const bridge = (await get_bridge(opts.lightning, id)) || { allow_editing: false, channels: [], id, @@ -38,56 +41,47 @@ export async function join( data: await plugin!.create_bridge(opts.channel) }); - await l.bridges.set_bridge(bridge); - - await l.redis.sendCommand([ - 'SET', - `lightning-bchannel-${opts.channel}`, - bridge.id - ]); + await set_bridge(opts.lightning, bridge); return [true, 'Joined a bridge!']; } export async function leave( - opts: command_arguments, - l: lightning + opts: command_arguments ): Promise<[boolean, string]> { - const bridge = await l.bridges.get_bridge({ - channel: opts.channel - }); + const bridge = await get_channel_bridge(opts.lightning, opts.channel); if (!bridge) { return [true, "You're not in a bridge, so try joining a bridge first."]; } - await l.bridges.set_bridge({ + await set_bridge(opts.lightning, { ...bridge, channels: bridge.channels.filter( i => i.id !== opts.channel && i.plugin !== opts.plugin ) }); - await l.redis.sendCommand(['DEL', `lightning-bchannel-${opts.channel}`]); + await del_key(opts.lightning, `lightning-bchannel-${opts.channel}`); return [true, 'Left a bridge!']; } -export async function reset(opts: command_arguments, l: lightning) { +export async function reset(opts: command_arguments) { if (!opts.opts.name) opts.opts.name = - (await l.bridges.get_bridge({ channel: opts.channel }))?.id || + (await get_channel_bridge(opts.lightning, opts.channel))?.id || opts.channel; - let [ok, text] = await leave(opts, l); + let [ok, text] = await leave(opts); if (!ok) return text; - [ok, text] = await join(opts, l); + [ok, text] = await join(opts); if (!ok) return text; return 'Reset this bridge!'; } -export async function toggle(opts: command_arguments, l: lightning) { - const bridge = await l.bridges.get_bridge({ channel: opts.channel }); +export async function toggle(opts: command_arguments) { + const bridge = await get_channel_bridge(opts.lightning, opts.channel); if (!bridge) { return "You're not in a bridge right now. Try joining one first."; @@ -105,26 +99,21 @@ export async function toggle(opts: command_arguments, l: lightning) { bridge[setting] = !bridge[setting]; - await l.bridges.set_bridge(bridge); + await set_bridge(opts.lightning, bridge); return 'Toggled that setting!'; } -export async function status(args: command_arguments, l: lightning) { - const current = await l.bridges.get_bridge({ channel: args.channel }); +export async function status(args: command_arguments) { + const current = await get_channel_bridge(args.lightning, args.channel); if (!current) { return "You're not in any bridges right now."; } - const editing_text = current.allow_editing - ? 'with editing enabled' - : 'with editing disabled'; - const rawname_text = current.use_rawname - ? 'and nicknames disabled' - : 'and nicknames enabled'; - return `This channel is connected to \`${current.id}\`, a bridge with ${ current.channels.length - 1 - } other channels connected to it, ${editing_text} ${rawname_text}`; + } other channels connected to it, with editing ${ + current.allow_editing ? 'enabled' : 'disabled' + } and nicknames ${current.use_rawname ? 'disabled' : 'enabled'}`; } diff --git a/packages/lightning/src/bridges/commands.ts b/packages/lightning/src/bridges/commands.ts deleted file mode 100644 index c481307..0000000 --- a/packages/lightning/src/bridges/commands.ts +++ /dev/null @@ -1,43 +0,0 @@ -import type { lightning } from '../lightning.ts'; -import type { command } from '../types.ts'; -import { join, leave, reset, status, toggle } from './command_functions.ts'; - -export function bridge_commands(l: lightning): command { - return { - name: 'bridge', - description: 'bridge this channel to somewhere else', - execute: () => `Try running the help command for help with bridges`, - options: { - subcommands: [ - { - name: 'join', - description: 'join a bridge', - execute: async opts => (await join(opts, l))[1], - options: { argument_name: 'name', argument_required: true } - }, - { - name: 'leave', - description: 'leave a bridge', - execute: async opts => (await leave(opts, l))[1] - }, - { - name: 'reset', - description: 'reset a bridge', - execute: async opts => await reset(opts, l), - options: { argument_name: 'name' } - }, - { - name: 'toggle', - description: 'toggle a setting on a bridge', - execute: async opts => await toggle(opts, l), - options: { argument_name: 'setting', argument_required: true } - }, - { - name: 'status', - description: 'see what bridges you are in', - execute: async opts => await status(opts, l) - } - ] - } - }; -} diff --git a/packages/lightning/src/bridges/functions.ts b/packages/lightning/src/bridges/functions.ts new file mode 100644 index 0000000..86e9320 --- /dev/null +++ b/packages/lightning/src/bridges/functions.ts @@ -0,0 +1,48 @@ +import type { lightning } from '../lightning.ts'; +import type { bridge_document } from '../types.ts'; + +export async function exists(l: lightning, key: string) { + return Boolean(await l.redis.sendCommand(['EXISTS', key])); +} + +export async function get_json( + l: lightning, + key: string +): Promise { + const reply = await l.redis.sendCommand(['GET', key]); + if (!reply || reply === 'OK') return; + return JSON.parse(reply as string) as T; +} + +export async function del_key(l: lightning, key: string) { + await l.redis.sendCommand(['DEL', key]); +} + +export async function set_json(l: lightning, key: string, value: unknown) { + await l.redis.sendCommand(['SET', key, JSON.stringify(value)]); +} + +export async function get_bridge(l: lightning, id: string) { + return await get_json(l, `lightning-bridge-${id}`); +} + +export async function get_channel_bridge(l: lightning, id: string) { + const ch = await l.redis.sendCommand(['GET', `lightning-bchannel-${id}`]); + return await get_bridge(l, ch as string); +} + +export async function get_message_bridge(l: lightning, id: string) { + return await get_json(l, `lightning-bridged-${id}`); +} + +export async function set_bridge(l: lightning, bridge: bridge_document) { + set_json(l, `lightning-bridge-${bridge.id}`, bridge); + + for (const channel of bridge.channels) { + await l.redis.sendCommand([ + 'SET', + `lightning-bchannel-${channel.id}`, + bridge.id + ]); + } +} diff --git a/packages/lightning/src/bridges/handle_message.ts b/packages/lightning/src/bridges/handle_message.ts index 3d49b25..49c064f 100644 --- a/packages/lightning/src/bridges/handle_message.ts +++ b/packages/lightning/src/bridges/handle_message.ts @@ -6,6 +6,11 @@ import type { message } from '../types.ts'; import { log_error } from '../utils.ts'; +import { + get_channel_bridge, + get_message_bridge, + set_json +} from './functions.ts'; export async function handle_message( lightning: lightning, @@ -14,8 +19,8 @@ export async function handle_message( ): Promise { const bridge = type === 'create_message' - ? await lightning.bridges.get_bridge(msg) - : await lightning.bridges.get_bridge_message(msg.id); + ? await get_channel_bridge(lightning, msg.channel) + : await get_message_bridge(lightning, msg.id); if (!bridge) return; @@ -75,28 +80,22 @@ export async function handle_message( } } - await lightning.redis.sendCommand([ - 'SET', - `lightning-isbridged-${dat}`, - '1' - ]); + await set_json(lightning, `lightning-isbridged-${dat}`, '1'); messages.push({ id: dat, channel: channel.id, plugin: channel.plugin }); } for (const i of messages) { - await lightning.redis.sendCommand([ - 'SET', - `lightning-bridged-${i.id}`, - JSON.stringify({ ...bridge, messages }) - ]); + await set_json(lightning, `lightning-bridged-${i.id}`, { + ...bridge, + messages + }); } - await lightning.redis.sendCommand([ - 'SET', - `lightning-bridged-${msg.id}`, - JSON.stringify({ ...bridge, messages }) - ]); + await set_json(lightning, `lightning-bridged-${msg.id}`, { + ...bridge, + messages + }); } async function get_reply_id( @@ -106,7 +105,7 @@ async function get_reply_id( ) { if (msg.reply_id) { try { - const bridged = await lightning.bridges.get_bridge_message(msg.reply_id); + const bridged = await get_message_bridge(lightning, msg.reply_id); if (!bridged) return; diff --git a/packages/lightning/src/bridges/mod.ts b/packages/lightning/src/bridges/mod.ts deleted file mode 100644 index 5759f60..0000000 --- a/packages/lightning/src/bridges/mod.ts +++ /dev/null @@ -1,106 +0,0 @@ -import type { lightning } from '../lightning.ts'; -import type { bridge_document } from '../types.ts'; -import { bridge_commands } from './commands.ts'; -import { handle_message } from './handle_message.ts'; - -/** a thing that bridges messages between plugins */ -export class bridges { - /** the parent instance of lightning */ - private l: lightning; - - /** create a bridge instance and attach to lightning */ - constructor(l: lightning) { - this.l = l; - l.on('create_message', async msg => { - await new Promise(res => setTimeout(res, 15)); - if (await this.is_bridged(msg.id)) return; - l.emit('create_nonbridged_message', msg); - handle_message(l, msg, 'create_message'); - }); - l.on('edit_message', async msg => { - await new Promise(res => setTimeout(res, 15)); - if (await this.is_bridged(msg.id)) return; - handle_message(l, msg, 'edit_message'); - }); - l.on('delete_message', async msg => { - await new Promise(res => setTimeout(res, 15)); - handle_message(l, msg, 'delete_message'); - }); - l.commands.set('bridge', bridge_commands(l)); - } - - /** - * get all the channels a message was bridged to - * @param id the id of the message to get the channels for - */ - async get_bridge_message(id: string): Promise { - const rdata = await this.l.redis.sendCommand([ - 'GET', - `lightning-bridged-${id}` - ]); - if (!rdata || rdata == 'OK') return; - return JSON.parse(rdata as string) as bridge_document; - } - - /** - * check if a message was bridged - * @param id the id of the message to check - */ - async is_bridged(id: string): Promise { - return Boolean( - await this.l.redis.sendCommand(['EXISTS', `lightning-bridged-${id}`]) - ); - } - - /** - * check if a channel is in a bridge - * @param channel the channel to check - */ - async is_in_bridge(channel: string): Promise { - return Boolean( - Number( - await this.l.redis.sendCommand([ - 'EXISTS', - `lightning-bchannel-${channel}` - ]) - ) - ); - } - - /** - * get a bridge using the bridges name or a channel in it - * @param param0 the id or channel of the bridge - */ - async get_bridge({ - id, - channel - }: { - id?: string; - channel?: string; - }): Promise { - if (channel) { - id = (await this.l.redis.sendCommand([ - 'GET', - `lightning-bchannel-${channel}` - ])) as string; - } - return JSON.parse( - (await this.l.redis.sendCommand([ - 'GET', - `lightning-bridge-${id}` - ])) as string - ); - } - - /** - * update a bridge in a database - * @param bridge the bridge to update - */ - async set_bridge(bridge: bridge_document): Promise { - await this.l.redis.sendCommand([ - 'SET', - `lightning-bridge-${bridge.id}`, - JSON.stringify(bridge) - ]); - } -} diff --git a/packages/lightning/src/bridges/setup_bridges.ts b/packages/lightning/src/bridges/setup_bridges.ts new file mode 100644 index 0000000..9f170c5 --- /dev/null +++ b/packages/lightning/src/bridges/setup_bridges.ts @@ -0,0 +1,62 @@ +import type { lightning } from '../lightning.ts'; +import { join, leave, reset, status, toggle } from './cmd_internals.ts'; +import { exists } from './functions.ts'; +import { handle_message } from './handle_message.ts'; + +export function setup_bridges(l: lightning) { + l.on('create_message', async msg => { + await new Promise(res => setTimeout(res, 15)); + if (await exists(l, `lightning-bridged-${msg.id}`)) return; + l.emit(`create_nonbridged_message`, msg); + handle_message(l, msg, 'create_message'); + }); + + l.on('edit_message', async msg => { + await new Promise(res => setTimeout(res, 15)); + if (await exists(l, `lightning-bridged-${msg.id}`)) return; + handle_message(l, msg, 'edit_message'); + }); + + l.on('delete_message', async msg => { + await new Promise(res => setTimeout(res, 15)); + handle_message(l, msg, 'delete_message'); + }); + + l.commands.set('bridge', { + name: 'bridge', + description: 'bridge this channel to somewhere else', + execute: () => `Try running the help command for help with bridges`, + options: { + subcommands: [ + { + name: 'join', + description: 'join a bridge', + execute: async opts => (await join(opts))[1], + options: { argument_name: 'name', argument_required: true } + }, + { + name: 'leave', + description: 'leave a bridge', + execute: async opts => (await leave(opts))[1] + }, + { + name: 'reset', + description: 'reset a bridge', + execute: async opts => await reset(opts), + options: { argument_name: 'name' } + }, + { + name: 'toggle', + description: 'toggle a setting on a bridge', + execute: async opts => await toggle(opts), + options: { argument_name: 'setting', argument_required: true } + }, + { + name: 'status', + description: 'see what bridges you are in', + execute: async opts => await status(opts) + } + ] + } + }); +} diff --git a/packages/lightning/src/cli/migrations.ts b/packages/lightning/src/cli/migrations.ts index 26dfb44..75981c5 100644 --- a/packages/lightning/src/cli/migrations.ts +++ b/packages/lightning/src/cli/migrations.ts @@ -1,122 +1,119 @@ import { MongoClient, RedisClient } from '../../deps.ts'; -import { convert_five_to_seven_redis } from '../migrations.ts'; +import { conv_mongo_to_redis } from '../migrations.ts'; import { versions } from '../types.ts'; -import { apply_migrations, get_migrations } from '../utils.ts'; -import { writeFile } from 'node:fs/promises'; +import { get_migrations } from '../utils.ts'; -export async function migrations() { - const redis_hostname = prompt( - `what hostname is used by your redis instance?`, - 'localhost' - ); - const redis_port = prompt( - `what port is used by your redis instance?`, - '6379' - ); - const mongo = confirm(`are you migrating from a mongo database?`); +const redis_hostname = prompt( + `what hostname is used by your redis instance?`, + 'localhost' +); +const redis_port = prompt(`what port is used by your redis instance?`, '6379'); +const mongo = confirm(`are you migrating from a mongo database?`); - if (!redis_hostname || !redis_port) return; +if (!redis_hostname || !redis_port) Deno.exit(); - const redis = new RedisClient( - await Deno.connect({ - hostname: redis_hostname, - port: Number(redis_port) - }) - ); +const redis = new RedisClient( + await Deno.connect({ + hostname: redis_hostname, + port: Number(redis_port) + }) +); - console.log('connected to redis!'); +console.log('connected to redis!'); - let data: [string, unknown][]; +let data: [string, unknown][]; - if (mongo) { - const mongo_str = prompt('what is your mongo connection string?'); - const mongo_db = prompt('what is your mongo database?'); - const mongo_collection = prompt('what is your mongo collection?'); +if (mongo) { + const mongo_str = prompt('what is your mongo connection string?'); + const mongo_db = prompt('what is your mongo database?'); + const mongo_collection = prompt('what is your mongo collection?'); - if (!mongo_str || !mongo_db || !mongo_collection) return; + if (!mongo_str || !mongo_db || !mongo_collection) Deno.exit(); - const client = new MongoClient(); + const client = new MongoClient(); - await client.connect(mongo_str); + await client.connect(mongo_str); - const collection = client.database(mongo_db).collection(mongo_collection); + const collection = client.database(mongo_db).collection(mongo_collection); - console.log(`connected to mongo!`); - console.log(`downloading data from mongo...`); + console.log(`connected to mongo!`); + console.log(`downloading data from mongo...`); - const mongo_data = (await collection.find({}).toArray()).map(i => [ - i._id, - i - ]) as [string, unknown][]; + const mongo_data = (await collection.find({}).toArray()).map(i => [ + i._id, + i + ]) as [string, unknown][]; - console.log(`downloaded data from mongo!`); - console.log(`applying migrations...`); + console.log(`downloaded data from mongo!`); + console.log(`applying migrations...`); - data = convert_five_to_seven_redis(mongo_data); - } else { - console.log(`available versions: ${Object.values(versions).join(', ')}`); + data = conv_mongo_to_redis(mongo_data); +} else { + console.log(`available versions: ${Object.values(versions).join(', ')}`); - const from_version = prompt('what version are you migrating from?') as - | versions - | undefined; - const to_version = prompt('what version are you migrating to?') as - | versions - | undefined; + const from_version = prompt('what version are you migrating from?') as + | versions + | undefined; + const to_version = prompt('what version are you migrating to?') as + | versions + | undefined; - if (!from_version || !to_version) return; + if (!from_version || !to_version) Deno.exit(); - const migrations = get_migrations(from_version, to_version); + const migrations = get_migrations(from_version, to_version); - if (migrations.length < 1) return; + if (migrations.length < 1) Deno.exit(); - console.log(`downloading data from redis...`); + console.log(`downloading data from redis...`); - const keys = (await redis.sendCommand(['KEYS', '*'])) as string[]; - const redis_data = [] as [string, unknown][]; + const keys = (await redis.sendCommand(['KEYS', '*'])) as string[]; + const redis_data = [] as [string, unknown][]; - // this is bad for the database, sorry database :( + // this is bad for the database, sorry database :( - for (const key of keys) { - try { - redis_data.push([ - key, - JSON.parse((await redis.sendCommand(['GET', key])) as string) - ]); - } catch { - console.log(`skipping ${key} due to invalid JSON...`); - continue; - } + for (const key of keys) { + try { + redis_data.push([ + key, + JSON.parse((await redis.sendCommand(['GET', key])) as string) + ]); + } catch { + console.log(`skipping ${key} due to invalid JSON...`); + continue; } + } - console.log(`downloaded data from redis!`); - console.log(`applying migrations...`); + console.log(`downloaded data from redis!`); + console.log(`applying migrations...`); - data = apply_migrations(migrations, redis_data); - } + data = migrations.reduce( + (acc, migration) => migration.translate(acc), + redis_data + ); +} - const final_data = data.map(([key, value]) => { - return [key, JSON.stringify(value)]; - }); +const final_data = data.map(([key, value]) => { + return [key, JSON.stringify(value)]; +}); - console.log(`migrated your data!`); +console.log(`migrated your data!`); - const file = await Deno.makeTempFile(); +const file = await Deno.makeTempFile(); - await writeFile(file, JSON.stringify(final_data)); +await Deno.writeTextFile(file, JSON.stringify(final_data)); - const write = confirm( - `do you want the data in ${file} to be written to the database?` - ); +const write = confirm( + `do you want the data in ${file} to be written to the database?` +); - if (!write) return; +if (!write) Deno.exit(); - const cmd = ['MSET', ...final_data.flat()]; +const cmd = ['MSET', ...final_data.flat()]; - const reply = await redis.sendCommand(cmd); +const reply = await redis.sendCommand(cmd); - if (reply === 'OK') { - console.log('data written to database'); - } else { - console.log('error writing data to database', reply); - } +if (reply === 'OK') { + console.log('data written to database'); +} else { + console.log('error writing data to database', reply); } diff --git a/packages/lightning/src/cli/mod.ts b/packages/lightning/src/cli/mod.ts index b22ec50..589a7f9 100644 --- a/packages/lightning/src/cli/mod.ts +++ b/packages/lightning/src/cli/mod.ts @@ -1,27 +1,43 @@ import { parseArgs } from '../../deps.ts'; -import { migrations } from './migrations.ts'; -import { run } from './run.ts'; +import { lightning } from '../lightning.ts'; +import type { config } from '../types.ts'; +import { log_error } from '../utils.ts'; -const cli_args = parseArgs(Deno.args, { +const _ = parseArgs(Deno.args, { string: ['config'] }); -const cmd = cli_args._[0]; +const cmd = _._[0]; if (cmd === 'version') { - console.log('0.6.0'); + console.log('0.7.0'); } else if (cmd === 'run') { - run(cli_args.config); + const cfg = (await import(_.config || `${Deno.cwd()}/config.ts`)) + ?.default as config; + + Deno.env.set('LIGHTNING_ERROR_HOOK', cfg.errorURL || ''); + + try { + new lightning( + cfg, + await Deno.connect({ + hostname: cfg.redis_host || 'localhost', + port: cfg.redis_port || 6379 + }) + ); + } catch (e) { + await log_error(e); + Deno.exit(1); + } } else if (cmd === 'migrations') { - migrations(); + import('./migrations.ts'); } else { - console.log('lightning v0.6.0 - cross-platform bot connecting communities'); + console.log('lightning v0.7.0 - extensible chatbot connecting communities'); console.log(' Usage: lightning [subcommand] '); console.log(' Subcommands:'); - console.log(' help: show this'); console.log(' run: run an of lightning using the settings in config.ts'); console.log(' migrations: run migration script'); console.log(' version: shows version'); console.log(' Options:'); - console.log(' --config : absolute path to config file'); + console.log(' --config : path to config file'); } diff --git a/packages/lightning/src/cli/run.ts b/packages/lightning/src/cli/run.ts deleted file mode 100644 index cfa7f5b..0000000 --- a/packages/lightning/src/cli/run.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { lightning } from '../lightning.ts'; -import { define_config, log_error } from '../utils.ts'; - -export async function run(config?: string) { - try { - const cfg = define_config( - (await import(config || `${Deno.cwd()}/config.ts`))?.default - ); - - Deno.env.set('LIGHTNING_ERROR_HOOK', cfg.errorURL || ''); - - const redis = await Deno.connect({ - hostname: cfg.redis_host, - port: cfg.redis_port || 6379 - }); - - new lightning(cfg, redis); - } catch (e) { - await log_error(e); - Deno.exit(1); - } -} diff --git a/packages/lightning/src/lightning.ts b/packages/lightning/src/lightning.ts index a053dac..f29c954 100644 --- a/packages/lightning/src/lightning.ts +++ b/packages/lightning/src/lightning.ts @@ -1,19 +1,16 @@ import { EventEmitter, RedisClient, parseArgs } from '../deps.ts'; -import { bridges } from './bridges/mod.ts'; +import { setup_bridges } from "./bridges/setup_bridges.ts"; import type { plugin } from './plugins.ts'; import type { command, command_arguments, config, - create_plugin, plugin_events } from './types.ts'; import { create_message, log_error } from './utils.ts'; /** an instance of lightning */ export class lightning extends EventEmitter { - /** the bridge system */ - bridges: bridges; /** the commands registered */ commands: Map; /** the config used */ @@ -30,14 +27,14 @@ export class lightning extends EventEmitter { this.commands = new Map(config.commands); this.redis = new RedisClient(redis_conn); this.listen_commands(); - this.bridges = new bridges(this); - this.load(this.config.plugins); + setup_bridges(this); + this.load(); } /** load plugins */ - load(plugins: create_plugin>[]) { + load() { let unsupported = 0; - for (const p of plugins) { + for (const p of this.config.plugins || []) { if (p.support !== '0.7.0') { unsupported++; continue; @@ -75,16 +72,13 @@ export class lightning extends EventEmitter { cmd: cmd as string, subcmd: subcmd as string, opts, - channel: m.channel, - plugin: m.plugin, - reply: m.reply, - timestamp: m.timestamp + ...m }); }); } /** run a command */ - async run_command(args: command_arguments) { + async run_command(args: Omit) { let reply; try { @@ -94,16 +88,13 @@ export class lightning extends EventEmitter { cmd.options?.subcommands?.find(i => i.name === args.subcmd)?.execute || cmd.execute; - reply = await exec(args); + reply = create_message(await exec({ ...args, lightning: this })); } catch (e) { reply = (await log_error(e, { ...args, reply: undefined })).message; } try { - await args.reply( - typeof reply == 'string' ? create_message(reply) : reply, - false - ); + await args.reply(reply, false); } catch (e) { await log_error(e, { ...args, reply: undefined }); } diff --git a/packages/lightning/src/migrations.ts b/packages/lightning/src/migrations.ts index 0189229..3569378 100644 --- a/packages/lightning/src/migrations.ts +++ b/packages/lightning/src/migrations.ts @@ -1,4 +1,4 @@ -import { type bridge_document, versions } from './types.ts'; +import { versions, type bridge_document, type migration } from './types.ts'; type doc = [string, unknown][]; @@ -16,7 +16,7 @@ type fivedocredis = [ { plugin: string; channel: string; senddata: unknown; id: string }[] ][]; -export function convert_five_to_seven_redis(items: doc | fivedoc) { +export function conv_mongo_to_redis(items: doc | fivedoc) { return (items as fivedoc).map(([id, val]) => { return [ id, @@ -36,34 +36,36 @@ export function convert_five_to_seven_redis(items: doc | fivedoc) { }) as doc; } -export const fivesevenexistingredis = { - from: versions.Five, - to: versions.Seven, - translate: (items: doc | fivedocredis) => - (items as fivedocredis).flatMap(([id, val]) => { - if (id.startsWith('lightning-bridge-')) { - const [_, _2, msg_id] = id.split('-'); - return [ - [ - `lightning-bridged-${msg_id}`, - { - allow_editing: true, - channels: val.map(i => { - return { id: i.channel, data: i.senddata, plugin: i.plugin }; - }), - id: `oldeditsupport-${msg_id}`, - messages: val.map(i => { - return { - channel: i.channel, - id: i.id, - plugin: i.plugin - }; - }), - use_rawname: false - } as bridge_document - ] - ]; - } - return []; - }) as doc -}; +export const migrations = [ + { + from: versions.Five, + to: versions.Seven, + translate: (items: doc | fivedocredis) => + (items as fivedocredis).flatMap(([id, val]) => { + if (id.startsWith('lightning-bridge-')) { + const [_, _2, msg_id] = id.split('-'); + return [ + [ + `lightning-bridged-${msg_id}`, + { + allow_editing: true, + channels: val.map(i => { + return { id: i.channel, data: i.senddata, plugin: i.plugin }; + }), + id: `oldeditsupport-${msg_id}`, + messages: val.map(i => { + return { + channel: i.channel, + id: i.id, + plugin: i.plugin + }; + }), + use_rawname: false + } as bridge_document + ] + ]; + } + return []; + }) as doc + } +] as migration[]; diff --git a/packages/lightning/src/plugins.ts b/packages/lightning/src/plugins.ts index 0e9d394..afb5655 100644 --- a/packages/lightning/src/plugins.ts +++ b/packages/lightning/src/plugins.ts @@ -8,11 +8,6 @@ import type { plugin_events } from './types.ts'; -/** - * lightning plugins are used to add support for new chat apps to lightning - * @module - */ - /** a plugin for lightning */ export abstract class plugin extends EventEmitter { /** access the instance of lightning you're connected to */ diff --git a/packages/lightning/src/tests/error_handling.ts b/packages/lightning/src/tests/error_handling.ts deleted file mode 100644 index ce43d16..0000000 --- a/packages/lightning/src/tests/error_handling.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { assertEquals } from '../../deps.ts'; -import { log_error } from '../utils.ts'; - -const temporal_instant = Temporal.Instant.from('2021-01-01T00:00:00Z'); - -Temporal.Now.instant = () => { - return temporal_instant; -}; - -console.log = console.error = () => {}; - -Deno.test('basic error handling', async () => { - Deno.env.set('LIGHTNING_ERROR_HOOK', ''); - - const result = await log_error(err, extra, error_id); - - result.message.reply = error_return.message.reply; - - assertEquals(result, error_return); -}); - -Deno.test('webhook error handling', async () => { - Deno.env.set('LIGHTNING_ERROR_HOOK', 'http://localhost:8000'); - - let res: (value: unknown) => void; - - const promise = new Promise(resolve => { - res = resolve; - }); - - const server = Deno.serve(async req => { - res(await req.json()); - return new Response(); - }); - - await log_error(err, extra, error_id); - - await server.shutdown(); - - assertEquals(await promise, error_webhook); -}); - -const err = new Error('test'); -const extra = { test: 'test' }; -const error_id = () => 'test'; - -const error_return = { - e: err, - uuid: 'test', - extra: { test: 'test' }, - message: { - author: { - username: 'lightning', - profile: 'https://williamhorning.dev/assets/lightning.png', - rawname: 'lightning', - id: 'lightning' - }, - content: - 'Something went wrong! [Look here](https://williamhorning.dev/bolt) for help.\n```\ntest\ntest\n```', - channel: '', - id: '', - reply: async () => {}, - timestamp: Temporal.Instant.from('2021-01-01T00:00:00Z'), - plugin: 'lightning' - } -}; - -const error_webhook = { - embeds: [ - { - title: err.message, - description: error_return.uuid - } - ] -}; diff --git a/packages/lightning/src/tests/migrations.ts b/packages/lightning/src/tests/migrations.ts deleted file mode 100644 index 6b2fa72..0000000 --- a/packages/lightning/src/tests/migrations.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { assertEquals } from '../../deps.ts'; -import { fivesevenexistingredis } from '../migrations.ts'; -import { versions } from '../types.ts'; -import { apply_migrations, get_migrations } from '../utils.ts'; - -Deno.test('get a migration', () => { - const migrations = get_migrations(versions.Five, versions.Seven); - assertEquals(migrations, [fivesevenexistingredis]); -}); - -Deno.test('apply migrations', () => { - const result = apply_migrations([fivesevenexistingredis], migrations_five); - - assertEquals(result, migrations_seven as [string, unknown][]); -}); - -const migrations_five = [ - [ - 'lightning-bridge-1', - [ - { - plugin: 'bolt-discord', - channel: '000000000000000000', - senddata: { id: '1', token: '2' }, - id: '1' - }, - { - plugin: 'bolt-guilded', - channel: '6cb2f623-8eee-44a3-b5bf-cf9b147e46d7', - senddata: { id: '1', token: '2' }, - id: '2' - } - ] - ] -] as [string, unknown][]; - -const migrations_seven = [ - [ - 'lightning-bridged-1', - { - allow_editing: true, - channels: [ - { - id: '000000000000000000', - data: { id: '1', token: '2' }, - plugin: 'bolt-discord' - }, - { - id: '6cb2f623-8eee-44a3-b5bf-cf9b147e46d7', - data: { id: '1', token: '2' }, - plugin: 'bolt-guilded' - } - ], - id: 'oldeditsupport-1', - messages: [ - { - channel: '000000000000000000', - id: '1', - plugin: 'bolt-discord' - }, - { - channel: '6cb2f623-8eee-44a3-b5bf-cf9b147e46d7', - id: '2', - plugin: 'bolt-guilded' - } - ], - use_rawname: false - } - ] -]; diff --git a/packages/lightning/src/tests/utils.ts b/packages/lightning/src/tests/utils.ts deleted file mode 100644 index ca78ff7..0000000 --- a/packages/lightning/src/tests/utils.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { assertEquals } from '../../deps.ts'; -import { create_message, define_config } from '../utils.ts'; - -const temporal_instant = Temporal.Instant.from('2021-01-01T00:00:00Z'); - -Temporal.Now.instant = () => { - return temporal_instant; -}; - -console.log = console.error = () => {}; - -Deno.test('config handling', () => { - assertEquals(define_config(), cfg); -}); - -Deno.test('message creation', () => { - const result = create_message('test'); - - result.reply = msg.reply; - - assertEquals(result, msg); -}); - -const msg = { - author: { - username: 'lightning', - profile: 'https://williamhorning.dev/assets/lightning.png', - rawname: 'lightning', - id: 'lightning' - }, - content: 'test', - channel: '', - id: '', - reply: async () => {}, - timestamp: Temporal.Instant.from('2021-01-01T00:00:00Z'), - plugin: 'lightning' -}; - -const cfg = { - plugins: [], - redis_host: 'localhost', - redis_port: 6379 -}; diff --git a/packages/lightning/src/types.ts b/packages/lightning/src/types.ts index 70882c4..4214348 100644 --- a/packages/lightning/src/types.ts +++ b/packages/lightning/src/types.ts @@ -1,11 +1,6 @@ import type { lightning } from './lightning.ts'; import type { plugin } from './plugins.ts'; -/** - * types used by lightning - * @module - */ - /** attachments within a message */ export interface attachment { /** alt text for images */ @@ -21,11 +16,11 @@ export interface attachment { } /** channel within a bridge */ -export interface bridge_channel { +export interface bridge_channel { /** the id of this channel */ id: string; /** the data needed to bridge this channel */ - data: data_type; + data: unknown; /** the plugin used to bridge this channel */ plugin: string; } @@ -70,13 +65,13 @@ export interface create_plugin< export interface config { /** a list of plugins */ // deno-lint-ignore no-explicit-any - plugins: create_plugin[]; + plugins?: create_plugin[]; /** the prefix used for commands */ cmd_prefix?: string; /** the set of commands to use */ commands?: [string, command][]; /** the hostname of your redis instance */ - redis_host: string; + redis_host?: string; /** the port of your redis instance */ redis_port?: number; /** the webhook used to send errors to */ @@ -99,6 +94,8 @@ export interface command_arguments { opts: Record; /** the function to reply to the command */ reply: (message: message, optional?: unknown) => Promise; + /** the instance of lightning the command is ran against */ + lightning: lightning } /** options when parsing a command */ @@ -131,10 +128,7 @@ export interface deleted_message { channel: string; /** the plugin that recieved the message */ plugin: string; - /** - * the time the message was sent/edited as a temporal instant - * @see https://tc39.es/proposal-temporal/docs/instant.html - */ + /** the time the message was sent/edited as a temporal instant */ timestamp: Temporal.Instant; } diff --git a/packages/lightning/src/utils.ts b/packages/lightning/src/utils.ts index e05d4ed..326989d 100644 --- a/packages/lightning/src/utils.ts +++ b/packages/lightning/src/utils.ts @@ -1,22 +1,5 @@ -import { fivesevenexistingredis } from './migrations.ts'; -import type { config, err, message, migration, versions } from './types.ts'; - -/** - * utilities used by lightning - * @module - */ - -/** - * apply many migrations given data - * @param migrations the migrations to apply - * @param data the data to apply the migrations to - */ -export function apply_migrations( - migrations: migration[], - data: [string, unknown][] -): [string, unknown][] { - return migrations.reduce((acc, migration) => migration.translate(acc), data); -} +import { migrations } from "./migrations.ts"; +import type { err, message, migration, versions } from './types.ts'; /** * creates a message that can be sent using lightning @@ -40,26 +23,12 @@ export function create_message(text: string): message { return data; } -/** - * a function that returns a config object when given a partial config object - * @param config a partial config object - */ -export function define_config(config?: Partial): config { - return { - plugins: [], - redis_host: 'localhost', - redis_port: 6379, - ...(config || {}) - }; -} - /** * get migrations that can then be applied using apply_migrations * @param from the version that the data is currently in * @param to the version that the data will be migrated to */ export function get_migrations(from: versions, to: versions): migration[] { - const migrations: migration[] = [fivesevenexistingredis]; return migrations.slice( migrations.findIndex(i => i.from === from), migrations.findLastIndex(i => i.to === to) + 1 @@ -70,17 +39,15 @@ export function get_migrations(from: versions, to: versions): migration[] { * logs an error and returns a unique id and a message for users * @param e the error to log * @param extra any extra data to log - * @param _id a function that returns a unique id (used for testing) */ export async function log_error( e: Error, extra: Record = {}, - _id?: () => string ): Promise { - const uuid = _id ? _id() : crypto.randomUUID(); + const uuid = crypto.randomUUID(); const error_hook = Deno.env.get('LIGHTNING_ERROR_HOOK'); - if (error_hook) { + if (error_hook && error_hook.length > 0) { await ( await fetch(error_hook, { method: 'POST',