restructure and expressjs endpoints
This commit is contained in:
15
app.ts
15
app.ts
@@ -1,15 +0,0 @@
|
||||
import { Client } from "discord.js";
|
||||
import Chat from "./common/chat";
|
||||
import { config as dotenv } from "dotenv";
|
||||
import { Controller } from "./core";
|
||||
import { ClientController } from "./controllers";
|
||||
|
||||
dotenv();
|
||||
|
||||
const client: Client = new Client();
|
||||
const chat: Chat = new Chat(client);
|
||||
|
||||
const controllers = new Controller(new ClientController(client));
|
||||
|
||||
controllers.register();
|
||||
chat.register(process.env.DEV_TOKEN || "");
|
||||
@@ -1,33 +0,0 @@
|
||||
import { readFileSync } from "fs";
|
||||
|
||||
export default function getSettings(environment: string) {
|
||||
let _returnValue = null;
|
||||
if (environment === "development") {
|
||||
try {
|
||||
_returnValue = safelyJsonParse(readFileSync("./.configs/development/config.json", "utf-8"));
|
||||
} catch (err) {
|
||||
_returnValue = null;
|
||||
}
|
||||
} else if (environment === "testing") {
|
||||
try {
|
||||
_returnValue = safelyJsonParse(readFileSync("./.configs/testing/config.json", "utf-8"));
|
||||
} catch (err) {
|
||||
_returnValue = null;
|
||||
}
|
||||
} else if (environment === "production") {
|
||||
try {
|
||||
_returnValue = safelyJsonParse(readFileSync("./.configs/production/config.json", "utf-8"));
|
||||
} catch (err) {
|
||||
_returnValue = null;
|
||||
}
|
||||
}
|
||||
return _returnValue;
|
||||
}
|
||||
|
||||
function safelyJsonParse(data: any) {
|
||||
try {
|
||||
return JSON.parse(data);
|
||||
} catch (err) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
export { default as Chat } from "./chat";
|
||||
export { default as getSettings } from "./getSettings";
|
||||
@@ -1,3 +0,0 @@
|
||||
export const ENVIRONMENTS = ["development", "testing", "production"];
|
||||
export const ENVIRONMENT =
|
||||
ENVIRONMENTS.filter((env) => process?.argv?.includes?.(`--${env}`))?.[0] || "development";
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from "./environments";
|
||||
export * from "./version";
|
||||
@@ -1,3 +0,0 @@
|
||||
import { version } from "../package.json";
|
||||
|
||||
export const APP_VERSION = version;
|
||||
@@ -1,59 +0,0 @@
|
||||
import { Client, Message, MessageEmbed, TextChannel } from "discord.js";
|
||||
import * as cron from "node-cron";
|
||||
import axios from "axios";
|
||||
import cheerio from "cheerio";
|
||||
|
||||
class ClientController {
|
||||
constructor(private client: Client) {}
|
||||
|
||||
public register = (): void => {
|
||||
this.client.on("ready", (): void => {
|
||||
cron.schedule("0 9 * * *", this.sendMessage);
|
||||
});
|
||||
};
|
||||
|
||||
private sendMessage = async (): Promise<void> => {
|
||||
try {
|
||||
const href = await getFirstHtml();
|
||||
const { img, title } = await getImgTitle(href);
|
||||
|
||||
this.client.channels.cache.forEach(async (channel) => {
|
||||
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) {
|
||||
await msg.react(reaction);
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
type Legica = {
|
||||
img: string;
|
||||
title: string;
|
||||
};
|
||||
|
||||
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,45 +0,0 @@
|
||||
import { Client, Message, MessageEmbed } from "discord.js";
|
||||
import axios from "axios";
|
||||
import * as puppeteer from "puppeteer";
|
||||
|
||||
class PuleController {
|
||||
constructor(private client: Client) {}
|
||||
|
||||
public register = (): void => {
|
||||
this.client.on("ready", (): void => {
|
||||
this.sendMessage();
|
||||
});
|
||||
};
|
||||
|
||||
private sendMessage = async (): Promise<void> => {
|
||||
const href = await getFirstHtml();
|
||||
const user = await this.client.users.fetch("329236932309680128");
|
||||
const dm = await user.createDM();
|
||||
const embeddedMessage = new MessageEmbed().setTitle("Nibba").setImage(href || "");
|
||||
const msg = await dm.send(embeddedMessage);
|
||||
};
|
||||
}
|
||||
|
||||
async function getFirstHtml(): Promise<string | undefined> {
|
||||
const browser = await puppeteer.launch();
|
||||
const page = await browser.newPage();
|
||||
await page.goto(
|
||||
"https://duckduckgo.com/?q=black+guy&t=newext&atb=v315-4&iar=images&iax=images&ia=images"
|
||||
);
|
||||
await page.waitForSelector(".tile.tile--img.has-detail", { timeout: 10000 });
|
||||
|
||||
const body = await page.evaluate(() => {
|
||||
function randomIntFromInterval(min: number, max: number): number {
|
||||
return Math.floor(Math.random() * (max - min + 1) + min);
|
||||
}
|
||||
const randNum = randomIntFromInterval(1, 25);
|
||||
return document.querySelectorAll(".tile.tile--img.has-detail")[randNum].querySelector("img")
|
||||
?.src;
|
||||
});
|
||||
|
||||
await browser.close();
|
||||
|
||||
return body;
|
||||
}
|
||||
|
||||
export default PuleController;
|
||||
@@ -1,14 +0,0 @@
|
||||
class Controller {
|
||||
private controllers: any[];
|
||||
constructor(...args: any[]) {
|
||||
this.controllers = [...args];
|
||||
}
|
||||
|
||||
public register = (): void => {
|
||||
this?.controllers?.forEach?.((controller) => {
|
||||
controller.register();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export default Controller;
|
||||
12
process-env.d.ts
vendored
Normal file
12
process-env.d.ts
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
declare global {
|
||||
namespace NodeJS {
|
||||
interface ProcessEnv {
|
||||
TOKEN: string;
|
||||
PORT: string;
|
||||
CRON_LEGICA: string;
|
||||
PASSWORD: string;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
||||
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);
|
||||
@@ -1,20 +1,23 @@
|
||||
import { CommandFunction, ICommand } from "@models";
|
||||
import type { Client, Message } from "discord.js";
|
||||
|
||||
export default class Chat {
|
||||
private commands: any[] = [];
|
||||
private prefix: string = "!";
|
||||
constructor(private client: Client) {}
|
||||
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}`) {
|
||||
} else if (
|
||||
message?.content?.split?.(/\s/g)?.[0] == `${this.prefix}${command?.name}`
|
||||
) {
|
||||
const args = message?.content
|
||||
?.replace?.(`${this.prefix}${command?.name}`, "")
|
||||
.trim?.()
|
||||
@@ -33,7 +36,7 @@ export default class Chat {
|
||||
this.client.login(token);
|
||||
};
|
||||
|
||||
public command = (name: string, callback: Function): void => {
|
||||
public command = (name: string, callback: CommandFunction): void => {
|
||||
this.commands = [
|
||||
...this.commands,
|
||||
{
|
||||
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,2 +1 @@
|
||||
export { default as ClientController } from "./Client.controller";
|
||||
export { default as PuleController } from "./Pule.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;
|
||||
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";
|
||||
Reference in New Issue
Block a user