Réécriture complète 4.0
Some checks failed
Build and Push Docker Image / build-and-push (push) Failing after 6m16s
Some checks failed
Build and Push Docker Image / build-and-push (push) Failing after 6m16s
This commit is contained in:
@@ -1,193 +1,327 @@
|
||||
import { Guild, TextChannel, EmbedBuilder, hyperlink } from 'discord.js'
|
||||
import axios, { AxiosHeaderValue } from 'axios'
|
||||
import dbGuild from '../schemas/guild'
|
||||
import chalk from 'chalk'
|
||||
|
||||
interface NotificationData {
|
||||
metadata: {
|
||||
message_type: string
|
||||
}
|
||||
payload: {
|
||||
subscription: {
|
||||
type: string
|
||||
}
|
||||
event: {
|
||||
broadcaster_user_name: string
|
||||
broadcaster_user_login: string
|
||||
}
|
||||
session: {
|
||||
id: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface Condition {
|
||||
broadcaster_user_id: string
|
||||
}
|
||||
|
||||
export async function checkChannel (client_id: string, client_secret: string, channel_access_token: string, guild: Guild) {
|
||||
let guildProfile = await dbGuild.findOne({ guildId: guild?.id })
|
||||
if (!guildProfile) return console.log(chalk.magenta(`[Twitch] Database data for ${guild?.name} does not exist, please initialize with \`/database init\` !`))
|
||||
|
||||
let dbData = guildProfile.get('guildTwitch')
|
||||
if (!dbData?.enabled) return console.log(chalk.magenta(`[Twitch] module is disabled for "${guild?.name}", please activate with \`/database edit guildTwitch.enabled True\` !`))
|
||||
|
||||
if (!await validateToken(channel_access_token)) {
|
||||
let channel_refresh_token = dbData.channelRefreshToken
|
||||
if (!channel_refresh_token) return console.log(chalk.magenta('[Twitch] No refresh token found in database !'))
|
||||
|
||||
let GetAccessFromRefresh = await refreshToken(client_id, client_secret, channel_refresh_token)
|
||||
if (!GetAccessFromRefresh) return false;
|
||||
|
||||
[channel_access_token, channel_refresh_token] = [dbData.channelAccessToken, dbData.channelRefreshToken] = GetAccessFromRefresh
|
||||
|
||||
guildProfile.set('guildTwitch', dbData)
|
||||
guildProfile.markModified('guildTwitch')
|
||||
await guildProfile.save().catch(console.error)
|
||||
}
|
||||
return channel_access_token
|
||||
}
|
||||
|
||||
export async function validateToken (access_token: string) {
|
||||
return await axios.get('https://id.twitch.tv/oauth2/validate', {
|
||||
headers: {
|
||||
'Authorization': `OAuth ${access_token}`,
|
||||
}
|
||||
}).then(() => {
|
||||
return true
|
||||
}).catch(error => { console.error(error.response.data) })
|
||||
}
|
||||
|
||||
export async function refreshToken (client_id: string, client_secret: string, refresh_token: string) {
|
||||
return await axios.post('https://id.twitch.tv/oauth2/token', {
|
||||
client_id,
|
||||
client_secret,
|
||||
refresh_token,
|
||||
grant_type: 'refresh_token'
|
||||
}).then(response => {
|
||||
if (response.data.token_type === 'bearer') return [response.data.access_token, response.data.refresh_token]
|
||||
}).catch(error => { console.error(error.response.data) })
|
||||
}
|
||||
|
||||
export async function getStreams (client_id: string, access_token: string, user_login: string) {
|
||||
return await axios.get(`https://api.twitch.tv/helix/streams?user_login=${user_login}`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${access_token}`,
|
||||
'Client-Id': client_id as AxiosHeaderValue
|
||||
}
|
||||
}).then(response => {
|
||||
return response.data.data[0]
|
||||
}).catch(error => { console.error(error.response) })
|
||||
}
|
||||
|
||||
export async function getUserInfo (client_id: string, access_token: string, type: string) {
|
||||
return await axios.get('https://api.twitch.tv/helix/users', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${access_token}`,
|
||||
'Client-Id': client_id as AxiosHeaderValue
|
||||
}
|
||||
}).then(response => {
|
||||
if (type === 'login') return response.data.data[0].login
|
||||
if (type === 'id') return response.data.data[0].id
|
||||
if (type === 'profile_image_url') return response.data.data[0].profile_image_url
|
||||
}).catch(error => { console.error(error.response.data) })
|
||||
}
|
||||
|
||||
export async function notification (client_id: string, client_secret: string, channel_access_token: string, data: NotificationData, guild: Guild) {
|
||||
if (!guild) return console.log(chalk.magenta('[Twitch] Can\'t find guild !'))
|
||||
let { subscription, event } = data.payload
|
||||
|
||||
let guildProfile = await dbGuild.findOne({ guildId: guild.id })
|
||||
if (!guildProfile) return console.log(chalk.magenta(`[Twitch] {${guild.name}} Database data does not exist, please initialize with \`/database init\` !`))
|
||||
|
||||
let dbData = guildProfile.get('guildTwitch')
|
||||
if (!dbData?.enabled) return console.log(chalk.magenta(`[Twitch] {${guild.name}} Module is disabled, please activate with \`/database edit guildTwitch.enabled True\` !`))
|
||||
|
||||
let liveChannelId = dbData.liveChannelId
|
||||
if (!liveChannelId) return console.log(chalk.magenta(`[Twitch] {${guild.name}} No live channel id found in database !`))
|
||||
|
||||
let liveChannel = guild.channels.cache.get(liveChannelId) as TextChannel
|
||||
if (!liveChannel) return console.log(chalk.magenta(`[Twitch] {${guild.name}} Can't find channel with id ${liveChannelId}`))
|
||||
|
||||
let liveMessageInterval
|
||||
|
||||
// Check if the channel access token is still valid before connecting
|
||||
channel_access_token = await checkChannel(client_id, client_secret, channel_access_token, guild) as string
|
||||
if (!channel_access_token) return console.log(chalk.magenta(`[Twitch] {${guild.name}} Can't refresh channel access token !`))
|
||||
|
||||
if (subscription.type === 'stream.online') {
|
||||
console.log(chalk.magenta(`[Twitch] {${guild.name}} Stream from ${event.broadcaster_user_name} is now online, sending Discord message...`))
|
||||
|
||||
let stream_data = await getStreams(client_id, channel_access_token, event.broadcaster_user_login)
|
||||
let user_profile_image_url = await getUserInfo(client_id, channel_access_token, 'profile_image_url')
|
||||
|
||||
let embed = new EmbedBuilder()
|
||||
.setColor('#6441a5')
|
||||
.setTitle(stream_data.title)
|
||||
.setURL(`https://twitch.tv/${event.broadcaster_user_login}`)
|
||||
.setAuthor({ name: `🔴 ${event.broadcaster_user_name.toUpperCase()} EST ACTUELLEMENT EN LIVE ! 🎥`, iconURL: user_profile_image_url })
|
||||
.setDescription(`Joue à ${stream_data.game_name} avec ${stream_data.viewer_count} viewers`)
|
||||
.setImage(stream_data.thumbnail_url.replace('{width}', '1920').replace('{height}', '1080'))
|
||||
.setTimestamp()
|
||||
let hidden = hyperlink('démarre un live', 'https://www.youtube.com/watch?v=dQw4w9WgXcQ&pp=ygUJcmljayByb2xs')
|
||||
let message = await liveChannel.send({ content: `Hey @everyone ! <@${dbData.liveBroadcasterId}> ${hidden} sur **Twitch**, venez !`, embeds: [embed] })
|
||||
|
||||
dbData.liveMessageId = message.id
|
||||
guildProfile.set('guildTwitch', dbData)
|
||||
guildProfile.markModified('guildTwitch')
|
||||
await guildProfile.save().catch(console.error)
|
||||
|
||||
liveMessageInterval = setInterval(async () => {
|
||||
let stream_data = await getStreams(client_id, channel_access_token, event.broadcaster_user_login)
|
||||
if (!stream_data) return console.log(chalk.magenta(`[Twitch] {${guild.name}} Can't find stream data for ${event.broadcaster_user_name}`))
|
||||
embed.setTitle(stream_data.title)
|
||||
.setDescription(`Joue à ${stream_data.game_name} avec ${stream_data.viewer_count} viewers`)
|
||||
.setImage(stream_data.thumbnail_url.replace('{width}', '1920').replace('{height}', '1080'))
|
||||
.setTimestamp()
|
||||
message.edit({ content: `Hey @everyone !\n<@${dbData.liveBroadcasterId}> est en live sur **Twitch**, venez !`, embeds: [embed] }).catch(console.error)
|
||||
}, 60000)
|
||||
}
|
||||
else if (subscription.type === 'stream.offline') {
|
||||
console.log(chalk.magenta(`[Twitch] {${guild.name}} Stream from ${event.broadcaster_user_name} is now offline, editing Discord message...`))
|
||||
|
||||
let message = await liveChannel.messages.fetch(dbData.liveMessageId as string)
|
||||
if (!message) return console.log(chalk.magenta(`[Twitch] {${guild.name}} Can't find message with id ${dbData.liveMessageId}`))
|
||||
if (!message.embeds[0]) return console.log(chalk.magenta(`[Twitch] {${guild.name}} Can't find embed in message with id ${dbData.liveMessageId}`))
|
||||
|
||||
let duration = new Date().getTime() - new Date(message.embeds[0].data.timestamp ?? 0).getTime()
|
||||
let seconds = Math.floor(duration / 1000)
|
||||
let minutes = Math.floor(seconds / 60)
|
||||
let hours = Math.floor(minutes / 60)
|
||||
let duration_string = `${hours ? hours + 'H ' : ''}${minutes % 60 ? minutes % 60 + 'M ' : ''}${seconds % 60 ? seconds % 60 + 'S' : ''}`
|
||||
|
||||
let user_profile_image_url = await getUserInfo(client_id, channel_access_token, 'profile_image_url')
|
||||
|
||||
let embed = new EmbedBuilder()
|
||||
.setColor('#6441a5')
|
||||
.setAuthor({ name: `⚫ C'EST FINI, LE LIVE A DURÉ ${duration_string} ! 📼`, iconURL: user_profile_image_url })
|
||||
.setTimestamp()
|
||||
|
||||
message.edit({ content: `Re @everyone !\n<@${dbData.liveBroadcasterId}> a terminé son live sur **Twitch** !`, embeds: [embed] }).catch(console.error)
|
||||
clearInterval(liveMessageInterval)
|
||||
}
|
||||
}
|
||||
|
||||
export async function subscribeToEvents (client_id: string, access_token: string, session_id: string, type: string, version: string, condition: Condition) {
|
||||
return await axios.post('https://api.twitch.tv/helix/eventsub/subscriptions', {
|
||||
type,
|
||||
version,
|
||||
condition,
|
||||
transport: {
|
||||
method: 'websocket',
|
||||
session_id
|
||||
}
|
||||
}, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${access_token}`,
|
||||
'Client-Id': client_id,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}).then(response => {
|
||||
return response.data.data[0].status
|
||||
}).catch(error => { return error.response.data })
|
||||
}
|
||||
// ENVIRONMENT VARIABLES
|
||||
const clientId = process.env.TWITCH_APP_ID
|
||||
const clientSecret = process.env.TWITCH_APP_SECRET
|
||||
if (!clientId || !clientSecret) {
|
||||
console.warn(chalk.red("[Twitch] Missing TWITCH_APP_ID or TWITCH_APP_SECRET in environment variables!"))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
// PACKAGES
|
||||
import { AppTokenAuthProvider } from "@twurple/auth"
|
||||
import { ApiClient } from "@twurple/api"
|
||||
import { ReverseProxyAdapter, EventSubHttpListener } from "@twurple/eventsub-http"
|
||||
import { NgrokAdapter } from "@twurple/eventsub-ngrok"
|
||||
import type { EventSubStreamOnlineEvent, EventSubStreamOfflineEvent } from "@twurple/eventsub-base"
|
||||
import { EmbedBuilder, ChannelType, ComponentType, ButtonBuilder, ButtonStyle, Locale } from "discord.js"
|
||||
import type { Client, Guild } from "discord.js"
|
||||
import chalk from "chalk"
|
||||
import discordClient from "@/index"
|
||||
import type { GuildTwitch } from "@/types/schemas"
|
||||
import dbGuild from "@/schemas/guild"
|
||||
import { t } from "@/utils/i18n"
|
||||
import { logConsole } from "@/utils/console"
|
||||
|
||||
// Twurple API client setup
|
||||
const authProvider = new AppTokenAuthProvider(clientId, clientSecret)
|
||||
export const twitchClient = new ApiClient({ authProvider })
|
||||
|
||||
let adapter
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
const authtoken = process.env.NGROK_AUTHTOKEN
|
||||
|
||||
logConsole('twitch', 'starting_listener_ngrok')
|
||||
adapter = new NgrokAdapter({ ngrokConfig: { authtoken } })
|
||||
} else {
|
||||
const hostName = process.env.TWURPLE_HOSTNAME ?? "localhost"
|
||||
const port = process.env.TWURPLE_PORT ?? "3000"
|
||||
|
||||
console.log(chalk.magenta(`[Twitch] Starting listener with port ${port}...`))
|
||||
adapter = new ReverseProxyAdapter({ hostName, port: parseInt(port) })
|
||||
}
|
||||
|
||||
const secret = process.env.TWURPLE_SECRET ?? "VeryUnsecureSecretPleaseChangeMe"
|
||||
export const listener = new EventSubHttpListener({ apiClient: twitchClient, adapter, secret })
|
||||
listener.start()
|
||||
|
||||
// Twurple subscriptions callback functions
|
||||
export const onlineSub = async (event: EventSubStreamOnlineEvent) => {
|
||||
console.log(chalk.magenta(`[Twitch] Stream from ${event.broadcasterName} (ID ${event.broadcasterId}) is now online, sending Discord messages...`))
|
||||
|
||||
const results = await Promise.allSettled(discordClient.guilds.cache.map(async guild => {
|
||||
try {
|
||||
console.log(chalk.magenta(`[Twitch] Processing guild: ${guild.name} (ID: ${guild.id}) for streamer ${event.broadcasterName}`))
|
||||
|
||||
const notification = await generateNotification(guild, event.broadcasterId, event.broadcasterName)
|
||||
if (notification.status !== "ok") { console.log(chalk.magenta(`[Twitch] StreamWatching - {${guild.name}} Notification generation failed with status: ${notification.status}`)); return }
|
||||
|
||||
const { guildProfile, dbData, channel, content, embed } = notification
|
||||
if (!dbData) { console.log(chalk.magenta(`[Twitch] StreamWatching - {${guild.name}} No dbData found`)); return }
|
||||
|
||||
const streamerIndex = dbData.streamers.findIndex(s => s.twitchUserId === event.broadcasterId)
|
||||
if (streamerIndex === -1) { console.log(chalk.magenta(`[Twitch] StreamWatching - {${guild.name}} Streamer ${event.broadcasterName} not found in this guild`)); return }
|
||||
|
||||
if (dbData.streamers[streamerIndex].messageId) { console.log(chalk.magenta(`[Twitch] StreamWatching - {${guild.name}} Message already exists for ${event.broadcasterName}, skipping`)); return }
|
||||
|
||||
console.log(chalk.magenta(`[Twitch] StreamWatching - {${guild.name}} Sending notification for ${event.broadcasterName}`))
|
||||
const message = await channel.send({ content, embeds: [embed] })
|
||||
console.log(chalk.magenta(`[Twitch] StreamWatching - {${guild.name}} Message sent with ID: ${message.id}`))
|
||||
|
||||
dbData.streamers[streamerIndex].messageId = message.id
|
||||
|
||||
guildProfile.set("guildTwitch", dbData)
|
||||
guildProfile.markModified("guildTwitch")
|
||||
await guildProfile.save().catch(console.error)
|
||||
|
||||
startStreamWatching(guild.id, event.broadcasterId, event.broadcasterName, message.id)
|
||||
} catch (error) {
|
||||
console.log(chalk.magenta(`[Twitch] Error processing guild ${guild.name}`))
|
||||
console.error(error)
|
||||
}
|
||||
}))
|
||||
|
||||
results.forEach((result, index) => {
|
||||
if (result.status === "rejected") console.log(chalk.magenta(`[Twitch] Guild ${index} failed:`), result.reason)
|
||||
})
|
||||
}
|
||||
|
||||
export const offlineSub = async (event: EventSubStreamOfflineEvent) => {
|
||||
console.log(chalk.magenta(`[Twitch] Stream from ${event.broadcasterName} (ID ${event.broadcasterId}) is now offline, editing Discord messages...`))
|
||||
|
||||
await Promise.all(discordClient.guilds.cache.map(async guild => {
|
||||
await stopStreamWatching(guild.id, event.broadcasterId, event.broadcasterName)
|
||||
}))
|
||||
}
|
||||
|
||||
// Stream upadting intervals
|
||||
const streamIntervals = new Map<string, NodeJS.Timeout>()
|
||||
|
||||
export function startStreamWatching(guildId: string, streamerId: string, streamerName: string, messageId: string) {
|
||||
console.log(chalk.magenta(`[Twitch] StreamWatching - Démarrage du visionnage de ${streamerName} (ID ${streamerId}) sur ${guildId}`))
|
||||
|
||||
const key = `${guildId}-${streamerId}`
|
||||
if (streamIntervals.has(key)) {
|
||||
clearInterval(streamIntervals.get(key))
|
||||
streamIntervals.delete(key)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
const interval = setInterval(async () => {
|
||||
try {
|
||||
const guild = await discordClient.guilds.fetch(guildId)
|
||||
const notification = await generateNotification(guild, streamerId, streamerName)
|
||||
if (notification.status !== "ok") { console.log(chalk.magenta(`[Twitch] StreamWatching - {${guild.name}} Notification generation failed with status: ${notification.status}`)); return }
|
||||
|
||||
const { channel, content, embed } = notification
|
||||
if (!embed) { console.log(chalk.magenta(`[Twitch] StreamWatching - {${guild.name}} Embed is missing`)); return }
|
||||
|
||||
try {
|
||||
const message = await channel.messages.fetch(messageId)
|
||||
await message.edit({ content, embeds: [embed] })
|
||||
} catch (error) {
|
||||
console.log(chalk.magenta(`[Twitch] StreamWatching - {${guild.name}} Error editing message for ${streamerName} (ID ${streamerId})`))
|
||||
console.error(error)
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(chalk.magenta(`[Twitch] StreamWatching - Erreur lors du visionnage de ${streamerName} (ID ${streamerId}) sur ${guildId}`))
|
||||
console.error(error)
|
||||
await stopStreamWatching(guildId, streamerId, streamerName)
|
||||
}
|
||||
}, 60000)
|
||||
|
||||
streamIntervals.set(key, interval)
|
||||
}
|
||||
|
||||
export async function stopStreamWatching(guildId: string, streamerId: string, streamerName: string) {
|
||||
console.log(chalk.magenta(`[Twitch] StreamWatching - Arrêt du visionnage de ${streamerName} (ID ${streamerId})`))
|
||||
|
||||
const key = `${guildId}-${streamerId}`
|
||||
if (streamIntervals.has(key)) {
|
||||
clearInterval(streamIntervals.get(key))
|
||||
streamIntervals.delete(key)
|
||||
}
|
||||
|
||||
const guild = await discordClient.guilds.fetch(guildId)
|
||||
const guildProfile = await dbGuild.findOne({ guildId: guild.id })
|
||||
if (!guildProfile) { console.log(chalk.magenta(`[Twitch] StreamWatching - {${guild.name}} Database data does not exist !`)); return }
|
||||
|
||||
const dbData = guildProfile.get("guildTwitch") as GuildTwitch
|
||||
if (!dbData.enabled || !dbData.channelId) { console.log(chalk.magenta(`[Twitch] StreamWatching - {${guild.name}} Database data does not exist !`)); return }
|
||||
|
||||
const channel = await guild.channels.fetch(dbData.channelId)
|
||||
if (!channel || (channel.type !== ChannelType.GuildText && channel.type !== ChannelType.GuildAnnouncement)) {
|
||||
console.log(chalk.magenta(`[Twitch] StreamWatching - {${guild.name}} Channel with ID ${dbData.channelId} not found for Twitch notifications`))
|
||||
return
|
||||
}
|
||||
|
||||
const streamer = dbData.streamers.find(s => s.twitchUserId === streamerId)
|
||||
if (!streamer) { console.log(chalk.magenta(`[Twitch] StreamWatching - {${guild.name}} Streamer not found in guild for ${streamerName} (ID ${streamerId})`)); return }
|
||||
|
||||
const messageId = streamer.messageId
|
||||
if (!messageId) { console.log(chalk.magenta(`[Twitch] StreamWatching - {${guild.name}} Message ID not found for ${streamerName} (ID ${streamerId})`)); return }
|
||||
|
||||
const user = await twitchClient.users.getUserById(streamerId)
|
||||
if (!user) console.log(chalk.magenta(`[Twitch] StreamWatching - {${guild.name}} User data not found for ${streamerName} (ID ${streamerId})`))
|
||||
|
||||
let duration_string = ""
|
||||
const stream = await user?.getStream()
|
||||
if (!stream) {
|
||||
console.log(chalk.magenta(`[Twitch] StreamWatching - {${guild.name}} Stream data not found for ${streamerName} (ID ${streamerId})`))
|
||||
duration_string = t(guild.preferredLocale, "twitch.notification.offline.duration_unknown")
|
||||
} else {
|
||||
const duration = new Date().getTime() - stream.startDate.getTime()
|
||||
const seconds = Math.floor(duration / 1000)
|
||||
const minutes = Math.floor(seconds / 60)
|
||||
const hours = Math.floor(minutes / 60)
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands
|
||||
duration_string = `${hours ? hours + "H " : ""}${minutes % 60 ? (minutes % 60) + "M " : ""}${seconds % 60 ? (seconds % 60) + "S" : ""}`
|
||||
}
|
||||
|
||||
let content = ""
|
||||
if (!streamer.discordUserId) content = t(guild.preferredLocale, "twitch.notification.offline.everyone", { streamer: streamerName })
|
||||
else content = t(guild.preferredLocale, "twitch.notification.offline.everyone_with_mention", { discordId: streamer.discordUserId })
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setColor("#6441a5")
|
||||
.setAuthor({
|
||||
name: t(guild.preferredLocale, "twitch.notification.offline.author", { duration: duration_string }),
|
||||
iconURL: user?.profilePictureUrl ?? "https://static-cdn.jtvnw.net/emoticons/v2/58765/static/light/3.0"
|
||||
})
|
||||
.setTimestamp()
|
||||
|
||||
try {
|
||||
const message = await channel.messages.fetch(messageId)
|
||||
await message.edit({ content, embeds: [embed] })
|
||||
} catch (error) {
|
||||
console.log(chalk.magenta(`[Twitch] StreamWatching - {${guild.name}} Error editing message for ${streamerName} (ID ${streamerId})`))
|
||||
console.error(error)
|
||||
}
|
||||
|
||||
const streamerIndex = dbData.streamers.findIndex(s => s.twitchUserId === streamerId)
|
||||
if (streamerIndex === -1) return
|
||||
|
||||
dbData.streamers[streamerIndex].messageId = ""
|
||||
|
||||
guildProfile.set("guildTwitch", dbData)
|
||||
guildProfile.markModified("guildTwitch")
|
||||
await guildProfile.save().catch(console.error)
|
||||
}
|
||||
|
||||
async function generateNotification(guild: Guild, streamerId: string, streamerName: string) {
|
||||
const guildProfile = await dbGuild.findOne({ guildId: guild.id })
|
||||
if (!guildProfile) {
|
||||
console.log(chalk.magenta(`[Twitch] StreamWatching - {${guild.name}} Database data does not exist !`))
|
||||
return { status: "noProfile" }
|
||||
}
|
||||
|
||||
const dbData = guildProfile.get("guildTwitch") as GuildTwitch
|
||||
if (!dbData.enabled || !dbData.channelId) {
|
||||
console.log(chalk.magenta(`[Twitch] StreamWatching - {${guild.name}} Twitch module is not enabled or channel ID is missing`))
|
||||
return { status: "disabled" }
|
||||
}
|
||||
|
||||
const channel = await guild.channels.fetch(dbData.channelId)
|
||||
if ((channel?.type !== ChannelType.GuildText && channel?.type !== ChannelType.GuildAnnouncement)) {
|
||||
console.log(chalk.magenta(`[Twitch] StreamWatching - {${guild.name}} Channel with ID ${dbData.channelId} not found for Twitch notifications`))
|
||||
return { status: "noChannel" }
|
||||
}
|
||||
|
||||
const streamer = dbData.streamers.find(s => s.twitchUserId === streamerId)
|
||||
if (!streamer) {
|
||||
console.log(chalk.magenta(`[Twitch] StreamWatching - {${guild.name}} Streamer not found in guild for ${streamerName} (ID ${streamerId})`))
|
||||
return { status: "noStreamer" }
|
||||
}
|
||||
|
||||
const user = await twitchClient.users.getUserById(streamerId)
|
||||
if (!user) console.log(chalk.magenta(`[Twitch] StreamWatching - {${guild.name}} User data not found for ${streamerName} (ID ${streamerId})`))
|
||||
|
||||
const stream = await user?.getStream()
|
||||
if (!stream) console.log(chalk.magenta(`[Twitch] StreamWatching - {${guild.name}} Stream data not found for ${streamerName} (ID ${streamerId})`))
|
||||
|
||||
let content = ""
|
||||
if (!streamer.discordUserId) content = t(guild.preferredLocale, "twitch.notification.online.everyone", { streamer: user?.displayName ?? streamerName })
|
||||
else content = t(guild.preferredLocale, "twitch.notification.online.everyone_with_mention", { discordId: streamer.discordUserId })
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setColor("#6441a5")
|
||||
.setTitle(stream?.title ?? t(guild.preferredLocale, "twitch.notification.online.title_unknown"))
|
||||
.setURL(`https://twitch.tv/${streamerName}`)
|
||||
.setAuthor({
|
||||
name: t(guild.preferredLocale, "twitch.notification.online.author", { streamer: (user?.displayName ?? streamerName).toUpperCase() }),
|
||||
iconURL: user?.profilePictureUrl ?? "https://static-cdn.jtvnw.net/emoticons/v2/58765/static/light/3.0"
|
||||
})
|
||||
.setDescription(t(guild.preferredLocale, "twitch.notification.online.description", {
|
||||
game: stream?.gameName ?? "?",
|
||||
viewers: stream?.viewers.toString() ?? "?"
|
||||
}))
|
||||
.setImage(stream?.thumbnailUrl.replace("{width}", "1920").replace("{height}", "1080") ?? "https://assets.help.twitch.tv/article/img/000002222-01a.png")
|
||||
.setTimestamp()
|
||||
|
||||
return { status: "ok", guildProfile, dbData, channel, content, embed }
|
||||
}
|
||||
|
||||
export function generateTwitchEmbed(dbData: GuildTwitch, client: Client, guildId: string, locale: Locale) {
|
||||
// Récupérer les informations du canal
|
||||
let channelInfo = t(locale, "twitch.channel_not_configured")
|
||||
if (dbData.channelId) {
|
||||
const guild = client.guilds.cache.get(guildId)
|
||||
const channel = guild?.channels.cache.get(dbData.channelId)
|
||||
channelInfo = channel ? `<#${channel.id}>` : t(locale, "twitch.common.channel_not_found")
|
||||
}
|
||||
|
||||
// Créer l'embed principal
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle(t(locale, "twitch.title"))
|
||||
.setColor(dbData.enabled ? 0x9146FF : 0x808080)
|
||||
.addFields(
|
||||
{ name: t(locale, "common.status"), value: dbData.enabled ? t(locale, "twitch.common.enabled") : t(locale, "twitch.common.disabled"), inline: true },
|
||||
{ name: t(locale, "common.channel"), value: channelInfo, inline: true },
|
||||
{ name: "👥 Streamers", value: t(locale, "twitch.streamers_count", { count: dbData.streamers.length.toString() }), inline: true }
|
||||
)
|
||||
.setFooter({ text: t(locale, "twitch.managed_by", { bot: client.user?.displayName ?? "Bot" }) })
|
||||
.setTimestamp()
|
||||
|
||||
// Boutons première ligne - Toggle et configuration
|
||||
const toggleButton = new ButtonBuilder()
|
||||
.setCustomId(dbData.enabled ? "twitch_disable" : "twitch_enable")
|
||||
.setLabel(dbData.enabled ? t(locale, "common.disable") : t(locale, "common.enable"))
|
||||
.setStyle(dbData.enabled ? ButtonStyle.Danger : ButtonStyle.Success)
|
||||
.setEmoji(dbData.enabled ? "❌" : "✅")
|
||||
|
||||
const channelButton = new ButtonBuilder()
|
||||
.setCustomId("twitch_channel")
|
||||
.setLabel(t(locale, "twitch.common.configure_channel"))
|
||||
.setStyle(ButtonStyle.Secondary)
|
||||
.setEmoji("📺")
|
||||
|
||||
// Boutons seconde ligne - Gestion des streamers
|
||||
const listButton = new ButtonBuilder()
|
||||
.setCustomId("twitch_streamer_list")
|
||||
.setLabel(t(locale, "common.list"))
|
||||
.setStyle(ButtonStyle.Secondary)
|
||||
.setEmoji("📋")
|
||||
|
||||
const addButton = new ButtonBuilder()
|
||||
.setCustomId("twitch_streamer_add")
|
||||
.setLabel(t(locale, "common.add"))
|
||||
.setStyle(ButtonStyle.Primary)
|
||||
.setEmoji("➕")
|
||||
|
||||
const removeButton = new ButtonBuilder()
|
||||
.setCustomId("twitch_streamer_remove")
|
||||
.setLabel(t(locale, "common.remove"))
|
||||
.setStyle(ButtonStyle.Danger)
|
||||
.setEmoji("🗑️")
|
||||
|
||||
const components = [
|
||||
{
|
||||
type: ComponentType.ActionRow,
|
||||
components: [toggleButton, channelButton]
|
||||
},
|
||||
{
|
||||
type: ComponentType.ActionRow,
|
||||
components: [listButton, addButton, removeButton]
|
||||
}
|
||||
]
|
||||
|
||||
return { embed, components }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user