initial commit
This commit is contained in:
5
.env.example
Normal file
5
.env.example
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
PREFIX=prefixLike!
|
||||||
|
USERNAME=usernameOfBot
|
||||||
|
ACCESS_TOKEN=oauthAccessToken
|
||||||
|
CHANNELS=channelsOfBot
|
||||||
|
DEVELOPERS=userIdOfDeveloper
|
||||||
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
package-lock.jsom
|
||||||
6
.zed/settings.json
Normal file
6
.zed/settings.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"code_actions_on_format": {
|
||||||
|
"source.organizeImports.biome": true,
|
||||||
|
"source.fixAll.biome": true
|
||||||
|
}
|
||||||
|
}
|
||||||
1289
package-lock.json
generated
Normal file
1289
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
30
package.json
Normal file
30
package.json
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"name": "twitch-bot",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"clean": "rimraf dist",
|
||||||
|
"build": "npm run clean && tsc -p .",
|
||||||
|
"start": "node dist/bot.js",
|
||||||
|
"dev": "nodemon src/bot.ts"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"type": "module",
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^24.5.2",
|
||||||
|
"@types/tmi.js": "^1.8.6",
|
||||||
|
"nodemon": "^3.1.10",
|
||||||
|
"rimraf": "^6.0.1",
|
||||||
|
"ts-node": "^10.9.2",
|
||||||
|
"typescript": "^5.9.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@discordjs/collection": "^2.1.1",
|
||||||
|
"chalk": "^5.6.2",
|
||||||
|
"dotenv": "^17.2.2",
|
||||||
|
"tmi.js": "^1.8.5"
|
||||||
|
}
|
||||||
|
}
|
||||||
13
src/bot.ts
Normal file
13
src/bot.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import type { Events } from "tmi.js";
|
||||||
|
import { client } from "./core/client.ts";
|
||||||
|
import { events } from "./events/collection.ts";
|
||||||
|
|
||||||
|
// Register all events with the TMI client
|
||||||
|
for (const [eventName, eventHandler] of events) {
|
||||||
|
client.on(
|
||||||
|
eventName as keyof Events,
|
||||||
|
eventHandler.triggered.bind(null, client),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
client.connect();
|
||||||
7
src/commands/collection.ts
Normal file
7
src/commands/collection.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { Collection } from "@discordjs/collection";
|
||||||
|
import { SongCommand } from "./command-song.ts";
|
||||||
|
import type { ICommand } from "./interface.ts";
|
||||||
|
|
||||||
|
export const commands = new Collection<string, ICommand>();
|
||||||
|
|
||||||
|
commands.set(SongCommand.name, new SongCommand());
|
||||||
23
src/commands/command-song.ts
Normal file
23
src/commands/command-song.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import type { ChatUserstate, Client } from "tmi.js";
|
||||||
|
import type { ICommand, ICommandRequirements } from "./interface.ts";
|
||||||
|
|
||||||
|
export class SongCommand implements ICommand {
|
||||||
|
name = "song";
|
||||||
|
cooldown = 0;
|
||||||
|
enabled = true;
|
||||||
|
|
||||||
|
requirements: ICommandRequirements = {
|
||||||
|
developer: true,
|
||||||
|
mod: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
triggered = async (
|
||||||
|
client: Client,
|
||||||
|
channel: string,
|
||||||
|
_state: ChatUserstate,
|
||||||
|
_message: string,
|
||||||
|
_args: Array<string>,
|
||||||
|
) => {
|
||||||
|
client.say(channel, "testing");
|
||||||
|
};
|
||||||
|
}
|
||||||
12
src/commands/interface.ts
Normal file
12
src/commands/interface.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
export interface ICommand {
|
||||||
|
name: string;
|
||||||
|
cooldown: number;
|
||||||
|
enabled: boolean;
|
||||||
|
triggered(...args: unknown[]): Promise<unknown>;
|
||||||
|
requirements: ICommandRequirements;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ICommandRequirements {
|
||||||
|
developer: boolean;
|
||||||
|
mod: boolean;
|
||||||
|
}
|
||||||
11
src/config/config.ts
Normal file
11
src/config/config.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import dotenv from "dotenv";
|
||||||
|
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
|
export const Config = {
|
||||||
|
prefix: process.env.PREFIX || "",
|
||||||
|
username: process.env.USERNAME || "",
|
||||||
|
access_token: process.env.ACCESS_TOKEN || "",
|
||||||
|
channels: [process.env.CHANNELS || ""],
|
||||||
|
developers: [process.env.DEVELOPERS || ""],
|
||||||
|
};
|
||||||
11
src/core/client.ts
Normal file
11
src/core/client.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { Client } from "tmi.js";
|
||||||
|
import { Config } from "../config/config.ts";
|
||||||
|
|
||||||
|
export const client = new Client({
|
||||||
|
connection: { reconnect: true, secure: true },
|
||||||
|
identity: {
|
||||||
|
username: Config.username,
|
||||||
|
password: `oauth:${Config.access_token}`,
|
||||||
|
},
|
||||||
|
channels: Config.channels,
|
||||||
|
});
|
||||||
9
src/events/collection.ts
Normal file
9
src/events/collection.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { Collection } from "@discordjs/collection";
|
||||||
|
import ConnectedEvent from "./event-connected.ts";
|
||||||
|
import MessageEvent from "./event-message.ts";
|
||||||
|
import type { IEvent } from "./interface.ts";
|
||||||
|
|
||||||
|
export const events = new Collection<string, IEvent>();
|
||||||
|
|
||||||
|
events.set(ConnectedEvent.name, new ConnectedEvent());
|
||||||
|
events.set(MessageEvent.name, new MessageEvent());
|
||||||
11
src/events/event-connected.ts
Normal file
11
src/events/event-connected.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import type { Events } from "tmi.js";
|
||||||
|
import { logSuccess } from "../logger/logger.ts";
|
||||||
|
import type { IEvent } from "./interface.ts";
|
||||||
|
|
||||||
|
export default class ConnectedEvent implements IEvent {
|
||||||
|
name: keyof Events = "connected";
|
||||||
|
|
||||||
|
triggered = async () => {
|
||||||
|
logSuccess("connected");
|
||||||
|
};
|
||||||
|
}
|
||||||
78
src/events/event-message.ts
Normal file
78
src/events/event-message.ts
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import { Collection } from "@discordjs/collection";
|
||||||
|
import type { ChatUserstate, Client, Events } from "tmi.js";
|
||||||
|
import { commands } from "../commands/collection.ts";
|
||||||
|
import type { ICommand } from "../commands/interface.ts";
|
||||||
|
import { Config } from "../config/config.ts";
|
||||||
|
import type { IEvent } from "./interface.ts";
|
||||||
|
|
||||||
|
const Cooldowns = new Collection<string, number>();
|
||||||
|
|
||||||
|
export default class MessageEvent implements IEvent {
|
||||||
|
name: keyof Events = "message";
|
||||||
|
|
||||||
|
triggered = async (
|
||||||
|
client: Client,
|
||||||
|
channel: string,
|
||||||
|
state: ChatUserstate,
|
||||||
|
message: string,
|
||||||
|
self: boolean,
|
||||||
|
) => {
|
||||||
|
if (self) return;
|
||||||
|
await checkMessage(client, channel, state, message);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkMessage(
|
||||||
|
client: Client,
|
||||||
|
channel: string,
|
||||||
|
state: ChatUserstate,
|
||||||
|
message: string,
|
||||||
|
) {
|
||||||
|
const prefix = Config.prefix;
|
||||||
|
|
||||||
|
if (!message.startsWith(prefix)) return;
|
||||||
|
|
||||||
|
const args = message.slice(prefix.length).trim().split(/ +/g);
|
||||||
|
const commandName = args[0].toLowerCase();
|
||||||
|
const command = commands.get(commandName);
|
||||||
|
if (!command) return;
|
||||||
|
if (!command.enabled) return;
|
||||||
|
|
||||||
|
const userId = state["user-id"] as string;
|
||||||
|
if (command.requirements.developer && !isDeveloper(userId)) return;
|
||||||
|
if (command.requirements.mod && !isMod(state)) return;
|
||||||
|
|
||||||
|
const timeLeft = checkCooldown(command);
|
||||||
|
if (timeLeft > 0) {
|
||||||
|
return client.say(
|
||||||
|
channel,
|
||||||
|
`you must wait ${timeLeft} more seconds to use the command again`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await command.triggered(client, channel, state, message, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isDeveloper(userId: string): boolean {
|
||||||
|
return Config.developers.includes(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isMod(state: ChatUserstate): boolean {
|
||||||
|
return state.mod as boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkCooldown(command: ICommand): number {
|
||||||
|
const now = Date.now();
|
||||||
|
if (command.cooldown > 0) {
|
||||||
|
const cooldownTime = Cooldowns.get(command.name);
|
||||||
|
if (cooldownTime) {
|
||||||
|
if (cooldownTime < now) {
|
||||||
|
const timeLeft = 0; // TODO
|
||||||
|
return timeLeft;
|
||||||
|
} else {
|
||||||
|
Cooldowns.set(command.name, now + command.cooldown * 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
6
src/events/interface.ts
Normal file
6
src/events/interface.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import type { Events } from "tmi.js";
|
||||||
|
|
||||||
|
export interface IEvent {
|
||||||
|
name: keyof Events;
|
||||||
|
triggered(...args: unknown[]): Promise<unknown>;
|
||||||
|
}
|
||||||
13
src/logger/logger.ts
Normal file
13
src/logger/logger.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import chalk from "chalk";
|
||||||
|
|
||||||
|
export function logError(...args: unknown[]) {
|
||||||
|
console.log(chalk.red(args));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function logSuccess(...args: unknown[]) {
|
||||||
|
console.log(chalk.green(args));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function logInfo(...args: unknown[]) {
|
||||||
|
console.log(chalk.cyan(args));
|
||||||
|
}
|
||||||
23
tsconfig.json
Normal file
23
tsconfig.json
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/tsconfig",
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Node",
|
||||||
|
"strict": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"outDir": "./dist",
|
||||||
|
"rootDir": "./src",
|
||||||
|
"noEmit": true,
|
||||||
|
"types": ["node"],
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"incremental": true
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts"],
|
||||||
|
"exclude": ["node_modules", "dist"]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user