Dockerfile
Signed-off-by: adithyagenie <adithyagenie@gmail.com> Update .woodpecker/woodpecker.yml
This commit is contained in:
parent
01df8c87d7
commit
a5d82b03e7
16 changed files with 651 additions and 1316 deletions
13
.woodpecker/woodpecker.yml
Normal file
13
.woodpecker/woodpecker.yml
Normal file
|
@ -0,0 +1,13 @@
|
|||
when:
|
||||
- event: push
|
||||
|
||||
steps:
|
||||
- name: Build & Push
|
||||
image: git.ptr.moe/baalajimaestro/build-runner
|
||||
commands:
|
||||
- /dockerd-entrypoint.sh dockerd 2&> /dev/null &
|
||||
- echo $DOCKER_PASSWORD | docker login git.ptr.moe --username ${CI_REPO_OWNER} --password-stdin
|
||||
- docker build . -t git.ptr.moe/adithyagenie/forceplusplus:latest
|
||||
- docker push git.ptr.moe/adithyagenie/forceplusplus:latest
|
||||
secrets: [ docker_password ]
|
||||
privileged: true
|
37
Dockerfile
Normal file
37
Dockerfile
Normal file
|
@ -0,0 +1,37 @@
|
|||
FROM node:20-alpine as build
|
||||
|
||||
RUN npm install pnpm -g
|
||||
|
||||
RUN mkdir /app && \
|
||||
chown -R node:node /app
|
||||
|
||||
USER node
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
ADD package.json .
|
||||
ADD pnpm-lock.yaml .
|
||||
|
||||
RUN pnpm install --frozen-lockfile
|
||||
|
||||
ADD src ./src
|
||||
ADD tsconfig.json .
|
||||
ADD ecosystem.config.js .
|
||||
|
||||
RUN pnpm run build
|
||||
|
||||
FROM node:20-alpine
|
||||
|
||||
RUN mkdir /app && \
|
||||
chown -R node:node /app
|
||||
|
||||
RUN npm install pm2 -g
|
||||
|
||||
USER node
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=build /app/out ./out/
|
||||
COPY --from=build /app/node_modules ./node_modules/
|
||||
COPY --from=build /app/ecosystem.config.js ./ecosystem.config.js
|
||||
|
||||
CMD ["pm2-runtime", "ecosystem.config.js"]
|
|
@ -1,10 +1,13 @@
|
|||
# Force++ (F++)
|
||||
|
||||
Force++ (F++ for short) is a Telegram bot designed to notify users about upcoming coding contests on popular competitive programming platforms. Currently, the bot supports notifications for LeetCode, Codeforces and CodeChef contests.
|
||||
Force++ (F++ for short) is a Telegram bot designed to notify users about upcoming coding contests on popular competitive
|
||||
programming platforms. Currently, the bot supports notifications for LeetCode, Codeforces and CodeChef contests.
|
||||
|
||||
## ⚠️ Development Status
|
||||
|
||||
**Note: This bot is heavily unstable and highly in development.** Use it at your own risk, and expect frequent changes and potential issues. As of now, it works for a single group or person configured by the chat ID in environment variables.
|
||||
**Note: This bot is heavily unstable and highly in development.** Use it at your own risk, and expect frequent changes
|
||||
and potential issues. As of now, it works for a single group or person configured by the chat ID in environment
|
||||
variables.
|
||||
|
||||
## Installation
|
||||
|
||||
|
|
8
ecosystem.config.js
Normal file
8
ecosystem.config.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
module.exports = [
|
||||
{
|
||||
script: 'out/index.js',
|
||||
name: 'forceplusplus',
|
||||
exec_mode: 'cluster',
|
||||
instances: 1
|
||||
}
|
||||
]
|
|
@ -1,9 +0,0 @@
|
|||
// @ts-check
|
||||
|
||||
import eslint from '@eslint/js';
|
||||
import tseslint from 'typescript-eslint';
|
||||
|
||||
export default tseslint.config(
|
||||
eslint.configs.recommended,
|
||||
...tseslint.configs.recommended,
|
||||
);
|
21
package.json
21
package.json
|
@ -6,29 +6,26 @@
|
|||
"scripts": {
|
||||
"build": "tsc",
|
||||
"start": "node ./out/index.js",
|
||||
"dev": "node --watch ./out/index.js"
|
||||
"dev": "node --watch ./out/index.js",
|
||||
"test": "xo"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "AGPL-3.0",
|
||||
"dependencies": {
|
||||
"grammy": "^1.24.1",
|
||||
"dotenv": "^16.4.5",
|
||||
"node-schedule": "^2.1.1",
|
||||
"@grammyjs/parse-mode": "^1.10.0",
|
||||
"codeforces-api-ts": "^3.0.1",
|
||||
"dotenv": "^16.4.5",
|
||||
"fastify": "^4.27.0",
|
||||
"grammy": "^1.24.1",
|
||||
"leetcode-query": "^1.2.3",
|
||||
"codeforces-api-ts": "^3.0.1"
|
||||
"node-schedule": "^2.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@grammyjs/types": "^3.8.0",
|
||||
"typescript": "^5.4.5",
|
||||
"@types/node-schedule": "^2.1.7",
|
||||
"eslint": "^9.4.0",
|
||||
"typescript-eslint": "^7.12.0",
|
||||
"@eslint/js": "^9.4.0",
|
||||
"@types/eslint__js": "^8.42.3",
|
||||
"@types/node": "^20.14.2",
|
||||
"@types/node-fetch": "^2.6.11"
|
||||
"@types/node-fetch": "^2.6.11",
|
||||
"@types/node-schedule": "^2.1.7",
|
||||
"typescript": "^5.4.5"
|
||||
}
|
||||
}
|
||||
|
|
1811
pnpm-lock.yaml
1811
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
|
@ -10,8 +10,7 @@ export async function cfFetchContests(): Promise<{ name: string; startTime: numb
|
|||
id: o.id,
|
||||
startTime: o.startTimeSeconds as number
|
||||
}));
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
throw new Error("Unable to reach Codeforces API :(");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,12 +30,11 @@ export async function fetchLCContests() {
|
|||
if (resp.ok) {
|
||||
const res = await resp.json() as PastContestsResponse;
|
||||
let contestNames = res.data.pastContests.data.map(o => {
|
||||
return {key: o.titleSlug, name: o.title};
|
||||
return { key: o.titleSlug, name: o.title };
|
||||
});
|
||||
contestNames.sort();
|
||||
return contestNames;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
throw new Error("Can't access LeetCode API :(");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ export async function startserver() {
|
|||
const server = fastify({ logger: false });
|
||||
|
||||
if (process.env.RUN_METHOD === "WEBHOOK") {
|
||||
server.post(`/`, webhookCallback(bot, "fastify", {secretToken: process.env.WEBHOOK_SECRET as string}));
|
||||
server.post(`/`, webhookCallback(bot, "fastify", { secretToken: process.env.WEBHOOK_SECRET as string }));
|
||||
server.setErrorHandler(async(err, req, res) => {
|
||||
console.error(`Encountered error: ${err.name}\nStack trace: ${err.stack}`);
|
||||
await res.status(200).send({});
|
||||
|
@ -18,10 +18,10 @@ export async function startserver() {
|
|||
server.get("/", async() => {
|
||||
return "Force++ bot up and running ^_^";
|
||||
});
|
||||
await server.listen({ port: port });
|
||||
await server.listen({ port: port, host: "0.0.0.0" });
|
||||
console.log("Server up!")
|
||||
if (process.env.RUN_METHOD === "WEBHOOK")
|
||||
await bot.api.setWebhook(`${process.env.WEBHOOK_URL}`, {secret_token: process.env.WEBHOOK_SECRET as string});
|
||||
await bot.api.setWebhook(`${process.env.WEBHOOK_URL}`, { secret_token: process.env.WEBHOOK_SECRET as string });
|
||||
console.log(`Force++ server listening on port ${port}!`);
|
||||
|
||||
return server;
|
||||
|
|
|
@ -11,7 +11,7 @@ export async function botInit() {
|
|||
setCommands(bot);
|
||||
bot.hears(/^\/start/, async(ctx) => await startCommand(ctx));
|
||||
bot.hears(/^\/help/, async(ctx) => await helpCommand(ctx));
|
||||
bot.hears(/.+/, async(ctx) => await messageSink(ctx));
|
||||
bot.chatType("private").hears(/.+/, async(ctx) => await messageSink(ctx));
|
||||
console.log("*********************");
|
||||
console.log("Force++ has started!");
|
||||
console.log("*********************");
|
||||
|
|
|
@ -13,10 +13,10 @@ Attend the contest here: https://www.codechef.com/contests/`);
|
|||
}
|
||||
|
||||
export async function reminderContestCF(contestName: string, contestID: number, contestTime: Date) {
|
||||
let options: {timeZone: string, timeStyle: "short"} = {
|
||||
timeZone: 'Asia/Calcutta',
|
||||
timeStyle: "short"
|
||||
};
|
||||
let options: { timeZone: string, timeStyle: "short" } = {
|
||||
timeZone: 'Asia/Calcutta',
|
||||
timeStyle: "short"
|
||||
};
|
||||
await bot.api.sendMessage(parseInt(process.env.CHAT_ID as string),
|
||||
`${contestName} at ${new Date(contestTime).toLocaleTimeString('en-US', options)} IST starts in ~10 minutes.
|
||||
Attend the contest here: https://codeforces.com/contests/${contestID}`);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Bot, Context } from "grammy";
|
||||
import { Context } from "grammy";
|
||||
import { myBot } from "../bot";
|
||||
|
||||
export function setCommands(bot: myBot) {
|
||||
|
@ -11,12 +11,14 @@ export function setCommands(bot: myBot) {
|
|||
|
||||
export async function startCommand(ctx: Context) {
|
||||
await ctx.reply(`
|
||||
Hello! 👋 I'm Force++, your coding contest notification bot.
|
||||
🔔 Currently, I'm not open for public use. Stay tuned for future updates.`);
|
||||
Hello! I'm Force++, your coding contest notification bot.
|
||||
|
||||
🔔 Currently, I'm not open for public use. Stay tuned for future updates.`);
|
||||
}
|
||||
|
||||
export async function helpCommand(ctx: Context) {
|
||||
await ctx.reply(`I'm Force++ (F++), a bot designed to give reminders for contests over different competitive coding platforms.
|
||||
await ctx.reply(`
|
||||
I'm Force++ (F++), a bot designed to give reminders for contests over different competitive coding platforms.
|
||||
|
||||
⚠️I'm not <b>available for public use</b> and currently under development. Check out my source code over <a href="https://git.ptr.moe/adithyagenie/forceplusplus">here</a>.
|
||||
Designed by @adithyagenie.`)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { gracefulShutdown, RecurrenceRule, scheduleJob } from "node-schedule";
|
||||
import { RecurrenceRule, scheduleJob } from "node-schedule";
|
||||
import { reminderContestLC } from "../bot/commands/contestRem";
|
||||
import { fetchLCContests } from "../api/lcFetch";
|
||||
|
||||
|
@ -18,7 +18,6 @@ function getNextBiweeklyDate(): Date {
|
|||
}
|
||||
|
||||
|
||||
|
||||
export async function lcSchedule() {
|
||||
const contestNames = await fetchLCContests();
|
||||
const biweekly = contestNames.filter(o =>
|
||||
|
@ -29,7 +28,7 @@ export async function lcSchedule() {
|
|||
);
|
||||
|
||||
if (biweekly.length > 0) {
|
||||
biweekly.sort((a, b) => a > b ? - 1 : 1);
|
||||
biweekly.sort((a, b) => a > b ? -1 : 1);
|
||||
const matches = biweekly[0].key.match(/^biweekly-contest-(\d+)$/);
|
||||
|
||||
const newnum = matches ? parseInt(matches[1]) + 1 : -1;
|
||||
|
@ -39,13 +38,13 @@ export async function lcSchedule() {
|
|||
const date = getNextBiweeklyDate();
|
||||
date.setMinutes(date.getMinutes() - 10);
|
||||
|
||||
scheduleJob(`${newname}`, date, async function (newname: string, newkey: string) {
|
||||
scheduleJob(`${newname}`, date, async function(newname: string, newkey: string) {
|
||||
await reminderContestLC(newname, newkey)
|
||||
}.bind(null, newname, newkey));
|
||||
}
|
||||
|
||||
if (weekly.length > 0) {
|
||||
weekly.sort((a, b) => a > b ? - 1 : 1);
|
||||
weekly.sort((a, b) => a > b ? -1 : 1);
|
||||
const matches = weekly[0].key.match(/^weekly-contest-(\d+)$/);
|
||||
|
||||
const newnum = matches ? parseInt(matches[1]) + 1 : -1;
|
||||
|
@ -59,7 +58,7 @@ export async function lcSchedule() {
|
|||
weeklyJob.second = 0;
|
||||
weeklyJob.tz = "Etc/UTC";
|
||||
|
||||
scheduleJob(`${newname}`, weeklyJob, async function (newname: string, newkey: string) {
|
||||
scheduleJob(`${newname}`, weeklyJob, async function(newname: string, newkey: string) {
|
||||
await reminderContestLC(newname, newkey)
|
||||
}.bind(null, newname, newkey));
|
||||
|
||||
|
|
14
src/index.ts
14
src/index.ts
|
@ -1,11 +1,21 @@
|
|||
import { config } from "dotenv";
|
||||
config();
|
||||
|
||||
import { FastifyInstance } from "fastify";
|
||||
import { startserver } from "./api/server";
|
||||
import { botInit } from "./bot/bot";
|
||||
import { contestScheduler } from "./helpers/scheduler";
|
||||
|
||||
config();
|
||||
|
||||
if (process.env.BOT_TOKEN === undefined ||
|
||||
process.env.RUN_METHOD === undefined ||
|
||||
(process.env.RUN_METHOD !== "POLLING" && process.env.RUN_METHOD !== "WEBHOOK") ||
|
||||
process.env.CHAT_ID === undefined ||
|
||||
(process.env.RUN_METHOD === "WEBHOOK" && (process.env.WEBHOOK_URL === undefined || process.env.WEBHOOK_SECRET === undefined))
|
||||
) {
|
||||
console.error("ENV variables are not set correctly.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
|
||||
export let server: FastifyInstance;
|
||||
startserver().then((s) => {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"outDir": "./out",
|
||||
"target": "ES2017",
|
||||
"target": "ES2021",
|
||||
"module": "commonjs",
|
||||
"allowJs": true,
|
||||
"strict": true
|
||||
|
|
Loading…
Reference in a new issue