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";
|
import type { Client, Message } from "discord.js";
|
||||||
|
|
||||||
export default class Chat {
|
export default class Chat {
|
||||||
private commands: any[] = [];
|
|
||||||
private prefix: string = "!";
|
private prefix: string = "!";
|
||||||
constructor(private client: Client) {}
|
constructor(private client: Client, private commands: ICommand[] = []) {}
|
||||||
|
|
||||||
public registerPrefix = (prefix: string): void => {
|
public registerPrefix = (prefix: string): void => {
|
||||||
this.prefix = prefix;
|
this.prefix = prefix;
|
||||||
};
|
};
|
||||||
|
|
||||||
public register = (token: string): void => {
|
public register = (token: string): void => {
|
||||||
|
if (!this.commands) return;
|
||||||
this.client.on("message", (message: Message): void => {
|
this.client.on("message", (message: Message): void => {
|
||||||
this.commands.forEach((command) => {
|
this.commands.forEach((command) => {
|
||||||
if (message?.content === `${this.prefix}${command?.name}`) {
|
if (message?.content === `${this.prefix}${command?.name}`) {
|
||||||
command?.callback?.(message);
|
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
|
const args = message?.content
|
||||||
?.replace?.(`${this.prefix}${command?.name}`, "")
|
?.replace?.(`${this.prefix}${command?.name}`, "")
|
||||||
.trim?.()
|
.trim?.()
|
||||||
@@ -33,7 +36,7 @@ export default class Chat {
|
|||||||
this.client.login(token);
|
this.client.login(token);
|
||||||
};
|
};
|
||||||
|
|
||||||
public command = (name: string, callback: Function): void => {
|
public command = (name: string, callback: CommandFunction): void => {
|
||||||
this.commands = [
|
this.commands = [
|
||||||
...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 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