Adjust the directory structure to prepare for merging into Master.
This commit is contained in:
@@ -0,0 +1,207 @@
|
||||
import EventEmitter from "events"
|
||||
import {
|
||||
ChannelType,
|
||||
ChatInputCommandInteraction,
|
||||
Client,
|
||||
GatewayIntentBits,
|
||||
Interaction,
|
||||
Message,
|
||||
MessageReplyOptions,
|
||||
Partials,
|
||||
REST,
|
||||
Routes,
|
||||
} from "discord.js"
|
||||
import { nanoid } from "nanoid"
|
||||
import _ from "lodash"
|
||||
import base64 from "base64-js"
|
||||
|
||||
import { ExchangeMessageData } from "../types/index.mjs"
|
||||
|
||||
interface BotAppEvents {
|
||||
message_request: (data: ExchangeMessageData) => void
|
||||
}
|
||||
|
||||
declare interface BotApp {
|
||||
on<U extends keyof BotAppEvents>(event: U, cb: BotAppEvents[U]): this
|
||||
off<U extends keyof BotAppEvents>(event: U, cb: BotAppEvents[U]): this
|
||||
emit<U extends keyof BotAppEvents>(
|
||||
event: U,
|
||||
...args: Parameters<BotAppEvents[U]>
|
||||
): boolean
|
||||
}
|
||||
|
||||
class BotApp extends EventEmitter {
|
||||
constructor(clientId: string, token: string) {
|
||||
super()
|
||||
|
||||
this.clientId = clientId
|
||||
this.token = token
|
||||
|
||||
this.init()
|
||||
}
|
||||
|
||||
private clientId: string | undefined
|
||||
|
||||
private token: string | undefined
|
||||
|
||||
private client: Client | undefined
|
||||
|
||||
private cachedMessages: Map<string /* message id */, Message> = new Map()
|
||||
|
||||
private voiceSettings: Map<string /* chat id */, boolean> = new Map()
|
||||
|
||||
private typingTexts = _.range(1, 5).map(n => `_Typing${_.repeat(".", n)}_`)
|
||||
|
||||
private init = () => {
|
||||
const client = new Client({
|
||||
intents: [
|
||||
GatewayIntentBits.Guilds,
|
||||
GatewayIntentBits.GuildMessages,
|
||||
GatewayIntentBits.DirectMessages,
|
||||
GatewayIntentBits.MessageContent,
|
||||
],
|
||||
partials: [Partials.Channel],
|
||||
})
|
||||
|
||||
client.on("ready", () => {
|
||||
console.log(`Logged in as ${client.user?.tag}!`)
|
||||
})
|
||||
|
||||
client.on("messageCreate", this.handleMessageCreate)
|
||||
client.on("interactionCreate", this.handleInteractionCreate)
|
||||
|
||||
this.client = client
|
||||
}
|
||||
|
||||
private handleMessageCreate = async (message: Message) => {
|
||||
console.log("handleMessageCreate", message.author.id)
|
||||
const botId = this.client?.user?.id ?? ""
|
||||
const isExpectedMessage =
|
||||
message.author.id !== botId &&
|
||||
(message.channel.type === ChannelType.DM ||
|
||||
(message.channel.type === ChannelType.GuildText &&
|
||||
message.mentions.has(botId) === true &&
|
||||
message.mentions.everyone === false))
|
||||
|
||||
if (isExpectedMessage === false) {
|
||||
return
|
||||
}
|
||||
|
||||
const msgId = nanoid()
|
||||
const chatId = message.channel.id
|
||||
|
||||
this.cachedMessages.set(msgId, message)
|
||||
this.emit("message_request", {
|
||||
user: {
|
||||
id: `${message.author.id}`,
|
||||
},
|
||||
chat: {
|
||||
id: chatId,
|
||||
},
|
||||
message: {
|
||||
id: msgId,
|
||||
type: "text",
|
||||
content: message.content
|
||||
.replaceAll(/@here/g, "")
|
||||
.replaceAll(/@everyone/g, "")
|
||||
.replaceAll(/\<@.*?\>/g, "")
|
||||
.trim(),
|
||||
},
|
||||
options: {
|
||||
voice: this.voiceSettings.get(chatId) ?? false,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
private handleInteractionCreate = async (interaction: Interaction) => {
|
||||
console.log("handleInteractionCreate", interaction.isChatInputCommand())
|
||||
if (interaction.isChatInputCommand()) {
|
||||
if (interaction.commandName === "reset") {
|
||||
await this.handleCommandReset(interaction)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private handleCommandReset = async (
|
||||
interaction: ChatInputCommandInteraction
|
||||
) => {
|
||||
const channelId = interaction.channel?.id
|
||||
const useId = interaction.user?.id
|
||||
if (channelId === undefined || useId === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
this.emit("message_request", {
|
||||
user: {
|
||||
id: useId,
|
||||
},
|
||||
chat: {
|
||||
id: channelId,
|
||||
},
|
||||
message: {
|
||||
id: nanoid(),
|
||||
type: "clear",
|
||||
content: "",
|
||||
},
|
||||
})
|
||||
await interaction.reply("Done!")
|
||||
}
|
||||
|
||||
replyMessage = async (data: ExchangeMessageData) => {
|
||||
const cachedMessage = this.cachedMessages.get(data.message.id)
|
||||
|
||||
if (cachedMessage === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
const { type, content } = data.message
|
||||
const replyOptions: MessageReplyOptions = {
|
||||
content: "",
|
||||
files: [],
|
||||
}
|
||||
|
||||
if (type === "text") {
|
||||
replyOptions.content = content
|
||||
} else if (type === "image") {
|
||||
replyOptions.files?.push(Buffer.from(base64.toByteArray(content)))
|
||||
} else if (type === "voice") {
|
||||
replyOptions.files?.push({
|
||||
attachment: Buffer.from(base64.toByteArray(content)),
|
||||
name: `${Date.now()}.wav`,
|
||||
})
|
||||
} else if (type === "end") {
|
||||
}
|
||||
|
||||
await cachedMessage.reply(replyOptions)
|
||||
}
|
||||
|
||||
start = async () => {
|
||||
if (
|
||||
this.token === undefined ||
|
||||
this.clientId === undefined ||
|
||||
this.client === undefined
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
const commands = [
|
||||
{
|
||||
name: "reset",
|
||||
description:
|
||||
"Clear the history messages of the current session on the server.",
|
||||
},
|
||||
]
|
||||
const rest = new REST({ version: "10" }).setToken(this.token)
|
||||
|
||||
await rest.put(Routes.applicationCommands(this.clientId), {
|
||||
body: commands,
|
||||
})
|
||||
await this.client.login(this.token)
|
||||
}
|
||||
|
||||
stop = () => {
|
||||
this.client?.destroy()
|
||||
}
|
||||
}
|
||||
|
||||
export default BotApp
|
||||
@@ -0,0 +1,57 @@
|
||||
import dotenv from "dotenv"
|
||||
import path from "path"
|
||||
|
||||
import { ExchangeMessageData } from "../types/index.mjs"
|
||||
import BotApp from "./bot-app.mjs"
|
||||
import WebsocketServer from "./websocket-server.mjs"
|
||||
|
||||
dotenv.config({
|
||||
path: path.resolve(process.cwd(), ".env.local"),
|
||||
})
|
||||
|
||||
class App {
|
||||
constructor() {
|
||||
// @ts-ignore
|
||||
const botApp = new BotApp(process.env.CLIENT_ID, process.env.BOT_TOKEN)
|
||||
const websocketServer = new WebsocketServer(
|
||||
parseInt(process.env.WEBSOCKET_PORT as string, 10)
|
||||
)
|
||||
|
||||
botApp.on("message_request", this.handleBotAppMessageRequest)
|
||||
websocketServer.on("message_response", this.handleWebsocketMessageResponse)
|
||||
|
||||
this.botApp = botApp
|
||||
this.websocketServer = websocketServer
|
||||
}
|
||||
|
||||
private botApp: BotApp
|
||||
|
||||
private websocketServer: WebsocketServer
|
||||
|
||||
private handleBotAppMessageRequest = (data: ExchangeMessageData) => {
|
||||
console.log("handleBotAppMessageRequest", data)
|
||||
this.websocketServer.sendMessageRequest(data)
|
||||
}
|
||||
|
||||
private handleWebsocketMessageResponse = (data: ExchangeMessageData) => {
|
||||
console.log("handleWebsocketMessageResponse", data.message.type)
|
||||
this.botApp.replyMessage(data)
|
||||
}
|
||||
|
||||
start = () => {
|
||||
this.botApp?.start()
|
||||
this.websocketServer?.start()
|
||||
}
|
||||
|
||||
stop = () => {
|
||||
this.botApp?.stop()
|
||||
this.websocketServer?.stop()
|
||||
}
|
||||
}
|
||||
|
||||
process.on("unhandledRejection", (error: Error) => {
|
||||
console.log("unhandledRejection", error.message)
|
||||
})
|
||||
|
||||
const app = new App()
|
||||
app.start()
|
||||
@@ -0,0 +1,78 @@
|
||||
import { createServer, Server as HTTPServer } from "http"
|
||||
import { Server as SocketIOServer, Socket } from "socket.io"
|
||||
import express from "express"
|
||||
import EventEmitter from "events"
|
||||
|
||||
import { ExchangeMessageData } from "../types/index.mjs"
|
||||
|
||||
interface WebsocketServerEvents {
|
||||
message_response: (data: ExchangeMessageData) => void
|
||||
}
|
||||
|
||||
declare interface WebsocketServer {
|
||||
on<U extends keyof WebsocketServerEvents>(
|
||||
event: U,
|
||||
cb: WebsocketServerEvents[U]
|
||||
): this
|
||||
off<U extends keyof WebsocketServerEvents>(
|
||||
event: U,
|
||||
cb: WebsocketServerEvents[U]
|
||||
): this
|
||||
emit<U extends keyof WebsocketServerEvents>(
|
||||
event: U,
|
||||
...args: Parameters<WebsocketServerEvents[U]>
|
||||
): boolean
|
||||
}
|
||||
|
||||
class WebsocketServer extends EventEmitter {
|
||||
constructor(port: number) {
|
||||
super()
|
||||
|
||||
const app = express()
|
||||
const httpServer = createServer(app)
|
||||
const sioServer = new SocketIOServer(httpServer, {
|
||||
maxHttpBufferSize: 50 * 1024 * 1024,
|
||||
pingInterval: 25000,
|
||||
pingTimeout: 20000,
|
||||
})
|
||||
|
||||
sioServer.on("connect", this.handleConnect)
|
||||
|
||||
this.port = port
|
||||
this.httpServer = httpServer
|
||||
this.sioServer = sioServer
|
||||
}
|
||||
|
||||
private port: number
|
||||
|
||||
private httpServer: HTTPServer
|
||||
|
||||
private sioServer: SocketIOServer
|
||||
|
||||
private handleConnect = (socket: Socket) => {
|
||||
console.log("handleConnect", socket.id)
|
||||
|
||||
socket.on("disconnect", (reason: string) => {
|
||||
console.log("socket disconnect", reason, Date.now())
|
||||
})
|
||||
socket.on("chat_message", (data: ExchangeMessageData) => {
|
||||
this.emit("message_response", data)
|
||||
})
|
||||
}
|
||||
|
||||
sendMessageRequest = (data: ExchangeMessageData) => {
|
||||
this.sioServer.emit("chat_message", data)
|
||||
}
|
||||
|
||||
start = () => {
|
||||
this.httpServer.listen(this.port, () => {
|
||||
console.log(`listening on *:${this.port}`)
|
||||
})
|
||||
}
|
||||
|
||||
stop = () => {
|
||||
this.sioServer.close()
|
||||
}
|
||||
}
|
||||
|
||||
export default WebsocketServer
|
||||
@@ -0,0 +1,16 @@
|
||||
export interface ExchangeMessageData {
|
||||
user: {
|
||||
id: string
|
||||
}
|
||||
chat: {
|
||||
id: string
|
||||
}
|
||||
message: {
|
||||
type: "text" | "image" | "voice" | "clear" | "end"
|
||||
content: string
|
||||
id: string
|
||||
}
|
||||
options?: {
|
||||
voice: boolean
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user