restructure and expressjs endpoints
This commit is contained in:
28
src/app.ts
Normal file
28
src/app.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { Client } from "discord.js";
|
||||
import { Chat } from "@common";
|
||||
import { Controller } from "@core";
|
||||
import { ClientController } from "@controllers";
|
||||
import express from "express";
|
||||
import { config } from "@constants";
|
||||
import basicAuth from "express-basic-auth";
|
||||
import bodyParser from "body-parser";
|
||||
|
||||
const client: Client = new Client();
|
||||
const chat: Chat = new Chat(client);
|
||||
const app = express();
|
||||
|
||||
app.use(bodyParser.json());
|
||||
|
||||
app.use(
|
||||
basicAuth({
|
||||
users: {
|
||||
admin: config.PASSWORD,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const controllers = new Controller([new ClientController(client, app)]);
|
||||
|
||||
controllers.register();
|
||||
chat.register(config.TOKEN || "");
|
||||
app.listen(config.PORT);
|
||||
48
src/common/chat.ts
Normal file
48
src/common/chat.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { CommandFunction, ICommand } from "@models";
|
||||
import type { Client, Message } from "discord.js";
|
||||
|
||||
export default class Chat {
|
||||
private prefix: string = "!";
|
||||
constructor(private client: Client, private commands: ICommand[] = []) {}
|
||||
|
||||
public registerPrefix = (prefix: string): void => {
|
||||
this.prefix = prefix;
|
||||
};
|
||||
|
||||
public register = (token: string): void => {
|
||||
if (!this.commands) return;
|
||||
this.client.on("message", (message: Message): void => {
|
||||
this.commands.forEach((command) => {
|
||||
if (message?.content === `${this.prefix}${command?.name}`) {
|
||||
command?.callback?.(message);
|
||||
} else if (
|
||||
message?.content?.split?.(/\s/g)?.[0] == `${this.prefix}${command?.name}`
|
||||
) {
|
||||
const args = message?.content
|
||||
?.replace?.(`${this.prefix}${command?.name}`, "")
|
||||
.trim?.()
|
||||
?.split?.(/\s(?=(?:[^'"`]*(['"`])[^'"`]*\1)*[^'"`]*$)/g)
|
||||
.map((d) => {
|
||||
if (d?.[0] == '"' && d?.[d?.length - 1] == '"') {
|
||||
return d?.substr?.(1)?.slice?.(0, -1);
|
||||
}
|
||||
return d;
|
||||
})
|
||||
.filter((d) => d);
|
||||
command?.callback?.(message, args);
|
||||
}
|
||||
});
|
||||
});
|
||||
this.client.login(token);
|
||||
};
|
||||
|
||||
public command = (name: string, callback: CommandFunction): void => {
|
||||
this.commands = [
|
||||
...this.commands,
|
||||
{
|
||||
name,
|
||||
callback,
|
||||
},
|
||||
];
|
||||
};
|
||||
}
|
||||
1
src/common/index.ts
Normal file
1
src/common/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default as Chat } from "./chat";
|
||||
11
src/constants/config.ts
Normal file
11
src/constants/config.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { config as dotenv } from "dotenv";
|
||||
dotenv();
|
||||
|
||||
const config: NodeJS.ProcessEnv = {
|
||||
TOKEN: process.env.TOKEN,
|
||||
PASSWORD: process.env.PASSWORD,
|
||||
PORT: process.env.PORT || "3000",
|
||||
CRON_LEGICA: process.env.CRON_LEGICA || "0 9 * * *",
|
||||
};
|
||||
|
||||
export { config };
|
||||
2
src/constants/index.ts
Normal file
2
src/constants/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from "./version";
|
||||
export * from "./config";
|
||||
3
src/constants/version.ts
Normal file
3
src/constants/version.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { version } from "../../package.json";
|
||||
|
||||
export const APP_VERSION = version;
|
||||
137
src/controllers/Client.controller.ts
Normal file
137
src/controllers/Client.controller.ts
Normal file
@@ -0,0 +1,137 @@
|
||||
import { Client, MessageEmbed, TextChannel } from "discord.js";
|
||||
import * as cron from "cron";
|
||||
import axios from "axios";
|
||||
import cheerio from "cheerio";
|
||||
import { Express } from "express";
|
||||
import { IController, Legica } from "@models";
|
||||
import { config } from "@constants";
|
||||
|
||||
class ClientController implements IController {
|
||||
private legicaTask: cron.CronJob | null = null;
|
||||
constructor(private client: Client, private app: Express) {}
|
||||
|
||||
public register = (): void => {
|
||||
this.client.on("ready", (): void => {
|
||||
this.legicaTask = new cron.CronJob(
|
||||
config.CRON_LEGICA,
|
||||
this.sendNextMessage,
|
||||
null,
|
||||
true,
|
||||
"utc"
|
||||
);
|
||||
});
|
||||
|
||||
this.app.get("", (_, res) => {
|
||||
res.send(this.legicaTask?.running);
|
||||
});
|
||||
|
||||
this.app.post("/start", (_, res) => {
|
||||
if (this.legicaTask?.running) {
|
||||
res.status(400).send("Task already running.");
|
||||
} else {
|
||||
this.legicaTask?.start();
|
||||
res.send("Task started.");
|
||||
}
|
||||
});
|
||||
|
||||
this.app.post("/stop", (_, res) => {
|
||||
if (!this.legicaTask?.running) {
|
||||
res.status(400).send("Task already stopped.");
|
||||
} else {
|
||||
this.legicaTask.stop();
|
||||
res.send("Task stopped.");
|
||||
}
|
||||
});
|
||||
|
||||
this.app.get("/next", (_, res) => {
|
||||
if (!this.legicaTask?.running) {
|
||||
res.status(400).send("Task is not running.");
|
||||
} else {
|
||||
res.send(this.legicaTask.nextDate().toISO());
|
||||
}
|
||||
});
|
||||
|
||||
this.app.post("/post-next", async (_, res) => {
|
||||
try {
|
||||
await this.sendNextMessage();
|
||||
res.send(true);
|
||||
} catch (err) {
|
||||
res.status(400).send(err);
|
||||
}
|
||||
});
|
||||
|
||||
this.app.post("/post", async (req, res) => {
|
||||
try {
|
||||
const url = req.body.url;
|
||||
await this.sendMessage(url);
|
||||
res.send(true);
|
||||
} catch (err) {
|
||||
res.status(400).send(err);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
private sendNextMessage = async (): Promise<void> => {
|
||||
try {
|
||||
const href = await getFirstHtml();
|
||||
await this.sendMessage(href);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
|
||||
private sendMessage = async (url: string): Promise<void> => {
|
||||
if (!url) return;
|
||||
const { img, title } = await getImgTitle(url);
|
||||
|
||||
this.client.channels.cache.forEach(async (channel) => {
|
||||
try {
|
||||
if (channel.type !== "text") return null;
|
||||
const embeddedMessage = new MessageEmbed().setTitle(title).setImage(img);
|
||||
const msg = await (channel as TextChannel).send(embeddedMessage);
|
||||
const reactions = [
|
||||
"1️⃣",
|
||||
"2️⃣",
|
||||
"3️⃣",
|
||||
"4️⃣",
|
||||
"5️⃣",
|
||||
"6️⃣",
|
||||
"7️⃣",
|
||||
"8️⃣",
|
||||
"9️⃣",
|
||||
"🔟",
|
||||
];
|
||||
for (const reaction of reactions) {
|
||||
try {
|
||||
await msg.react(reaction);
|
||||
} catch {
|
||||
console.error(`Reaction ${reaction} to channel ${channel.id} failed.`);
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
console.error(`Message to channel ${channel.id} failed.`);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
async function getImgTitle(href: string): Promise<Legica> {
|
||||
const response = await axios.get(href);
|
||||
const html = response.data;
|
||||
const $ = cheerio.load(html);
|
||||
|
||||
const title = $(".Article-inner > h1").text();
|
||||
const { src: img } = $(".Article-media > img").attr() || {};
|
||||
|
||||
return { title, img };
|
||||
}
|
||||
|
||||
async function getFirstHtml(): Promise<string> {
|
||||
const response = await axios.get("https://sib.net.hr/legica-dana");
|
||||
const html = response.data;
|
||||
const $ = cheerio.load(html);
|
||||
const { href } = $(".News-link.c-def")?.attr() || {};
|
||||
return href;
|
||||
}
|
||||
|
||||
export default ClientController;
|
||||
1
src/controllers/index.ts
Normal file
1
src/controllers/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default as ClientController } from "./Client.controller";
|
||||
13
src/core/Controller.ts
Normal file
13
src/core/Controller.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { IController } from "models";
|
||||
|
||||
class Controller {
|
||||
constructor(private controllers: IController[]) {}
|
||||
|
||||
public register = (): void => {
|
||||
this.controllers?.forEach((controller) => {
|
||||
controller.register();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export default Controller;
|
||||
1
src/core/index.ts
Normal file
1
src/core/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default as Controller } from "./Controller";
|
||||
8
src/models/Command.ts
Normal file
8
src/models/Command.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { Message } from "discord.js";
|
||||
|
||||
export type CommandFunction = (message: Message, args?: string[]) => void;
|
||||
|
||||
export interface ICommand {
|
||||
callback: CommandFunction;
|
||||
name: string;
|
||||
}
|
||||
3
src/models/Controller.ts
Normal file
3
src/models/Controller.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export interface IController {
|
||||
register(): void;
|
||||
}
|
||||
4
src/models/Legica.ts
Normal file
4
src/models/Legica.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export type Legica = {
|
||||
img: string;
|
||||
title: string;
|
||||
};
|
||||
3
src/models/index.ts
Normal file
3
src/models/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from "./Controller";
|
||||
export * from "./Command";
|
||||
export * from "./Legica";
|
||||
11
src/modules/Common.module.ts
Normal file
11
src/modules/Common.module.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { Message } from "discord.js";
|
||||
import { APP_VERSION } from "../constants";
|
||||
|
||||
class CommonModule {
|
||||
constructor() {}
|
||||
public showVersion = (message: Message): void => {
|
||||
message?.channel?.send?.(`Current version of the Monke BOT is ${APP_VERSION}.`);
|
||||
};
|
||||
}
|
||||
|
||||
export default CommonModule;
|
||||
1
src/modules/index.ts
Normal file
1
src/modules/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default as CommonModule } from "./Common.module";
|
||||
Reference in New Issue
Block a user