Skip to content

Commit 968cb7d

Browse files
authored
Merge pull request #2 from christian-bromann/main
chore(so-bot): update project
2 parents ae02634 + fbcf87f commit 968cb7d

File tree

11 files changed

+205
-40
lines changed

11 files changed

+205
-40
lines changed

.github/dependabot.yml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
version: 2
2+
updates:
3+
- package-ecosystem: npm
4+
directory: "/"
5+
schedule:
6+
interval: weekly
7+
time: "11:00"
8+
open-pull-requests-limit: 10
9+
versioning-strategy: increase-if-necessary
10+
groups:
11+
patch-deps-updates-main:
12+
update-types:
13+
- "patch"
14+
minor-deps-updates-main:
15+
update-types:
16+
- "minor"
17+
major-deps-updates-main:
18+
update-types:
19+
- "major"
20+
- package-ecosystem: github-actions
21+
directory: "/"
22+
schedule:
23+
interval: weekly
24+
time: "11:00"
25+
open-pull-requests-limit: 10
26+
groups:
27+
patch-deps-updates:
28+
update-types:
29+
- "patch"
30+
minor-deps-updates:
31+
update-types:
32+
- "minor"
33+
major-deps-updates:
34+
update-types:
35+
- "major"

.github/workflows/notify.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,16 @@ jobs:
1313
steps:
1414

1515
- name: Checkout repository
16-
uses: actions/checkout@v3
16+
uses: actions/checkout@v4
1717
with:
1818
# Persist credentials so git push works
1919
persist-credentials: true
2020
fetch-depth: 0
2121

2222
- name: Setup Node.js
23-
uses: actions/setup-node@v3
23+
uses: actions/setup-node@v4
2424
with:
25-
node-version: '22'
25+
node-version-file: .nvmrc
2626

2727
- name: Install dependencies
2828
run: npm ci

.nvmrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
v22.15.0

LICENSE

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
Copyright (c) OpenJS Foundation and other contributors
2+
3+
Permission is hereby granted, free of charge, to any person obtaining
4+
a copy of this software and associated documentation files (the
5+
'Software'), to deal in the Software without restriction, including
6+
without limitation the rights to use, copy, modify, merge, publish,
7+
distribute, sublicense, and/or sell copies of the Software, and to
8+
permit persons to whom the Software is furnished to do so, subject to
9+
the following conditions:
10+
11+
The above copyright notice and this permission notice shall be
12+
included in all copies or substantial portions of the Software.
13+
14+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
15+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

README.md

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# WDIO Discord Bot
2+
3+
A Node.js bot that bridges Stack Overflow and Discord for the WebdriverIO community.
4+
5+
## Features
6+
7+
- **Monitors Stack Overflow** for new questions tagged with a configurable tag (default: `webdriver-io`).
8+
- **Posts new questions** as rich embeds to a specified Discord channel via webhook.
9+
- **Prevents duplicate notifications** by persisting sent question IDs.
10+
- **Configurable** via environment variables.
11+
- **Easy to extend** for other tags or notification channels.
12+
13+
## Getting Started
14+
15+
### Prerequisites
16+
17+
- Node.js v22+ (for native fetch and ESM support)
18+
- npm
19+
- Access to a Discord server where you can create a webhook
20+
21+
### Installation
22+
23+
1. **Clone the repository:**
24+
```sh
25+
git clone <repo-url>
26+
cd wdio-discord-bot
27+
```
28+
29+
2. **Install dependencies:**
30+
```sh
31+
npm install
32+
```
33+
34+
3. **Configure environment variables:**
35+
36+
Create a `.env` file in the project root with the following content:
37+
38+
```
39+
DISCORD_WEBHOOK_ID=your_discord_webhook_id
40+
DISCORD_WEBHOOK_TOKEN=your_discord_webhook_token
41+
TAG_TO_MONITOR=webdriver-io
42+
# Optional: STACKEXCHANGE_KEY=your_stackexchange_api_key
43+
```
44+
45+
- `DISCORD_WEBHOOK_ID` and `DISCORD_WEBHOOK_TOKEN` are from your Discord channel webhook.
46+
- `TAG_TO_MONITOR` is the Stack Overflow tag to monitor.
47+
- `STACKEXCHANGE_KEY` is optional but recommended for higher API rate limits.
48+
49+
4. **Run the bot:**
50+
51+
- For development (with TypeScript):
52+
```sh
53+
npm run dev
54+
```
55+
- For production:
56+
```sh
57+
npm start
58+
```
59+
60+
## How It Works
61+
62+
- On each run, the bot:
63+
1. Loads the list of previously notified question IDs from `data/sentIds.json`.
64+
2. Fetches the latest questions from Stack Overflow tagged with your configured tag.
65+
3. Filters out questions already sent.
66+
4. Formats and posts new questions to Discord as embeds.
67+
5. Updates `sentIds.json` and auto-commits/pushes the file (if using git).
68+
69+
## Development Advice
70+
71+
- **TypeScript:** The project is written in TypeScript. Use `npm run dev` for live development.
72+
- **Persistence:** Sent question IDs are stored in `data/sentIds.json`. To reset notifications, delete or clear this file.
73+
- **Auto-commit:** Each time new questions are sent, the bot auto-commits and pushes the updated `sentIds.json`. Ensure your environment has git configured and permissions set.
74+
- **Extending:** To monitor a different tag, change `TAG_TO_MONITOR` in your `.env`.
75+
- **Formatting:** The embed includes a snippet of the question body, with code blocks formatted for Discord.
76+
- **Error Handling:** If the bot fails to send a message or update sent IDs, errors are logged to the console.
77+
- **API Limits:** For higher Stack Exchange API limits, set `STACKEXCHANGE_KEY` in your `.env`.
78+
79+
## Project Structure
80+
81+
```
82+
src/
83+
config/ # Environment variable validation
84+
services/ # Stack Overflow, Discord, and persistence logic
85+
utils/ # HTML formatting for Discord
86+
index.ts # Entry point
87+
data/
88+
sentIds.json # Tracks sent question IDs
89+
```
90+
91+
## Troubleshooting
92+
93+
- **No new questions:** If there are no new questions, the bot will log "No new questions."
94+
- **Webhook errors:** Double-check your Discord webhook credentials.
95+
- **Git errors:** Ensure your environment has git installed and configured for auto-commits.
96+
97+
## License
98+
99+
MIT
100+
101+
Feel free to further customize this README for your community or deployment environment!

package.json

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
{
22
"name": "wdio-discord-bot",
3+
"author": "Rondleysg",
4+
"license": "MIT",
5+
"description": "",
36
"version": "1.0.0",
4-
"main": "index.js",
7+
"private": true,
58
"scripts": {
69
"build": "tsc",
710
"start": "npm run build && node dist/index.js",
811
"dev": "ts-node src/index.ts"
912
},
10-
"keywords": [],
11-
"author": "",
12-
"license": "ISC",
13-
"description": "",
1413
"dependencies": {
1514
"@octokit/rest": "^21.1.1",
1615
"discord.js": "^14.19.3",

src/index.ts

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,20 @@
1-
import { env } from './config/env';
21
import { fetchLatestQuestions } from './services/stackoverflow';
32
import { formatBody } from './utils/htmlFormatter';
43
import { sendQuestion } from './services/notifier';
54
import { loadSentIds, saveSentIds } from './services/persistence';
65

7-
(async () => {
8-
const sent = await loadSentIds();
9-
const questions = await fetchLatestQuestions();
10-
const newQs = questions.filter(q => !sent.has(q.question_id));
6+
const sent = await loadSentIds();
7+
const questions = await fetchLatestQuestions();
8+
const newQs = questions.filter((q) => !sent.has(q.question_id));
119

12-
for (const q of newQs.reverse()) {
13-
const snippet = formatBody(q.body).split('\n').slice(0, 10).join('\n').substring(0, 1024);
14-
await sendQuestion(q, snippet);
15-
sent.add(q.question_id);
16-
}
10+
for (const q of newQs.reverse()) {
11+
const snippet = formatBody(q.body).split('\n').slice(0, 10).join('\n').substring(0, 1024);
12+
await sendQuestion(q, snippet);
13+
sent.add(q.question_id);
14+
}
1715

18-
if (newQs.length > 0) {
19-
await saveSentIds(sent);
20-
} else {
21-
console.log('No new questions.');
22-
}
23-
})();
16+
if (newQs.length > 0) {
17+
await saveSentIds(sent);
18+
} else {
19+
console.log('No new questions.');
20+
}

src/services/notifier.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { WebhookClient, EmbedBuilder } from "discord.js";
2-
import { env } from "../config/env";
2+
33
import { Question } from "./stackoverflow";
4+
import { env } from "../config/env";
45

56
const webhook = new WebhookClient({
67
id: env.DISCORD_WEBHOOK_ID,

src/services/persistence.ts

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,35 @@
1-
import fs from 'fs/promises';
2-
import { exec } from 'child_process';
3-
import path from 'path';
1+
import fs from 'node:fs/promises';
2+
import cp from 'node:child_process';
3+
import path from 'node:path';
44

55
const DATA_FILE = path.resolve(__dirname, '../../data/sentIds.json');
66

77
export async function loadSentIds(): Promise<Set<number>> {
8-
try {
9-
const raw = await fs.readFile(DATA_FILE, 'utf-8');
10-
return new Set<number>(JSON.parse(raw));
11-
} catch {
8+
const isExisting = await fs.access(DATA_FILE).then(() => true).catch(() => false);
9+
if (!isExisting) {
10+
await fs.writeFile(DATA_FILE, JSON.stringify([], null, 2));
1211
return new Set();
1312
}
13+
14+
const raw = await fs.readFile(DATA_FILE, 'utf-8');
15+
return new Set<number>(JSON.parse(raw));
1416
}
1517

1618
export async function saveSentIds(ids: Set<number>) {
1719
const arr = Array.from(ids);
1820
await fs.writeFile(DATA_FILE, JSON.stringify(arr, null, 2));
21+
1922
// Auto‑commit and push
20-
exec(
23+
return new Promise<void>((resolve, reject) => cp.exec(
2124
`git add ${DATA_FILE} && git commit -m "chore: update sent question IDs" && git push`,
2225
(err, stdout, stderr) => {
23-
if (err) console.error('Git commit failed:', stderr, stdout);
24-
else console.log('Sent IDs file committed.');
26+
if (err) {
27+
console.error('Git commit failed:', stderr, stdout);
28+
return reject(err);
29+
}
30+
31+
console.log('Sent IDs file committed.');
32+
resolve();
2533
}
26-
);
34+
));
2735
}

src/services/stackoverflow.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ export async function fetchLatestQuestions(): Promise<Question[]> {
1717
pagesize: "10",
1818
filter: "withbody",
1919
});
20-
if (env.STACKEXCHANGE_KEY) params.append("key", env.STACKEXCHANGE_KEY);
20+
21+
if (env.STACKEXCHANGE_KEY) {
22+
params.append("key", env.STACKEXCHANGE_KEY)
23+
};
2124

2225
const url = `https://api.stackexchange.com/2.3/questions?${params}`;
2326
const res = await fetch(url);

0 commit comments

Comments
 (0)