diff --git a/src/client/classes/Datastore.ts b/src/client/classes/Datastore.ts index 891b72c..3efa223 100644 --- a/src/client/classes/Datastore.ts +++ b/src/client/classes/Datastore.ts @@ -1,34 +1,21 @@ import { Guild } from 'discord.js'; -import fs, { NoParamCallback } from 'fs'; +import fs from 'fs'; import path from 'path'; +import { state } from './Radio'; +import { statistics } from './Statistics'; -interface entry { +export interface datastore { guild: { id: string, name?: string }, - statistics: { - [key: string]: { - "time": number, - "used": number - } - } | {}, - state: { - channels: { - "text": string, - "voice": string - }, - date: string, - station: { - name: string, - owner: string - } - } | {}, + statistics: statistics | {}, + state: state | {}, updated?: string } -export default class { - map: Map; +export default class Datastore { + map: Map; constructor() { this.map = new Map(); this.loadData(); @@ -66,7 +53,7 @@ export default class { } createEntry(id: string){ - let newData: entry = { + let newData: datastore = { guild: { id: id, }, @@ -89,7 +76,7 @@ export default class { return this.map.get(id); } - updateEntry(guild: Guild | { id: string, name: string }, newData: entry) { + updateEntry(guild: Guild | { id: string, name: string }, newData: datastore) { newData.guild.name = guild.name; let date = new Date(); @@ -100,32 +87,12 @@ export default class { //this.showEntry(this.getEntry(guild.id)); } - showEntry(data : entry){ + showEntry(data : datastore){ console.log(data); } - createTestFile () { - let newData = { - "guild": { - "id": "test", - "name": "Test" - }, - "statistics": { - "test": { - "time": 0, - "used": 0 - } - }, - "state": { - - } - } - - this.updateEntry(newData.guild, newData); - } - - saveEntry(file: string, data: entry) { - fs.writeFile(path.join(path.dirname(__dirname), '../../datastore') + "/" + file + ".json", JSON.stringify(data, null, 4), 'utf8', function(err: any) { + saveEntry(file: string, data: datastore) { + fs.writeFile(path.join(path.dirname(__dirname), '../../datastore') + "/" + file + ".json", JSON.stringify(data, null, 4), 'utf8', function(err: NodeJS.ErrnoException | null) { if (err) { } diff --git a/src/client/classes/Radio.ts b/src/client/classes/Radio.ts index 2137211..06020ef 100644 --- a/src/client/classes/Radio.ts +++ b/src/client/classes/Radio.ts @@ -1,27 +1,40 @@ -import { Guild, GuildMember, TextBasedChannel, VoiceBasedChannel, VoiceChannel } from "discord.js"; +import { Channel, Collection, GuildMember, OAuth2Guild, TextBasedChannel, VoiceBasedChannel, VoiceChannel } from "discord.js"; import { getVoiceConnection, joinVoiceChannel, VoiceConnection } from "@discordjs/voice"; import RadioClient from "../../Client"; import { station } from "./Stations"; +import { datastore } from "./Datastore"; export interface radio { - textChannel: TextBasedChannel | null, - voiceChannel: VoiceBasedChannel, + textChannel: Channel | TextBasedChannel | undefined | null, + voiceChannel: Channel | VoiceBasedChannel | undefined, connection: VoiceConnection | null, message: null, station: station, - datastore?: any, + datastore?: datastore, currentTime?: number, startTime: number, playTime?: number, } +export interface state extends Object { + channels: { + "text": string, + "voice": string + }, + date: string, + station: { + name: string, + owner: string + } +} + export default class Radio extends Map { constructor() { super(); } - save(client: RadioClient) { + save(client: RadioClient): void { let currentRadios = this.keys(); let radio = currentRadios.next(); @@ -29,7 +42,7 @@ export default class Radio extends Map { let currentRadio = this.get(radio.value); if (currentRadio) { - currentRadio.guild = client.datastore?.getEntry(radio.value).guild; + currentRadio.guild = client.datastore?.getEntry(radio.value)?.guild; client.statistics?.update(client, currentRadio.guild, currentRadio); client.funcs.saveState(client, currentRadio.guild, currentRadio); @@ -42,29 +55,32 @@ export default class Radio extends Map { } } - restore(client: RadioClient, guilds: any) { + restore(client: RadioClient, guilds: Collection): void { if(!client.stations) return; - guilds.forEach(async (guild: Guild) => { + guilds.forEach(async (guild: OAuth2Guild) => { let state = client.funcs.loadState(client, guild); + if(!state) return; - if(!state.station || !state.channels.voice || !state.channels.text) return; + if(!state.hasOwnProperty('station') || !state.hasOwnProperty('channels')) return; + let voiceChannel = client.channels.cache.get(state.channels.voice); if(!voiceChannel || !(voiceChannel instanceof VoiceChannel)) return; if(voiceChannel.members.filter((member: GuildMember) => !member.user.bot).size === 0) return; - - const sstation = await client.stations?.search(state.station.name, "direct"); + const sstation = client.stations?.search(state.station.name, "direct"); let station = sstation; if(!station) return; - const construct: any = { + let date = new Date(); + const construct: radio = { textChannel: client.channels.cache.get(state.channels.text), voiceChannel: client.channels.cache.get(state.channels.voice), connection: null, message: null, - station: station + station: station, + startTime: date.getTime() }; this.set(guild.id, construct); @@ -81,6 +97,7 @@ export default class Radio extends Map { let date = new Date(); construct.startTime = date.getTime(); client.datastore?.checkEntry(guild.id); + //@ts-ignore client.funcs.play(client, null, guild, station); } catch (error) { console.log(error); diff --git a/src/client/classes/Stations.ts b/src/client/classes/Stations.ts index a1b7fa1..637b413 100644 --- a/src/client/classes/Stations.ts +++ b/src/client/classes/Stations.ts @@ -1,62 +1,44 @@ -const _importDynamic = new Function('modulePath', 'return import(modulePath)'); -// @ts-ignore -const fetch = (...args: any) => _importDynamic('node-fetch').then(({default: fetch}) => fetch(...args)); import logger from "../funcs/logger"; export interface station { name: string, owner: string, logo: string, - stream: any + stream: { + [key: string]: string + } } export default class Stations extends Array { - logger: any; + counter: number; constructor() { super(); + this.counter = 0; } - async fetch(options: any){ + async fetch(options: { url: string, show?: boolean }){ try { logger('Stations', 'Started fetching list - ' + options.url); - let list = await fetch(options.url) + let stations = await fetch(options.url) .then(this.checkFetchStatus) - .then((response: { json: () => station; }) => response.json()); + .then((response: { json: () => station[]; }) => response.json()); - if(list){ - this.length = 0; - list.forEach((station: station) => { - try { - this.push(station); - } catch (error) { - - } - }); - - if(options.show){ - list.forEach((station: { name: any; }) => { - logger('Stations', station.name); - }); - } - - list.forEach(async (station: station) => { - try { - let stationTest = await fetch(station.stream[station.stream.default]); - if(stationTest.ok === true) return; - this.splice(this.indexOf(station),1); - } catch (error) { - this.splice(this.indexOf(station),1); - } - }); + if(!stations) return; + for (const station of stations){ + this.push(station.name); + if(options.show) logger('Stations', station.name); } + this.counter = 0; + logger('Stations', 'Successfully fetched list'); } catch (error) { logger('Stations', 'Fetching list failed'); console.error(error + "\n"); - if(this.length == 0) this.fetch(options); + this.counter = this.counter + 1; + if(this.length == 0 && 5 < this.counter) this.fetch(options); } } @@ -126,21 +108,23 @@ export default class Stations extends Array { } } } - let highestProbabilityStation : any | { station: string, name: string, probability: number } | string | null = null; + /*let highestProbabilityStation : { station: string, name: string, probability: number } | string | undefined; for (let i = 0; i < foundStations.length; i++) { if ( !highestProbabilityStation || + //@ts-ignore highestProbabilityStation.probability < foundStations[i].probability ) highestProbabilityStation = foundStations[i]; if ( highestProbabilityStation && + //@ts-ignore highestProbabilityStation.probability === foundStations[i].probability ) { highestProbabilityStation = foundStations[i].station; } } - return highestProbabilityStation; + return highestProbabilityStation;*/ } } }; diff --git a/src/client/classes/Statistics.ts b/src/client/classes/Statistics.ts index 7f52bbd..438e5c0 100644 --- a/src/client/classes/Statistics.ts +++ b/src/client/classes/Statistics.ts @@ -2,8 +2,17 @@ import { Guild } from "discord.js"; import RadioClient from "../../Client"; import { radio } from "./Radio"; +export interface statistics { + [key: string]: statistic +} + +interface statistic { + "time": number, + "used": number +} + export default class Statistics { - map: Map; + map: Map; constructor() { this.map = new Map(); @@ -16,19 +25,26 @@ export default class Statistics { radio.datastore = client.datastore?.getEntry(guild.id); + //@ts-ignore if(!radio.datastore.statistics[radio.station.name]){ - radio.datastore.statistics[radio.station.name] = {}; - radio.datastore.statistics[radio.station.name].time = 0; - radio.datastore.statistics[radio.station.name].used = 0; + //@ts-ignore + radio.datastore.statistics[radio.station.name] = { + time: 0, + used: 0 + }; + //@ts-ignore client.datastore?.updateEntry(guild, radio.datastore); } let date = new Date(); radio.currentTime = date.getTime(); radio.playTime = radio.currentTime - radio.startTime; + //@ts-ignore radio.datastore.statistics[radio.station.name].time = parseInt(radio.datastore.statistics[radio.station.name].time) + radio.playTime; + //@ts-ignore radio.datastore.statistics[radio.station.name].used = parseInt(radio.datastore.statistics[radio.station.name].used)+1; + //@ts-ignore client.datastore?.updateEntry(guild, radio.datastore); this.calculateGlobal(client); } @@ -39,7 +55,7 @@ export default class Statistics { let guilds = client.datastore.map.keys(); let stations = client.stations; - let statistics : any = {}; + let statistics : statistics = {}; if(!client.stations) return; @@ -50,14 +66,18 @@ export default class Statistics { if(calculation.value != 'global'){ if(stations){ for(const station of stations) { - if(currentGuild.statistics[station.name] && currentGuild.statistics[station.name].time && parseInt(currentGuild.statistics[station.name].time) != 0 && currentGuild.statistics[station.name].used && parseInt(currentGuild.statistics[station.name].used) != 0){ + //@ts-ignore + if(currentGuild.statistics[station.name] && currentGuild.statistics[station.name]?.time && parseInt(currentGuild.statistics[station.name].time) != 0 && currentGuild.statistics[station.name].used && parseInt(currentGuild.statistics[station.name].used) != 0){ if(!statistics[station.name]){ - statistics[station.name] = {}; - statistics[station.name].time = 0; - statistics[station.name].used = 0; + statistics[station.name] = { + time: 0, + used: 0 + }; } + //@ts-ignore statistics[station.name].time = parseInt(statistics[station.name].time)+parseInt(currentGuild.statistics[station.name].time); + //@ts-ignore statistics[station.name].used = parseInt(statistics[station.name].used)+parseInt(currentGuild.statistics[station.name].used); } } @@ -66,11 +86,13 @@ export default class Statistics { calculation = guilds.next(); } - let newData : any = {}; - newData.guild = {}; - newData.guild.id = "global"; - newData.guild.name = "global"; - newData.statistics = statistics; + let newData = { + guild: { + id: "global", + name: "global" + }, + statistics: statistics + }; client.datastore.updateEntry(newData.guild, newData); } diff --git a/src/client/classes/Streamer.ts b/src/client/classes/Streamer.ts index 1da2190..6a55421 100644 --- a/src/client/classes/Streamer.ts +++ b/src/client/classes/Streamer.ts @@ -4,7 +4,7 @@ import RadioClient from "../../Client"; import { station } from "./Stations"; export default class Streamer { - map: any; + map: Map; mode: "auto" | "manual" = "manual"; constructor() { @@ -37,12 +37,11 @@ export default class Streamer { refresh(client: RadioClient){ this.init(client); - let streamers = this.map.keys(); - streamers.forEach((streamer: any) => { + for (const streamer of this.map.keys()){ if(client.stations?.findIndex((station: station) => station.name == streamer) == -1){ this.stop(streamer); } - }); + } } play(station: station) { @@ -94,14 +93,14 @@ export default class Streamer { return audioPlayer; } - stop(station: station){ - let audioPlayer = this.map.get(station.name); + stop(streamer: string){ + let audioPlayer = this.map.get(streamer); if(audioPlayer){ - logger('Streamer', station.name + " / " + "Stop"); + logger('Streamer', streamer + " / " + "Stop"); audioPlayer.removeAllListeners(); audioPlayer.stop(); } - this.map.delete(station.name); + this.map.delete(streamer); } listen(station: station) { @@ -113,7 +112,7 @@ export default class Streamer { leave(client: RadioClient) { if(!client.stations) return; client.stations.forEach((station: station) => { - this.stop(station); + this.stop(station.name); }); } }; diff --git a/src/client/commands/maintenance.ts b/src/client/commands/maintenance.ts index eb20c3a..7985e33 100644 --- a/src/client/commands/maintenance.ts +++ b/src/client/commands/maintenance.ts @@ -1,10 +1,7 @@ -import { ActionRowBuilder, AnyComponentBuilder, APIActionRowComponent, APISelectMenuOption, ButtonInteraction, ChatInputCommandInteraction, ColorResolvable, EmbedBuilder, StringSelectMenuBuilder, StringSelectMenuInteraction } from "discord.js"; +import { ActionRowBuilder, APISelectMenuOption, ButtonInteraction, ChatInputCommandInteraction, ColorResolvable, EmbedBuilder, StringSelectMenuBuilder, StringSelectMenuInteraction } from "discord.js"; import RadioClient from "../../Client"; import Streamer from "../classes/Streamer"; import commands from "../commands"; -const _importDynamic = new Function('modulePath', 'return import(modulePath)'); -// @ts-ignore -const fetch = (...args) => _importDynamic('node-fetch').then(({default: fetch}) => fetch(...args)); export default { name: 'maintenance', diff --git a/src/client/events/ready.ts b/src/client/events/ready.ts index b36ebf5..3defdb1 100644 --- a/src/client/events/ready.ts +++ b/src/client/events/ready.ts @@ -1,10 +1,11 @@ import RadioClient from "../../Client"; -import Datastore from "../classes/Datastore"; +import Datastore, { datastore } from "../classes/Datastore"; import Radio from "../classes/Radio"; import Stations from "../classes/Stations"; import Streamer from "../classes/Streamer"; import Statistics from "../classes/Statistics"; import commands from "../commands"; +import { OAuth2Guild } from "discord.js"; export default async function ready(client: RadioClient) { client.funcs.logger("Bot", "Ready"); @@ -13,7 +14,7 @@ export default async function ready(client: RadioClient) { client.funcs.logger('Datastore', 'Initialize'); client.datastore = new Datastore(); - client.datastore.map.forEach((datastore: { guild: { id: string; name: string; }; }) => { + client.datastore.map.forEach((datastore: datastore) => { client.funcs.logger('Datastore', datastore.guild.id + " / " + datastore.guild.name); }); @@ -45,7 +46,7 @@ export default async function ready(client: RadioClient) { client.funcs.logger('Guilds', 'Started fetching list'); let guilds = await client.guilds.fetch(); - guilds.forEach((guild: { id: string; name: string; }) => { + guilds.forEach((guild: OAuth2Guild) => { client.funcs.logger('Guilds', guild.id + " / " + guild.name); }); diff --git a/src/client/funcs/loadState.ts b/src/client/funcs/loadState.ts index cc69fd6..a2922f8 100644 --- a/src/client/funcs/loadState.ts +++ b/src/client/funcs/loadState.ts @@ -1,15 +1,12 @@ -import { Guild } from "discord.js"; +import { OAuth2Guild } from "discord.js"; import RadioClient from "../../Client"; -export default function loadState(client: RadioClient, guild: Guild){ +export default function loadState(client: RadioClient, guild: OAuth2Guild) { if(!client.datastore) return; let data = client.datastore.getEntry(guild.id); if(!data) return; - let state; - - state = data.state; + let state = data.state; if(!state) return; - data.state = {}; client.datastore.updateEntry(guild, data); return state; diff --git a/src/client/funcs/play.ts b/src/client/funcs/play.ts index c46131b..9738730 100644 --- a/src/client/funcs/play.ts +++ b/src/client/funcs/play.ts @@ -1,8 +1,8 @@ -import { ActionRowBuilder, ButtonBuilder, ButtonStyle, ChatInputCommandInteraction, ColorResolvable, EmbedBuilder, Guild, StringSelectMenuInteraction } from "discord.js"; +import { ActionRowBuilder, ButtonBuilder, ButtonStyle, ChatInputCommandInteraction, ColorResolvable, EmbedBuilder, Guild, OAuth2Guild, StringSelectMenuInteraction } from "discord.js"; import RadioClient from "../../Client"; import { station } from "../classes/Stations"; -export default async function play(client: RadioClient, interaction: ChatInputCommandInteraction | StringSelectMenuInteraction | null, guild: Guild | null, station: station) { +export default async function play(client: RadioClient, interaction: ChatInputCommandInteraction | StringSelectMenuInteraction | null, guild: OAuth2Guild | Guild | null, station: station) { if(!guild) return; const radio = client.radio?.get(guild.id); diff --git a/src/client/funcs/saveState.ts b/src/client/funcs/saveState.ts index 9634ccb..63a6dd5 100644 --- a/src/client/funcs/saveState.ts +++ b/src/client/funcs/saveState.ts @@ -9,15 +9,18 @@ export default function saveState(client: RadioClient, guild: Guild, radio: radi let date = new Date(); let data = client.datastore.getEntry(guild.id); - - data.state = {}; - data.state.channels = {}; - data.state.channels.text = radio.textChannel?.id; - data.state.channels.voice = radio.voiceChannel.id; - data.state.date = date.toISOString(); - data.state.station = {}; - data.state.station.name = radio.station.name; - data.state.station.owner = radio.station.owner; + if(!data) return; + data.state = { + channels: { + text: radio.textChannel?.id, + voice: radio.voiceChannel?.id + }, + date: date.toISOString(), + station: { + name: radio.station.name, + owner: radio.station.owner + } + }; client.datastore.updateEntry(guild, data); }