Skip to content

Commit

Permalink
Send telegram message when scheduled job completes
Browse files Browse the repository at this point in the history
Improve logging situation
  • Loading branch information
tireymorris committed Jun 22, 2024
1 parent d4f3328 commit 573e79a
Show file tree
Hide file tree
Showing 12 changed files with 145 additions and 113 deletions.
5 changes: 3 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
PORT=1234
DEBUG=true

DEBUG=false
TELEGRAM_BOT_TOKEN=abcd
TELEGRAM_CHAT_ID=efgh
48 changes: 24 additions & 24 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
{
"parser": "@babel/eslint-parser",
"parserOptions": {
"ecmaVersion": 2021,
"sourceType": "module",
"babelOptions": {
"presets": [
"@babel/preset-env",
"@babel/preset-react"
]
}
},
"env": {
"browser": true,
"node": true
},
"plugins": [
"react"
],
"extends": [
"eslint:recommended",
"plugin:react/recommended"
],
"rules": {}
}
"parser": "@babel/eslint-parser",
"parserOptions": {
"ecmaVersion": 2021,
"sourceType": "module",
"babelOptions": {
"presets": ["@babel/preset-env", "@babel/preset-react"]
}
},
"env": {
"browser": true,
"node": true
},
"plugins": ["react", "@typescript-eslint"],
"extends": [
"eslint:recommended",
"plugin:react/recommended",
"plugin:@typescript-eslint/recommended"
],
"rules": {
"@typescript-eslint/explicit-module-boundary-types": "error",
"@typescript-eslint/explicit-function-return-type": "error"
}
}

Binary file modified bun.lockb
Binary file not shown.
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@
"build:css": "unocss \"src/**/*.tsx\" -o public/styles/uno.css",
"css": "unocss --watch \"src/**/*.tsx\" -o public/styles/uno.css",
"db": "bun run src/db.ts",
"dev": "bun install && concurrently --restart-tries=3 \"bun css\" \"nodemon --watch src --ext ts,tsx --exec 'bun run --hot src/server.tsx'\"",
"dev": "bun install && concurrently --restart-tries=3 \"bun css\" \"nodemon --watch src --ext ts,tsx --exec 'bun run --hot src/server.tsx'\" \"bun run src/worker.ts\"",
"prettier": "bunx prettier --write src/ test/ --plugin prettier-plugin-tailwindcss",
"server": "bun run --hot src/server.tsx",
"test": "NODE_ENV=test bun run test"
"test": "NODE_ENV=test bun run test",
"worker": "bun run src/worker.ts"
},
"dependencies": {
"@unocss/preset-web-fonts": "^0.61.0",
Expand All @@ -21,6 +22,7 @@
"zod": "^3.23.5"
},
"devDependencies": {
"@types/bun": "^1.1.5",
"@unocss/cli": "^0.61.0",
"bun-types": "^1.1.8",
"concurrently": "^8.2.1",
Expand Down
16 changes: 0 additions & 16 deletions src/schedule.ts

This file was deleted.

3 changes: 0 additions & 3 deletions src/server.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,6 @@ import {
isCacheValid,
} from "./util/api";

// Triggers long-running processes
import "./schedule";

const app = new Hono();

app.use("/styles/*", serveStatic({ root: "./public/" }));
Expand Down
74 changes: 20 additions & 54 deletions src/util/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Article } from "../types";
import shuffle from "./shuffle";
import { newsSources, NewsSource } from "./newsSources";
import { isValidArticle, insertArticle } from "./articleUtils";
import { debug, log } from "./log";

const generateIdFromTitle = (title: string): string => {
return Bun.hash(title).toString();
Expand All @@ -12,16 +13,12 @@ const generateIdFromTitle = (title: string): string => {
const fetchArticlesFromSource = async (
source: NewsSource,
): Promise<Article[]> => {
if (process.env["DEBUG"] === "true") {
console.log(`*** Fetching articles from: ${source.name}`);
}
log(`Fetching articles from: ${source.name}`);

const response = await fetch(source.url);
const text = await response.text();

if (process.env["DEBUG"] === "true") {
console.log(`*** FETCHING: ${source.name}`);
}
debug(`FETCHING: ${source.name}`);
const $ = load(text);
const articles: Article[] = [];

Expand All @@ -41,56 +38,38 @@ const fetchArticlesFromSource = async (
created_at: new Date().toISOString(),
};
if (!isValidArticle(article)) {
if (process.env["DEBUG"] === "true") {
console.log(`*** INVALID: ${source.name}: ${title} ${link}`);
}
debug(`*** INVALID: ${source.name}: ${title} ${link}`);
} else {
articles.push(article);
if (process.env["DEBUG"] === "true") {
console.log(`*** VALID: ${source.name}: ${title} ${link}`);
}
debug(`*** VALID: ${source.name}: ${title} ${link}`);
}
} else {
if (process.env["DEBUG"] === "true") {
console.log(
`*** MISSING INFO: ${source.name}: ${title} ${relativeLink}`,
);
}
debug(`*** MISSING INFO: ${source.name}: ${title} ${relativeLink}`);
}
});

if (process.env["DEBUG"] === "true") {
console.log(`*** Fetched ${articles.length} articles from: ${source.name}`);
}
debug(`*** Fetched ${articles.length} articles from: ${source.name}`);

return articles;
};

// Fetch articles from all sources
const fetchAllArticles = async (): Promise<Article[]> => {
const allArticles: Article[] = [];

for (const source of newsSources) {
if (process.env["DEBUG"] === "true") {
console.log(`*** Fetching articles from all sources`);
}
const fetchedArticles = await fetchArticlesFromSource(source);
allArticles.push(...fetchedArticles);
}

shuffle(allArticles);

if (process.env["DEBUG"] === "true") {
console.log(`*** Total articles fetched: ${allArticles.length}`);
}
log(`Total articles fetched: ${allArticles.length}`);

return allArticles;
};

const insertArticles = (articles: Article[]) => {
if (process.env["DEBUG"] === "true") {
console.log(`*** Inserting ${articles.length} articles into the database`);
}
log(`*** Inserting ${articles.length} articles into the database`);
articles.forEach(insertArticle);
};

Expand All @@ -99,11 +78,9 @@ const getCachedArticles = (
limit: number,
excludedIds: string[] = [],
): Article[] => {
if (process.env["DEBUG"] === "true") {
console.log(
`*** Getting cached articles with offset: ${offset}, limit: ${limit}, excluding IDs: ${excludedIds.join(", ")}`,
);
}
debug(
`Getting cached articles with offset: ${offset}, limit: ${limit}, excluding IDs: ${excludedIds.join(", ")}`,
);
const articles = db
.prepare(
"SELECT * FROM articles WHERE id NOT IN ('" +
Expand All @@ -112,22 +89,17 @@ const getCachedArticles = (
)
.all(limit, offset) as Article[];

if (process.env["DEBUG"] === "true") {
console.log(`*** Retrieved ${articles.length} cached articles`);
}
debug(`*** Retrieved ${articles.length} cached articles`);

return articles;
};

const fetchAndStoreArticles = async () => {
if (process.env["DEBUG"] === "true") {
console.log(`*** Fetching and storing articles`);
}
const fetchAndStoreArticles = async (): Promise<Article[]> => {
debug(`Fetching and storing articles`);
const allArticles = await fetchAllArticles();
insertArticles(allArticles);
if (process.env["DEBUG"] === "true") {
console.log(`*** Articles fetched and stored successfully`);
}
const insertedArticles = allArticles.filter(insertArticle);

return insertedArticles;
};

const isCacheValid = (): boolean => {
Expand All @@ -141,18 +113,12 @@ const isCacheValid = (): boolean => {
const hoursDifference =
(now.getTime() - articleDate.getTime()) / (1000 * 60 * 60);

if (process.env["DEBUG"] === "true") {
console.log(
`*** Cache validity checked. Hours difference: ${hoursDifference}`,
);
}
debug(`Cache validity checked. Hours difference: ${hoursDifference}`);

return hoursDifference < 1;
}

if (process.env["DEBUG"] === "true") {
console.log(`*** No articles in cache`);
}
debug(`No articles in cache`);

return false;
};
Expand Down
18 changes: 8 additions & 10 deletions src/util/articleUtils.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,21 @@
import db from "./db";
import { Article } from "../types";
import articleSchema from "./articleSchema";
import { debug, log } from "./log";

const isValidArticle = (article: Article) => {
try {
articleSchema.parse(article);
return true;
} catch (e) {
if (process.env["DEBUG"] === "true") {
console.log(
`*** INVALID: ${article.source}: ${article.title} - ${e.errors.map((err: any) => err.message).join(", ")}`,
);
debug(`INVALID: ${article.source}: ${article.title} - ${e}`);
}
return false;
}
};

const insertArticle = (article: Article) => {
const insertArticle = (article: Article): boolean => {
const insert = db.prepare(
"INSERT INTO articles (id, title, link, source, created_at) VALUES (?, ?, ?, ?, ?)",
);
Expand All @@ -35,15 +34,14 @@ const insertArticle = (article: Article) => {
article.source,
new Date().toISOString(),
);
return true;
} catch (error) {
if (process.env["DEBUG"] === "true") {
console.log(`*** ERROR: ${error.message}`);
}
debug(`ERROR: ${error}`);
return false;
}
} else {
if (process.env["DEBUG"] === "true") {
console.log(`*** DUPLICATE: ${article.title}`);
}
debug(`DUPLICATE: ${article.title}`);
return false;
}
};

Expand Down
9 changes: 9 additions & 0 deletions src/util/log.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export function debug(...args: any[]): void {
if (process.env["DEBUG"] === "true") {
console.log("$", ...args);
}
}

export function log(...args: any[]): void {
console.log(">>", ...args);
}
32 changes: 32 additions & 0 deletions src/util/sendTelegramMessage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
const sendTelegramMessage = async (message: string) => {
const botToken = process.env.TELEGRAM_BOT_TOKEN;
const chatId = process.env.TELEGRAM_CHAT_ID;
const url = `https://api.telegram.org/bot${botToken}/sendMessage`;

try {
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
chat_id: chatId,
text: message,
}),
});

const data = await response.json();

if (!response.ok) {
throw new Error(
`Error sending message: ${response.statusText}, ${JSON.stringify(data)}`,
);
}

console.log("Message sent successfully.");
} catch (error) {
console.error("Error sending Telegram message:", error);
}
};

export default sendTelegramMessage;
38 changes: 38 additions & 0 deletions src/worker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { fetchAndStoreArticles } from "./util/api";
import sendTelegramMessage from "./util/sendTelegramMessage";
import { newsSources } from "./util/newsSources";
import { log } from "./util/log";

const scheduleArticleUpdate = async () => {
try {
const articles = await fetchAndStoreArticles();
const successMessage = generateSuccessMessage(articles);
log(successMessage);
if (articles.length > 0) await sendTelegramMessage(successMessage);
} catch (error) {
const errorMessage = `Error fetching articles: ${JSON.stringify(error, null, 2)}`;
log(errorMessage);
await sendTelegramMessage(errorMessage);
}
};

const generateSuccessMessage = (articles: any[]) => {
const counts = articles.reduce(
(acc: Record<string, number>, article: any) => {
acc[article.source] = (acc[article.source] || 0) + 1;
return acc;
},
{},
);

const articleCounts = newsSources
.map((source) => `${source.name}: ${counts[source.name] || 0}`)
.join("\n");

return `Articles fetched and stored successfully.\n\n${articleCounts}\n\nVisit: https://hyperwave.codes`;
};

// Run import every 15 minutes, unless someone gets mad
setInterval(scheduleArticleUpdate, 1000 * 60 * 15);

scheduleArticleUpdate();
Loading

0 comments on commit 573e79a

Please sign in to comment.