djs-bridge 0.0.0-stage → 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,3 +1,77 @@
1
- # Temporary Holding Version
1
+ # djs-bridge
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ <img src="https://codeberg.org/sanin/djs-bridge/raw/branch/master/assets/djs-bridge.svg" alt="djs-bridge icon; a white silhouette of a bridge against a blurple background" width="200" height="200"/>
4
+
5
+ Yet another Discord ↔️ Fluxer bridge application.
6
+
7
+ ## Why Another?
8
+
9
+ I started building this before I discovered other similar applications. Popular amongst which are [Bifröst](https://bifrost-bot.com/) and [Fluxcord](https://fluxcord.jbcrn.dev/). While fulfilling the same goals, djs-bridge still manages to stand out by being lean and simple. Bifröst has a web frontend which is handy for hosted services but overkill for a self-hosted bot. Fluxcord supports bridging voice chats, which adds to its complexity and isn't always needed or desired.
10
+
11
+ djs-bridge was built from the ground-up to be a basic text-chat mirroring application. One SQLite table persists message mapping data. Configuration is done with one configuration file. No bot commands. The bare minimum to get chat relayed between guilds.
12
+
13
+ ## Features
14
+
15
+ - Messages are automatically relayed to channels across guilds with matching names
16
+ - Users' usernames and avatars are displayed via webhooks
17
+ - Attachments are supported
18
+ - Custom emoji is used when present on the destination guild
19
+ - Message edits and deletions are propogated across guilds
20
+ - Reactions kind of work. The bot can't do more than 1 reaction per emoji.
21
+
22
+ ## Planned
23
+
24
+ - Stickers (as attachments)
25
+ - Threads
26
+ - Mirroring bot activity
27
+
28
+ ## Out of scope
29
+
30
+ These features may exists in the alternatives mentioned above. They will not come to djs-bridge.
31
+
32
+ - Polls
33
+ - Voice chat bridging
34
+ - Members online count/status
35
+
36
+ # How to
37
+
38
+ The bot is available via [Codeberg's container registry](https://codeberg.org/sanin/-/packages/container/djs-bridge/), [Docker hub](https://hub.docker.com/r/corysanin/djs-bridge), [npm](https://www.npmjs.com/package/djs-bridge), or via source.
39
+
40
+ See [docker-compose.yml](https://codeberg.org/sanin/djs-bridge/src/branch/master/docker-compose.yml) for an example Docker deployment.
41
+
42
+ ## Build from source
43
+
44
+ ```bash
45
+ git clone https://codeberg.org/sanin/djs-bridge.git
46
+ cd djs-bridge
47
+ pnpm install
48
+ pnpm run build
49
+ cp config/config.example.yaml cp config/config.yaml # edit this file
50
+ pnpm run start
51
+ ```
52
+
53
+ ## Configuration
54
+
55
+ ```yaml
56
+ db:
57
+ # Where to store sqlite DB. Defaults to `./db/messages.db`
58
+ file: messages.db
59
+ # Whether to skip db migrations or not. Defaults to `false`
60
+ skipMigrations: false
61
+ # An array of bots to relay messages between. Should have at least two.
62
+ bots:
63
+ # The name that appears after usernames in relayed messages. Also used in the DB, must be unique. Required.
64
+ - name: A Fluxer Server
65
+ # The web address that will be used for the bot API. Defaults to `https://discord.com`
66
+ server: https://fluxer.com
67
+ # Bot token for the respective platform. Required.
68
+ token: 123_abc_xyz
69
+ # ID for the guild on this server to sync chat to/from. Required.
70
+ guild: '225989349949308928'
71
+ # ID for the channel in this guild to post messages to if a matching channel can't be found. Required.
72
+ defaultChannel: '1569188535674601475'
73
+ # True doesn't upload the incoming avatar to this server. Useful if you're concerned about disk space. Defaults to `false`.
74
+ suppressAvatar: false
75
+ - name: A Discord Server
76
+ ...
77
+ ```
@@ -0,0 +1,5 @@
1
+ import { DiscordBot } from './bot.discord.js';
2
+ import { FluxerBot } from './bot.fluxer.js';
3
+ import type { BotConfig } from './bot.interface.js';
4
+ import type { DB } from './db.js';
5
+ export declare function createBot(cfg: BotConfig, db: DB): Promise<DiscordBot | FluxerBot>;
@@ -0,0 +1,17 @@
1
+ import type { Guild, GuildBasedChannel } from 'discord.js';
2
+ import { iBot, type MessagePayload, type BotConfig, GetChannelOptions, GetDestinationOptions, ReactPayload } from './bot.interface.js';
3
+ import type { DB } from './db.js';
4
+ export declare class DiscordBot extends iBot<Guild, GuildBasedChannel> {
5
+ private client;
6
+ constructor(cfg: BotConfig, db: DB);
7
+ private getClient;
8
+ init(): Promise<void>;
9
+ getGuild: () => Promise<Guild>;
10
+ getChannel: (options: GetChannelOptions) => Promise<GuildBasedChannel | null>;
11
+ findEmoji: (guild: Guild, name?: string | null) => import("discord.js").GuildEmoji | undefined;
12
+ resolveEmoji: (nameOrEmoji: string) => Promise<string | import("discord.js").GuildEmoji | undefined>;
13
+ close(): Promise<void>;
14
+ deleteMessage: (payload: GetDestinationOptions & GetChannelOptions) => Promise<void>;
15
+ sendMessage: (payload: MessagePayload) => Promise<void>;
16
+ sendReact: (payload: ReactPayload) => Promise<void>;
17
+ }
@@ -0,0 +1,182 @@
1
+ import { Client, GatewayIntentBits, Events, ChannelType } from 'discord.js';
2
+ import { iBot } from './bot.interface.js';
3
+ export class DiscordBot extends iBot {
4
+ client;
5
+ constructor(cfg, db) {
6
+ super(cfg, db);
7
+ }
8
+ getClient() {
9
+ if (this.client) {
10
+ return this.client;
11
+ }
12
+ throw new Error('Bot not initialized!');
13
+ }
14
+ async init() {
15
+ const client = this.client = new Client({
16
+ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildWebhooks, GatewayIntentBits.GuildMessages, GatewayIntentBits.GuildMessageReactions, GatewayIntentBits.MessageContent],
17
+ allowedMentions: { parse: [] }
18
+ });
19
+ await client.login(this.config.token);
20
+ console.log(`[${this.config.name}] logged in to Discord as ${client.user?.username}`);
21
+ const handleMessage = async (emsg, newmsg) => {
22
+ const msg = newmsg ? (newmsg.partial ? await newmsg.fetch() : newmsg) : emsg;
23
+ if (!msg || msg.guild?.id !== this.config.guild || msg.member?.user.bot || msg.webhookId) {
24
+ return;
25
+ }
26
+ if (newmsg && this.isEditTooLate(emsg.createdTimestamp)) {
27
+ return;
28
+ }
29
+ const channel = msg.channel;
30
+ const payload = {
31
+ action: newmsg ? 'update' : 'send',
32
+ source: this.config.name,
33
+ voice: channel.type === ChannelType.GuildVoice,
34
+ username: msg.member?.nickname ?? msg.author?.username ?? 'UNKNOWN USER',
35
+ channel: channel.name ?? this.config.defaultChannel,
36
+ content: msg.content,
37
+ originatingId: msg.id,
38
+ };
39
+ msg.attachments && (payload.attachments = msg.attachments.map((v, _k) => {
40
+ const att = {
41
+ description: v.description,
42
+ attachment: v.url,
43
+ name: v.name,
44
+ };
45
+ v.waveform && (att.waveform = v.waveform);
46
+ v.title && (att.title = v.title);
47
+ v.duration && (att.duration = v.duration);
48
+ return att;
49
+ }));
50
+ const avatarUrl = msg.member?.avatarURL() ?? msg.member?.user.avatarURL() ?? msg.author?.avatarURL();
51
+ avatarUrl && (payload.avatar = avatarUrl);
52
+ this.emit('message', payload);
53
+ };
54
+ client.on(Events.MessageUpdate, handleMessage);
55
+ client.on(Events.MessageCreate, handleMessage);
56
+ client.on(Events.MessageDelete, e => {
57
+ const channel = e.channel;
58
+ const payload = {
59
+ action: 'delete',
60
+ source: this.config.name,
61
+ voice: channel.type === ChannelType.GuildVoice,
62
+ username: 'N/A',
63
+ channel: channel.name ?? this.config.defaultChannel,
64
+ content: null,
65
+ originatingId: e.id,
66
+ };
67
+ this.emit('message', payload);
68
+ });
69
+ const handleReact = (reaction, add) => {
70
+ if ((add ? reaction.count !== 1 : reaction.count !== 0) || (!!reaction.users.cache.size && reaction.users.cache.every(user => user.bot))) {
71
+ return;
72
+ }
73
+ const channel = reaction.message.channel;
74
+ const source = this.getSource({
75
+ destination: this.config.name,
76
+ destinationid: reaction.message.id
77
+ });
78
+ const payload = {
79
+ action: add ? 'add' : 'remove',
80
+ source: source?.source ?? this.config.name,
81
+ voice: channel.type === ChannelType.GuildVoice,
82
+ channel: channel.name ?? this.config.defaultChannel,
83
+ originatingId: source?.sourceid ?? reaction.message.id,
84
+ emoji: reaction.emoji.id ? `:${reaction.emoji.name}:` : reaction.emoji.name,
85
+ };
86
+ this.emit('react', payload);
87
+ };
88
+ client.on(Events.MessageReactionAdd, (reaction, _user) => handleReact(reaction, true));
89
+ client.on(Events.MessageReactionRemove, (reaction, _user) => handleReact(reaction, false));
90
+ }
91
+ getGuild = async () => {
92
+ const client = this.getClient();
93
+ return await client.guilds.fetch({ guild: this.config.guild, withCounts: false });
94
+ };
95
+ getChannel = async (options) => {
96
+ const guild = await this.getGuild();
97
+ return guild.channels.cache.find(ch => ch.name === options.channel && (ch.type === ChannelType.GuildVoice) === options.voice) ??
98
+ await guild.channels.fetch(this.config.defaultChannel);
99
+ };
100
+ findEmoji = (guild, name) => {
101
+ return guild.emojis.cache.find(emoji => emoji.name === name);
102
+ };
103
+ resolveEmoji = async (nameOrEmoji) => {
104
+ if (!nameOrEmoji.startsWith(':')) {
105
+ return nameOrEmoji;
106
+ }
107
+ const guild = (await this.getGuild());
108
+ const identifier = guild.emojis.resolveIdentifier(nameOrEmoji);
109
+ return this.findEmoji(guild, identifier);
110
+ };
111
+ async close() {
112
+ await this.client?.destroy();
113
+ }
114
+ deleteMessage = async (payload) => {
115
+ const existingMessage = this.getDestination(payload, this.config.name);
116
+ const channel = await this.getChannel(payload);
117
+ if (existingMessage && channel && 'messages' in channel) {
118
+ try {
119
+ await (await channel.messages.fetch(existingMessage)).delete();
120
+ }
121
+ catch (ex) {
122
+ console.error('unable to delete previous message', ex);
123
+ }
124
+ }
125
+ };
126
+ sendMessage = async (payload) => {
127
+ await this.deleteMessage(payload);
128
+ if (payload.action === 'delete') {
129
+ this.deleteDestination(payload, this.config.name);
130
+ return;
131
+ }
132
+ const guild = await this.getGuild();
133
+ const channel = await this.getChannel(payload);
134
+ if (!channel || !('send' in channel) || channel.isThread()) {
135
+ throw new Error(`failed to find ${this.config.guild}/${payload.channel} nor default channel with ID ${this.config.defaultChannel}`);
136
+ }
137
+ const webhook = await guild.channels.createWebhook({
138
+ avatar: ((!this.config.suppressAvatar) && payload.avatar) || null,
139
+ channel: channel,
140
+ name: `${payload.username} (${payload.source})${payload.action === 'update' ? '*' : ''}`.replace(/discord/gi, "discorɗ"),
141
+ reason: 'bridge bot message'
142
+ });
143
+ const responsePayload = {
144
+ content: await this.insertEmoji(payload.content ?? ''),
145
+ };
146
+ payload.attachments && (responsePayload.files = payload.attachments);
147
+ for (let i = 0; i < 2; i++) {
148
+ try {
149
+ const msg = await webhook.send(responsePayload);
150
+ msg?.id && this.upsert(payload, this.config.name, msg.id);
151
+ break;
152
+ }
153
+ catch (ex) {
154
+ console.error(ex);
155
+ if (!responsePayload.files) {
156
+ break;
157
+ }
158
+ delete responsePayload.files;
159
+ }
160
+ }
161
+ await webhook.delete();
162
+ };
163
+ sendReact = async (payload) => {
164
+ const msgId = payload.source === this.config.name ? payload.originatingId : this.getDestination(payload, this.config.name);
165
+ const channel = await this.getChannel(payload);
166
+ if (!channel || !msgId || !('messages' in channel)) {
167
+ return;
168
+ }
169
+ const msg = await channel.messages.fetch(msgId);
170
+ const emoji = await this.resolveEmoji(payload.emoji);
171
+ if (!emoji) {
172
+ return;
173
+ }
174
+ if (payload.action === 'add') {
175
+ await msg.react(emoji);
176
+ }
177
+ else {
178
+ msg.reactions.cache.find(r => r.me)?.remove();
179
+ }
180
+ };
181
+ }
182
+ //# sourceMappingURL=bot.discord.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bot.discord.js","sourceRoot":"","sources":["../src/bot.discord.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ import type { Guild, GuildChannel } from '@fluxerjs/core';
2
+ import { iBot } from './bot.interface.js';
3
+ import type { MessagePayload, BotConfig, GetDestinationOptions, GetChannelOptions, ReactPayload } from './bot.interface.js';
4
+ import type { DB } from './db.js';
5
+ export declare class FluxerBot extends iBot<Guild, GuildChannel> {
6
+ private client;
7
+ constructor(cfg: BotConfig, db: DB);
8
+ private getClient;
9
+ init(): Promise<void>;
10
+ close(): Promise<void>;
11
+ getGuild: () => Promise<Guild>;
12
+ getChannel: (options: GetChannelOptions) => Promise<GuildChannel | null>;
13
+ findEmoji: (guild: Guild, name?: string | null) => import("@fluxerjs/core").GuildEmoji | undefined;
14
+ resolveEmoji: (nameOrEmoji: string) => Promise<string | import("@fluxerjs/core").GuildEmoji | undefined>;
15
+ deleteMessage: (payload: GetDestinationOptions & GetChannelOptions) => Promise<void>;
16
+ sendMessage: (payload: MessagePayload) => Promise<void>;
17
+ sendReact: (payload: ReactPayload) => Promise<void>;
18
+ }
@@ -0,0 +1,183 @@
1
+ import { Client, Events, ChannelType } from '@fluxerjs/core';
2
+ import { iBot } from './bot.interface.js';
3
+ export class FluxerBot extends iBot {
4
+ client;
5
+ constructor(cfg, db) {
6
+ super(cfg, db);
7
+ }
8
+ getClient() {
9
+ if (this.client) {
10
+ return this.client;
11
+ }
12
+ throw new Error('Bot not initialized!');
13
+ }
14
+ async init() {
15
+ const client = this.client = await Client.fromDiscovery(this.config.server);
16
+ await client.login(this.config.token);
17
+ console.log(`[${this.config.name}] logged in to ${this.config.server}`);
18
+ const handleMessage = async (emsg, newmsg) => {
19
+ const msg = newmsg ? (newmsg.partial ? await newmsg.fetch() : newmsg) : emsg;
20
+ if (!msg || msg.guildId !== this.config.guild || msg.author?.bot || ('webhookId' in msg && msg.webhookId)) {
21
+ return;
22
+ }
23
+ if (newmsg && emsg?.createdAt && this.isEditTooLate(emsg.createdAt)) {
24
+ return;
25
+ }
26
+ const channel = msg.channel;
27
+ const payload = {
28
+ action: newmsg ? 'update' : 'send',
29
+ source: this.config.name,
30
+ voice: channel.type === ChannelType.GuildVoice,
31
+ username: msg.member?.nick ?? msg.author?.username ?? 'UNKNOWN USER',
32
+ channel: channel.name ?? this.config.defaultChannel,
33
+ content: msg?.content,
34
+ originatingId: msg.id
35
+ };
36
+ msg.attachments && (payload.attachments = msg.attachments.map((v, _k) => {
37
+ const att = {
38
+ description: v.description,
39
+ attachment: v.url,
40
+ name: v.filename,
41
+ };
42
+ v.waveform && (att.waveform = v.waveform);
43
+ v.title && (att.title = v.title);
44
+ v.duration && (att.duration = v.duration);
45
+ return att;
46
+ }));
47
+ const avatarUrl = msg.member?.avatarURL() ?? msg.member?.user.avatarURL() ?? msg.author.avatarURL();
48
+ avatarUrl && (payload.avatar = avatarUrl);
49
+ this.emit('message', payload);
50
+ };
51
+ client.on(Events.MessageCreate, handleMessage);
52
+ client.on(Events.MessageUpdate, handleMessage);
53
+ client.on(Events.MessageDelete, e => {
54
+ const channel = e.channel;
55
+ const payload = {
56
+ action: 'delete',
57
+ source: this.config.name,
58
+ voice: channel.type === ChannelType.GuildVoice,
59
+ username: 'N/A',
60
+ channel: channel.name ?? this.config.defaultChannel,
61
+ content: null,
62
+ originatingId: e.id
63
+ };
64
+ this.emit('message', payload);
65
+ });
66
+ const handleReact = (reaction, add) => {
67
+ // as of 2026-11-10 reaction.reaction.count is returned before being decremented
68
+ if ((add ? reaction.reaction.count !== 1 : reaction.reaction.count > 1) || reaction.user.bot) {
69
+ return;
70
+ }
71
+ const channel = reaction.channel;
72
+ const source = this.getSource({
73
+ destination: this.config.name,
74
+ destinationid: reaction.messageId
75
+ });
76
+ const payload = {
77
+ action: add ? 'add' : 'remove',
78
+ source: source?.source ?? this.config.name,
79
+ voice: channel.type === ChannelType.GuildVoice,
80
+ channel: channel.name ?? this.config.defaultChannel,
81
+ originatingId: source?.sourceid ?? reaction.messageId,
82
+ emoji: reaction.emoji.id ? `:${reaction.emoji.name}:` : reaction.emoji.name,
83
+ };
84
+ this.emit('react', payload);
85
+ };
86
+ client.on(Events.MessageReactionAdd, (react) => handleReact(react, true));
87
+ client.on(Events.MessageReactionRemove, (react) => handleReact(react, false));
88
+ }
89
+ async close() {
90
+ await this.client?.destroy();
91
+ }
92
+ getGuild = async () => {
93
+ const client = this.getClient();
94
+ return await client.guilds.fetch(this.config.guild);
95
+ };
96
+ getChannel = async (options) => {
97
+ const guild = await this.getGuild();
98
+ return guild.channels.find(ch => ch.name === options.channel && (ch.type === ChannelType.GuildVoice) === options.voice) ??
99
+ guild.channels.get(this.config.defaultChannel) ?? null;
100
+ };
101
+ findEmoji = (guild, name) => {
102
+ return guild.emojis.find(emoji => emoji.name === name);
103
+ };
104
+ resolveEmoji = async (nameOrEmoji) => {
105
+ if (!nameOrEmoji.startsWith(':')) {
106
+ return nameOrEmoji;
107
+ }
108
+ const identifier = nameOrEmoji.substring(1, nameOrEmoji.length - 1);
109
+ return this.findEmoji(await this.getGuild(), identifier);
110
+ };
111
+ deleteMessage = async (payload) => {
112
+ const existingMessage = this.getDestination(payload, this.config.name);
113
+ const channel = await this.getChannel(payload);
114
+ if (existingMessage && channel) {
115
+ try {
116
+ await (await channel.messages.fetch(existingMessage)).delete();
117
+ }
118
+ catch (ex) {
119
+ console.error('unable to delete previous message', ex);
120
+ }
121
+ }
122
+ };
123
+ sendMessage = async (payload) => {
124
+ await this.deleteMessage(payload);
125
+ if (payload.action === 'delete') {
126
+ this.deleteDestination(payload, this.config.name);
127
+ return;
128
+ }
129
+ const channel = await this.getChannel(payload);
130
+ if (!channel || !('send' in channel) || channel.isThread()) {
131
+ throw new Error(`failed to find ${this.config.guild}/${payload.channel} nor default channel with ID ${this.config.defaultChannel}`);
132
+ }
133
+ const webhook = await channel.createWebhook({
134
+ name: `${payload.username} (${payload.source})${payload.action === 'update' ? '*' : ''}`
135
+ });
136
+ const avatarUrl = (!this.config.suppressAvatar) && payload.avatar;
137
+ const responsePayload = {
138
+ content: await this.insertEmoji(payload.content ?? ''),
139
+ };
140
+ avatarUrl && (responsePayload.avatarUrl = avatarUrl);
141
+ payload.attachments && (responsePayload.files = payload.attachments.map(v => {
142
+ return {
143
+ name: v.name,
144
+ url: v.attachment,
145
+ filename: v.title
146
+ };
147
+ }));
148
+ for (let i = 0; i < 2; i++) {
149
+ try {
150
+ const msg = await webhook.send(responsePayload, true);
151
+ msg?.id && this.upsert(payload, this.config.name, msg.id);
152
+ break;
153
+ }
154
+ catch (ex) {
155
+ console.error(ex);
156
+ if (!responsePayload.files) {
157
+ break;
158
+ }
159
+ delete responsePayload.files;
160
+ }
161
+ }
162
+ await webhook.delete();
163
+ };
164
+ sendReact = async (payload) => {
165
+ const msgId = payload.source === this.config.name ? payload.originatingId : this.getDestination(payload, this.config.name);
166
+ const channel = await this.getChannel(payload);
167
+ if (!channel || !msgId || !('messages' in channel)) {
168
+ return;
169
+ }
170
+ const msg = await channel.messages.fetch(msgId);
171
+ const emoji = await this.resolveEmoji(payload.emoji);
172
+ if (!emoji) {
173
+ return;
174
+ }
175
+ if (payload.action === 'add') {
176
+ await msg.react(emoji);
177
+ }
178
+ else {
179
+ this.client?.user?.id && msg.removeReaction(emoji, this.client.user.id);
180
+ }
181
+ };
182
+ }
183
+ //# sourceMappingURL=bot.fluxer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bot.fluxer.js","sourceRoot":"","sources":["../src/bot.fluxer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,88 @@
1
+ import EventEmitter from 'node:events';
2
+ import type { AttachmentPayload } from 'discord.js';
3
+ import type { DB, ReverseKey } from './db.js';
4
+ export interface BotConfig {
5
+ /**
6
+ * max age for a message before edits stop being propogated (ms).
7
+ * 0 disables propogating edits entirely, -1 removes the expiration period.
8
+ */
9
+ editMaxAge?: number;
10
+ /**
11
+ * If true, do not relay the sender's avatar
12
+ */
13
+ suppressAvatar?: boolean;
14
+ server?: string;
15
+ name: string;
16
+ token: string;
17
+ guild: string;
18
+ /**
19
+ * snowflake for fallback text channel
20
+ */
21
+ defaultChannel: string;
22
+ }
23
+ export type SendPayloadAction = 'send' | 'update' | 'delete';
24
+ export interface MessagePayload {
25
+ action: SendPayloadAction;
26
+ source: string;
27
+ voice: boolean;
28
+ channel: string;
29
+ username: string;
30
+ content: string | null;
31
+ originatingId: string;
32
+ avatar?: string;
33
+ attachments?: AttachmentPayload[];
34
+ }
35
+ export type EmojiPayloadAction = 'add' | 'remove';
36
+ export interface ReactPayload {
37
+ action: EmojiPayloadAction;
38
+ source: string;
39
+ voice: boolean;
40
+ channel: string;
41
+ originatingId: string;
42
+ emoji: `:${string}:` | string;
43
+ }
44
+ export interface GetDestinationOptions {
45
+ source: string;
46
+ originatingId: string;
47
+ }
48
+ export interface GetChannelOptions {
49
+ channel: string;
50
+ voice: boolean;
51
+ }
52
+ export interface EmojiReplacements {
53
+ match: string;
54
+ replacement?: string;
55
+ }
56
+ interface BotEvents {
57
+ message: (message: MessagePayload) => void;
58
+ react: (message: ReactPayload) => void;
59
+ }
60
+ export declare interface iBot<T, U> {
61
+ on<T extends keyof BotEvents>(event: T, listener: BotEvents[T]): this;
62
+ emit<T extends keyof BotEvents>(event: T, ...args: Parameters<BotEvents[T]>): boolean;
63
+ }
64
+ export declare abstract class iBot<T, U> extends EventEmitter {
65
+ protected config: BotConfig;
66
+ protected db: DB;
67
+ constructor(cfg: BotConfig, db: DB);
68
+ abstract init(): Promise<void>;
69
+ abstract getGuild(): Promise<T>;
70
+ abstract getChannel(options: GetChannelOptions): Promise<U | null>;
71
+ abstract deleteMessage(payload: GetDestinationOptions & GetChannelOptions): Promise<void>;
72
+ abstract sendMessage(payload: MessagePayload): Promise<void>;
73
+ abstract sendReact(payload: ReactPayload): Promise<void>;
74
+ abstract findEmoji(guild: T, name?: string | null): {
75
+ name: string;
76
+ id: string;
77
+ animated: boolean;
78
+ } | undefined;
79
+ abstract close(): Promise<void>;
80
+ isEditTooLate: (timestamp: number | Date) => boolean;
81
+ createListeners(bots: iBot<any, any>[]): void;
82
+ insertEmoji: (message: string) => Promise<string>;
83
+ protected upsert: (payload: MessagePayload, destination: string, destinationid: string) => void;
84
+ protected getDestination: (payload: MessagePayload | GetDestinationOptions, destination: string) => string | undefined;
85
+ protected getSource: (query: ReverseKey) => import("./db.js").Message | undefined;
86
+ protected deleteDestination: (payload: MessagePayload | GetDestinationOptions, destination: string) => void;
87
+ }
88
+ export {};
@@ -0,0 +1,90 @@
1
+ import EventEmitter from 'node:events';
2
+ import dayjs from 'dayjs';
3
+ import { notStupidParseInt } from './util.js';
4
+ const EMOJI_CHARS = /[a-zA-Z0-9:_]/;
5
+ const DEFAULT_MAX_EDIT_AGE = notStupidParseInt(process.env['EDIT_MAX_AGE'] ?? 1000 * 60 * 2);
6
+ export class iBot extends EventEmitter {
7
+ config;
8
+ db;
9
+ constructor(cfg, db) {
10
+ super();
11
+ this.config = cfg;
12
+ this.db = db;
13
+ }
14
+ isEditTooLate = (timestamp) => {
15
+ const expiration = this.config.editMaxAge ?? DEFAULT_MAX_EDIT_AGE;
16
+ if (expiration <= 0) {
17
+ return expiration === 0;
18
+ }
19
+ return dayjs(timestamp).add(expiration, 'ms').isBefore(dayjs());
20
+ };
21
+ createListeners(bots) {
22
+ bots.forEach(b => {
23
+ if (b === this) {
24
+ return;
25
+ }
26
+ b.on('message', this.sendMessage);
27
+ b.on('react', this.sendReact);
28
+ });
29
+ }
30
+ insertEmoji = async (message) => {
31
+ const guild = await this.getGuild();
32
+ const emojiMatches = [];
33
+ let startStr = -1;
34
+ message.split('').forEach((ch, i) => {
35
+ if (ch === '>' && startStr >= 0) {
36
+ const match = message.substring(startStr, i + 1);
37
+ if (!emojiMatches.some(m => m.match === match)) {
38
+ emojiMatches.push({ match });
39
+ }
40
+ startStr = -1;
41
+ return;
42
+ }
43
+ if (ch === '<' && startStr === -1) {
44
+ startStr = i;
45
+ return;
46
+ }
47
+ if (startStr === -1) {
48
+ return;
49
+ }
50
+ if (!EMOJI_CHARS.test(ch)) {
51
+ startStr = -1;
52
+ }
53
+ });
54
+ emojiMatches.forEach(m => {
55
+ const tokens = m.match.substring(1, m.match.length - 1).split(':');
56
+ if (tokens.length !== 3) {
57
+ return;
58
+ }
59
+ const emoji = this.findEmoji(guild, tokens[1]);
60
+ m.replacement = emoji ? `${emoji.animated ? '<a' : '<'}:${emoji.name}:${emoji.id}>` : `:${tokens[1]}:`;
61
+ });
62
+ return emojiMatches.reduce((accumulator, m) => m.replacement ? accumulator.replaceAll(m.match, m.replacement) : accumulator, message);
63
+ };
64
+ upsert = (payload, destination, destinationid) => {
65
+ this.db.upsert({
66
+ source: payload.source,
67
+ sourceid: payload.originatingId,
68
+ destination,
69
+ destinationid
70
+ });
71
+ };
72
+ getDestination = (payload, destination) => {
73
+ return this.db.getMessage({
74
+ source: payload.source,
75
+ sourceid: payload.originatingId,
76
+ destination,
77
+ });
78
+ };
79
+ getSource = (query) => {
80
+ return this.db.getSource(query);
81
+ };
82
+ deleteDestination = (payload, destination) => {
83
+ return this.db.deleteMessage({
84
+ source: payload.source,
85
+ sourceid: payload.originatingId,
86
+ destination
87
+ });
88
+ };
89
+ }
90
+ //# sourceMappingURL=bot.interface.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bot.interface.js","sourceRoot":"","sources":["../src/bot.interface.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import { getRestOptions, Service } from './rest.js';
2
+ import { DiscordBot } from './bot.discord.js';
3
+ import { FluxerBot } from './bot.fluxer.js';
4
+ export async function createBot(cfg, db) {
5
+ const endpoints = await getRestOptions(cfg.server);
6
+ if (endpoints.type === Service.Discord) {
7
+ return new DiscordBot(cfg, db);
8
+ }
9
+ return new FluxerBot(cfg, db);
10
+ }
11
+ //# sourceMappingURL=bot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bot.js","sourceRoot":"","sources":["../src/bot.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAI5C,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,GAAc,EAAE,EAAM;IAClD,MAAM,SAAS,GAAG,MAAM,cAAc,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACnD,IAAI,SAAS,CAAC,IAAI,KAAK,OAAO,CAAC,OAAO,EAAE,CAAC;QACrC,OAAO,IAAI,UAAU,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;IACnC,CAAC;IACD,OAAO,IAAI,SAAS,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;AAClC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { type Database } from 'better-sqlite3';
2
+ export declare const TABLE = "packages";
3
+ export declare function migrate(db: Database): void;
@@ -0,0 +1,20 @@
1
+ export const TABLE = 'packages';
2
+ export function migrate(db) {
3
+ const migrations = [
4
+ [
5
+ 'CREATE TABLE IF NOT EXISTS messages (source VARCHAR(128), sourceid VARCHAR(128), destination VARCHAR(128), destinationid VARCHAR(128), PRIMARY KEY (source, sourceid, destination));',
6
+ 'CREATE INDEX IF NOT EXISTS reverse_index ON messages (destination, destinationid)'
7
+ ],
8
+ ];
9
+ const version = db.pragma('user_version')[0]?.user_version ?? 0;
10
+ const migrationsTodo = migrations.slice(version);
11
+ const runMigration = db.transaction((migrations) => {
12
+ migrations.forEach(m => db.exec(m));
13
+ });
14
+ migrationsTodo.forEach((m, i) => {
15
+ console.log(`Running DB migration ${i}`);
16
+ runMigration(m);
17
+ });
18
+ db.pragma(`user_version=${migrations.length}`);
19
+ }
20
+ //# sourceMappingURL=db-migrations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db-migrations.js","sourceRoot":"","sources":["../src/db-migrations.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,KAAK,GAAG,UAAU,CAAC;AAEhC,MAAM,UAAU,OAAO,CAAC,EAAY;IAChC,MAAM,UAAU,GAAe;QAC3B;YACI,sLAAsL;YACtL,mFAAmF;SACtF;KACJ,CAAC;IAEF,MAAM,OAAO,GAAI,EAAE,CAAC,MAAM,CAAC,cAAc,CAAkC,CAAC,CAAC,CAAC,EAAE,YAAY,IAAI,CAAC,CAAC;IAClG,MAAM,cAAc,GAAG,UAAU,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACjD,MAAM,YAAY,GAAG,EAAE,CAAC,WAAW,CAAC,CAAC,UAAoB,EAAE,EAAE;QACzD,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IACxC,CAAC,CAAC,CAAC;IACH,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QAC5B,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,EAAE,CAAC,CAAC;QACzC,YAAY,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,MAAM,CAAC,gBAAgB,UAAU,CAAC,MAAM,EAAE,CAAC,CAAC;AACnD,CAAC"}
@@ -0,0 +1,26 @@
1
+ export interface DBConfig {
2
+ file: string;
3
+ skipMigrations: boolean;
4
+ }
5
+ export interface MessageKey {
6
+ source: string;
7
+ sourceid: string;
8
+ destination: string;
9
+ }
10
+ export interface MessageDestinationId {
11
+ destinationid: string;
12
+ }
13
+ export interface ReverseKey {
14
+ destination: string;
15
+ destinationid: string;
16
+ }
17
+ export type Message = MessageDestinationId & MessageKey;
18
+ export declare class DB {
19
+ private db;
20
+ constructor(cfg: Partial<DBConfig>);
21
+ getMessage: (query: MessageKey) => string | undefined;
22
+ getSource: (query: ReverseKey) => Message | undefined;
23
+ upsert: (data: Message) => void;
24
+ deleteMessage: (key: MessageKey) => void;
25
+ close: () => void;
26
+ }
@@ -0,0 +1,30 @@
1
+ import path from 'path';
2
+ import Database from 'better-sqlite3';
3
+ import { migrate } from './db-migrations.js';
4
+ export class DB {
5
+ db;
6
+ constructor(cfg) {
7
+ const db = this.db = new Database(cfg.file ?? path.join('db', 'messages.db'));
8
+ cfg.skipMigrations || migrate(db);
9
+ }
10
+ getMessage = (query) => {
11
+ const statement = this.db.prepare('SELECT destinationid FROM messages WHERE source = @source AND sourceid = @sourceid AND destination = @destination;');
12
+ return statement.get(query)?.destinationid;
13
+ };
14
+ getSource = (query) => {
15
+ const statement = this.db.prepare('SELECT * FROM messages WHERE destination = @destination AND destinationid = @destinationid;');
16
+ return statement.get(query);
17
+ };
18
+ upsert = (data) => {
19
+ const statement = this.db.prepare('INSERT INTO messages (source, sourceid, destination, destinationid) VALUES(@source, @sourceid, @destination, @destinationid) ON CONFLICT(source, sourceid, destination) DO UPDATE SET destinationid=excluded.destinationid');
20
+ statement.run(data);
21
+ };
22
+ deleteMessage = (key) => {
23
+ const statement = this.db.prepare('DELETE FROM messages WHERE source = @source AND sourceid = @sourceid AND destination = @destination;');
24
+ statement.run(key);
25
+ };
26
+ close = () => {
27
+ this.db.close();
28
+ };
29
+ }
30
+ //# sourceMappingURL=db.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db.js","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AACtC,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAwB7C,MAAM,OAAO,EAAE;IACH,EAAE,CAAoB;IAE9B,YAAY,GAAsB;QAC9B,MAAM,EAAE,GAAG,IAAI,CAAC,EAAE,GAAG,IAAI,QAAQ,CAAC,GAAG,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,aAAa,CAAC,CAAC,CAAC;QAC9E,GAAG,CAAC,cAAc,IAAI,OAAO,CAAC,EAAE,CAAC,CAAC;IACtC,CAAC;IAED,UAAU,GAAG,CAAC,KAAiB,EAAE,EAAE;QAC/B,MAAM,SAAS,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAAmC,oHAAoH,CAAC,CAAC;QAC1L,OAAO,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,aAAa,CAAC;IAC/C,CAAC,CAAA;IAED,SAAS,GAAG,CAAC,KAAiB,EAAE,EAAE;QAC9B,MAAM,SAAS,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAAsB,6FAA6F,CAAC,CAAC;QACtJ,OAAO,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IAChC,CAAC,CAAA;IAED,MAAM,GAAG,CAAC,IAAa,EAAE,EAAE;QACvB,MAAM,SAAS,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CACb,4NAA4N,CAAC,CAAC;QAClP,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACxB,CAAC,CAAA;IAED,aAAa,GAAG,CAAC,GAAe,EAAE,EAAE;QAChC,MAAM,SAAS,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAAmB,sGAAsG,CAAC,CAAC;QAC5J,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACvB,CAAC,CAAA;IAED,KAAK,GAAG,GAAG,EAAE;QACT,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC;IACpB,CAAC,CAAA;CACJ"}
@@ -0,0 +1,7 @@
1
+ import { type DBConfig } from './db.js';
2
+ import type { BotConfig } from './bot.interface.js';
3
+ export interface BridgeConfig {
4
+ bots: BotConfig[];
5
+ db: DBConfig;
6
+ }
7
+ export declare function main(): Promise<void>;
@@ -0,0 +1,30 @@
1
+ import { parse } from 'yaml';
2
+ import fsp from 'fs/promises';
3
+ import path from 'path';
4
+ import { DB } from './db.js';
5
+ import { createBot } from './bot.js';
6
+ async function readYaml(cfg) {
7
+ try {
8
+ const configBody = await fsp.readFile(cfg, { encoding: 'utf-8' });
9
+ return parse(configBody);
10
+ }
11
+ catch (ex) {
12
+ console.error(`Failed to load ${cfg}`);
13
+ throw ex;
14
+ }
15
+ }
16
+ async function initialize() {
17
+ const config = await readYaml(process.env['BRIDGE_CONFIG'] ?? path.join('config', 'config.yaml'));
18
+ const db = new DB(config.db ?? {});
19
+ const bots = await Promise.all((config.bots ?? []).map(botCfg => createBot(botCfg, db)));
20
+ await Promise.all(bots.map(bot => bot.init()));
21
+ bots.forEach(b => b.createListeners(bots));
22
+ return bots;
23
+ }
24
+ export async function main() {
25
+ const bots = await initialize();
26
+ process.on('SIGTERM', () => {
27
+ bots.forEach(b => b.close());
28
+ });
29
+ }
30
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,MAAM,CAAC;AAC7B,OAAO,GAAG,MAAM,aAAa,CAAC;AAC9B,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,EAAE,EAAiB,MAAM,SAAS,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAGrC,KAAK,UAAU,QAAQ,CAAI,GAAW;IAClC,IAAI,CAAC;QACD,MAAM,UAAU,GAAG,MAAM,GAAG,CAAC,QAAQ,CAAC,GAAG,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC;QAClE,OAAO,KAAK,CAAC,UAAU,CAAM,CAAC;IAClC,CAAC;IACD,OAAO,EAAE,EAAE,CAAC;QACR,OAAO,CAAC,KAAK,CAAC,kBAAkB,GAAG,EAAE,CAAC,CAAC;QACvC,MAAM,EAAE,CAAC;IACb,CAAC;AACL,CAAC;AAOD,KAAK,UAAU,UAAU;IACrB,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAe,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAC;IAChH,MAAM,EAAE,GAAG,IAAI,EAAE,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;IACnC,MAAM,IAAI,GAAqB,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,SAAS,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;IAC3G,MAAM,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IAC/C,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC;IAE3C,OAAO,IAAI,CAAC;AAChB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,IAAI;IACtB,MAAM,IAAI,GAAG,MAAM,UAAU,EAAE,CAAC;IAChC,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE;QACvB,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;IACjC,CAAC,CAAC,CAAC;AACP,CAAC"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import { main } from './index.js';
3
+ main();
4
+ //# sourceMappingURL=main.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":";AACA,OAAO,EAAC,IAAI,EAAC,MAAM,YAAY,CAAC;AAEhC,IAAI,EAAE,CAAC"}
@@ -0,0 +1,9 @@
1
+ import type { RESTOptions } from 'discord.js';
2
+ export declare enum Service {
3
+ Discord = 0,
4
+ Fluxer = 1
5
+ }
6
+ export declare function getRestOptions(server?: string): Promise<{
7
+ type: Service;
8
+ rest: Partial<RESTOptions>;
9
+ }>;
@@ -0,0 +1,36 @@
1
+ import ky from 'ky';
2
+ export var Service;
3
+ (function (Service) {
4
+ Service[Service["Discord"] = 0] = "Discord";
5
+ Service[Service["Fluxer"] = 1] = "Fluxer";
6
+ })(Service || (Service = {}));
7
+ async function tryRequest(url) {
8
+ try {
9
+ return await ky.get(url).json();
10
+ }
11
+ catch {
12
+ return false;
13
+ }
14
+ }
15
+ export async function getRestOptions(server) {
16
+ if (!server || server.toLowerCase().startsWith('https://discord.com')) {
17
+ return {
18
+ type: Service.Discord,
19
+ rest: {}
20
+ };
21
+ }
22
+ const result = await tryRequest(new URL('.well-known/fluxer', server).href);
23
+ if (result) {
24
+ return {
25
+ type: Service.Fluxer,
26
+ rest: {
27
+ api: result.endpoints.api,
28
+ cdn: result.endpoints.static_cdn,
29
+ mediaProxy: result.endpoints.media,
30
+ version: `v${result.api_code_version}`
31
+ }
32
+ };
33
+ }
34
+ throw new Error(`Server ${server} does not seem to be supported by djs-bridge at this time.`);
35
+ }
36
+ //# sourceMappingURL=rest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rest.js","sourceRoot":"","sources":["../src/rest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AAqBpB,MAAM,CAAN,IAAY,OAGX;AAHD,WAAY,OAAO;IACf,2CAAO,CAAA;IACP,yCAAM,CAAA;AACV,CAAC,EAHW,OAAO,KAAP,OAAO,QAGlB;AAED,KAAK,UAAU,UAAU,CAAI,GAAW;IACpC,IAAI,CAAC;QACD,OAAO,MAAM,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAA;IACnC,CAAC;IACD,MAAM,CAAC;QACH,OAAO,KAAK,CAAC;IACjB,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,MAAe;IAChD,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,qBAAqB,CAAC,EAAE,CAAC;QACpE,OAAO;YACH,IAAI,EAAE,OAAO,CAAC,OAAO;YACrB,IAAI,EAAE,EAAE;SACX,CAAC;IACN,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,UAAU,CAAkB,IAAI,GAAG,CAAC,oBAAoB,EAAE,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC;IAC7F,IAAI,MAAM,EAAE,CAAC;QACT,OAAO;YACH,IAAI,EAAE,OAAO,CAAC,MAAM;YACpB,IAAI,EAAE;gBACF,GAAG,EAAE,MAAM,CAAC,SAAS,CAAC,GAAG;gBACzB,GAAG,EAAE,MAAM,CAAC,SAAS,CAAC,UAAU;gBAChC,UAAU,EAAE,MAAM,CAAC,SAAS,CAAC,KAAK;gBAClC,OAAO,EAAE,IAAI,MAAM,CAAC,gBAAgB,EAAE;aACzC;SACJ,CAAA;IACL,CAAC;IACD,MAAM,IAAI,KAAK,CAAC,UAAU,MAAM,4DAA4D,CAAC,CAAC;AAClG,CAAC"}
@@ -0,0 +1 @@
1
+ export declare function notStupidParseInt(v: string | undefined | number): number;
@@ -0,0 +1,7 @@
1
+ export function notStupidParseInt(v) {
2
+ if (typeof v === 'number') {
3
+ return v;
4
+ }
5
+ return v === undefined ? NaN : parseInt(v);
6
+ }
7
+ //# sourceMappingURL=util.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,iBAAiB,CAAC,CAA8B;IAC5D,IAAI,OAAO,CAAC,KAAK,QAAQ,EAAE,CAAC;QACxB,OAAO,CAAC,CAAC;IACb,CAAC;IACD,OAAO,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;AAC/C,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,60 @@
1
1
  {
2
2
  "name": "djs-bridge",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.0.1",
4
+ "description": "Yet another Discord ↔️ Fluxer bridge application",
5
+ "keywords": [
6
+ "discord",
7
+ "fluxer",
8
+ "chat"
9
+ ],
10
+ "homepage": "https://codeberg.org/sanin/djs-bridge",
11
+ "bugs": {
12
+ "url": "https://codeberg.org/sanin/djs-bridge/issues"
13
+ },
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://codeberg.org/sanin/djs-bridge.git"
17
+ },
18
+ "license": "MIT",
19
+ "author": "Cory Sanin",
20
+ "type": "module",
21
+ "exports": {
22
+ ".": {
23
+ "types": "./distribution/index.d.ts",
24
+ "import": "./distribution/index.js"
25
+ }
26
+ },
27
+ "main": "./distribution/index.js",
28
+ "types": "./distribution/index.d.ts",
29
+ "bin": {
30
+ "djs-bridge": "./distribution/main.js"
31
+ },
32
+ "files": [
33
+ "distribution"
34
+ ],
35
+ "dependencies": {
36
+ "@fluxerjs/core": "^3.3.0",
37
+ "better-sqlite3": "^13.0.3",
38
+ "dayjs": "^1.11.23",
39
+ "discord.js": "^14.28.1",
40
+ "ky": "^2.2.0",
41
+ "yaml": "^2.9.1"
42
+ },
43
+ "devDependencies": {
44
+ "@sindresorhus/tsconfig": "9.0.0",
45
+ "@types/better-sqlite3": "^9.6.0",
46
+ "@types/node": "^26.6.6",
47
+ "typescript": "7.0.2"
48
+ },
49
+ "devEngines": {
50
+ "packageManager": {
51
+ "name": "pnpm",
52
+ "version": "12.18.0",
53
+ "onFail": "download"
54
+ }
55
+ },
56
+ "scripts": {
57
+ "build": "tsc && chmod +x ./distribution/main.js",
58
+ "start": "node distribution/main.js"
59
+ }
6
60
  }