Initial: Pixelpöbel Discord Bot
Some checks failed
Auto Build and Push Docker Image / build (push) Failing after 8s
Some checks failed
Auto Build and Push Docker Image / build (push) Failing after 8s
- Modular Discord Bot mit TypeScript und discord.js - Grouped Commands (/admin, /owner subcommands) - SQLite Datenbank mit Persistenz - Twitch Monitoring mit Stream-ID Tracking - Konfigurierbares Warn-System (Auto-Kick/Mute/Ban) - Mute mit verschiedenen Zeiteinheiten (sec, min, hour, day, week, month, year, perm) - Auto-Deploy beim Start (Option 3) - Docker Support Features: - /ping, /help, /twitch (online, add, remove, list) - /admin (kick, warn, unwarn, mute, unmute, ban, unban, config) - /owner (deploy, stats, servers) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
120
src/structures/TwitchManager.ts
Normal file
120
src/structures/TwitchManager.ts
Normal file
@@ -0,0 +1,120 @@
|
||||
import { EmbedBuilder, TextChannel } from 'discord.js';
|
||||
|
||||
export class TwitchManager {
|
||||
private static accessToken: string | null = null;
|
||||
private static tokenExpires: number = 0;
|
||||
|
||||
private static async getAccessToken() {
|
||||
if (this.accessToken && Date.now() < this.tokenExpires) return this.accessToken;
|
||||
|
||||
const clientId = process.env.TWITCH_CLIENT_ID;
|
||||
const clientSecret = process.env.TWITCH_CLIENT_SECRET;
|
||||
|
||||
if (!clientId || !clientSecret) {
|
||||
console.error('[TWITCH] Missing Client ID or Secret in .env');
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`https://id.twitch.tv/oauth2/token?client_id=${clientId}&client_secret=${clientSecret}&grant_type=client_credentials`, {
|
||||
method: 'POST'
|
||||
});
|
||||
const data: any = await response.json();
|
||||
this.accessToken = data.access_token;
|
||||
this.tokenExpires = Date.now() + (data.expires_in * 1000) - 60000;
|
||||
return this.accessToken;
|
||||
} catch (error) {
|
||||
console.error('[TWITCH] Error getting access token:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static async fetchStreamData(channelName: string) {
|
||||
const token = await this.getAccessToken();
|
||||
if (!token) return null;
|
||||
|
||||
try {
|
||||
const response = await fetch(`https://api.twitch.tv/helix/streams?user_login=${channelName}`, {
|
||||
headers: {
|
||||
'Client-ID': process.env.TWITCH_CLIENT_ID!,
|
||||
'Authorization': `Bearer ${token}`
|
||||
}
|
||||
});
|
||||
const data: any = await response.json();
|
||||
return data.data?.[0] || null;
|
||||
} catch (error) {
|
||||
console.error(`[TWITCH] Error fetching stream data for ${channelName}:`, error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static async checkStreams(client: any) {
|
||||
const token = await this.getAccessToken();
|
||||
if (!token) return;
|
||||
|
||||
const monitors: any[] = (client as any).DB?.all('SELECT * FROM twitch_monitors') || [];
|
||||
if (monitors.length === 0) return;
|
||||
|
||||
for (const monitor of monitors) {
|
||||
try {
|
||||
const stream = await this.fetchStreamData(monitor.channel_name);
|
||||
|
||||
if (stream) {
|
||||
const isNewStream = monitor.last_stream_id !== stream.id;
|
||||
const wasOffline = monitor.last_status === 'offline';
|
||||
|
||||
if (wasOffline || (monitor.last_status === 'online' && isNewStream)) {
|
||||
await this.sendNotification(client, monitor, stream);
|
||||
(client as any).DB?.run(
|
||||
'UPDATE twitch_monitors SET last_status = "online", last_stream_id = ? WHERE id = ?',
|
||||
stream.id,
|
||||
monitor.id
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (monitor.last_status === 'online') {
|
||||
(client as any).DB?.run('UPDATE twitch_monitors SET last_status = "offline", last_stream_id = NULL WHERE id = ?', monitor.id);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`[TWITCH] Error checking stream ${monitor.channel_name}:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static async sendNotification(client: any, monitor: any, stream: any) {
|
||||
const guild = client.guilds.cache.get(monitor.guild_id);
|
||||
if (!guild || !monitor.discord_channel_id) return;
|
||||
|
||||
const channel = guild.channels.cache.get(monitor.discord_channel_id) as TextChannel;
|
||||
if (!channel) return;
|
||||
|
||||
const thumbnailUrl = stream.thumbnail_url
|
||||
.replace('{width}', '1280')
|
||||
.replace('{height}', '720') + `?t=${Date.now()}`;
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle(`${stream.user_name} ist jetzt LIVE auf Twitch!`)
|
||||
.setURL(`https://twitch.tv/${stream.user_login}`)
|
||||
.setColor(0x6441a5)
|
||||
.setImage(thumbnailUrl)
|
||||
.addFields(
|
||||
{ name: 'Titel', value: stream.title || 'Kein Titel', inline: false },
|
||||
{ name: 'Kategorie', value: stream.game_name || 'Unbekannt', inline: true },
|
||||
{ name: 'Zuschauer', value: stream.viewer_count.toString(), inline: true }
|
||||
)
|
||||
.setTimestamp();
|
||||
|
||||
let content = `📢 **${stream.user_name}** ist online!`;
|
||||
if (monitor.custom_message) {
|
||||
content += `\n\n${monitor.custom_message}`;
|
||||
}
|
||||
|
||||
await channel.send({ content, embeds: [embed] });
|
||||
}
|
||||
|
||||
static startPolling(client: any) {
|
||||
setInterval(() => this.checkStreams(client), 5 * 60 * 1000);
|
||||
console.log('[TWITCH] Polling started (5m interval).');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user