const path = require('path');
const find = require('findit');
const {Client,ClientOptions,GatewayIntentBits,Message, Partials, ActivityType,EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle} = require('discord.js')
const roleRulesM = require('./models/autoRoleRule');
const feedM = require('./models/feeds');
const xmlparser = require('xml-js')
const GuildM = require('./models/guilds');
const Spawner = require('child_process');
const mongoose = require('mongoose');

module.exports.GatewayIntentBits = GatewayIntentBits;
module.exports.Partials = Partials;
module.exports.ActivityType = ActivityType;
module.exports.EmbedBuilder = EmbedBuilder;
module.exports.ActionRowBuilder = ActionRowBuilder;
module.exports.ButtonBuilder = ButtonBuilder;
module.exports.ButtonStyle = ButtonStyle;
module.exports.Message = Message;

//@ts-check
class CommandOptions 
{
    /**
     * 
     * @param {String} name 
     * @param {String[]} aliases 
     * @param {String} description 
     * @param {Boolean} needsAdmin 
     * @param {Boolean} hidden 
     */
    constructor (name, aliases, description, needsAdmin, hidden)
    {
        this.name = name;
        this.aliases = aliases;
        this.description = description;
        this.needsAdmin = needsAdmin;
        this.hidden = hidden;
    }
}
class command 
{
    /**
     * @param {_Client} client extends discord.js.Client
     * @param {CommandOptions} options extends Lib.Options
     */
    constructor(client, options)
    {
        this.name = options.name;
        this.aliases =  options.aliases;
        this.description = options.description;
        this.needsAdmin =options.needsAdmin;
        this.hidden = options.hidden;
        this.client = client;
    }
}module.exports.Command = command;


class _Client extends Client
{
    /**
     * 
     * @param {ClientOptions} options 
     */
    commands = new Map();
    constructor(options, mongoDBURI)
    {
        super(options);
        let finder = find(path.resolve('commands'));
        finder.on('file', file=>
        {
            let command = require(file);
            if(typeof command === 'function')
            {
                let c = new command(this);
                
                this.commands.set(c.name,{
                    needsAdmin: c.needsAdmin?c.needsAdmin:false,
                    command: command,
                });
                if(c.aliases)
                {
                    for(var i =0; i<c.aliases.length+1; i++)
                    {
                        this.commands.set(c.aliases[i],{
                            needsAdmin: c.needsAdmin,
                            command: command,
                        });
                    }
                }
            }
        })
        
        finder.on('end', ()=>
        {
            this.enableCommands();
        })
        connectToDB();
        function connectToDB()
        {
            try {
                mongoose.connect(mongoDBURI);
            } catch (err) {
                console.log('Server: Error: There was an Error with the connection to the database, attempting to restart it.', err)
                connectToDB();
            }
        }
        mongoose.Promise = global.Promise;
        this.setGuilds();
        this.RoleSetter();
        this.rustCommits = new rustCommits(this);
        this.freegames = new FreeGames(this);
        this.strikes = new Strikes(this);
        this.on('disconnect', (event) => {
            console.log(`Client disconnected: ${event.reason}`);
        });
        this.on('reconnecting', () => {
            console.log('Client is reconnecting...');
        });
        this.YTFeed();
        this.music();
        setInterval(() => {
            this.YTFeed()
            this.music();
        }, 60*60*1000);
    }
    setGuilds()
    {
        this.on('ready', ()=>
        {

            this.guilds.cache.forEach(guild=>
            {
                GuildM.find({gID:guild.id})
                .then(
                    (guild_,err)=>
                    {
                        if(err || guild_.length==0)
                        {
                            var nGuild = GuildM();
                            nGuild.name = guild.name;
                            nGuild.memberCount = guild.members.cache.size;
                            nGuild.gID = guild.id;
                            nGuild.allowInvites = false;
                            nGuild.strikes = false;
                            nGuild.music = false;
                            nGuild.save(err=>
                                {
                                    if(err) console.log('Server: Adding non existing guilds to the DB. Error ocurred:', err)
                                });
                        }
                    }
                )
            })
        })
        this.on('guildCreate', guild=> 
        {
            var guildModel = new GuildM();
            guildModel.name = guild.name;
            guildModel.memberCount = guild.members.cache.size;
            guildModel.gID = guild.id;
            guildModel.allowInvites = false;
            guildModel.strikes = false;
            guildModel.music = false;
            guildModel.save()
        });
        this.on('guildDelete', guild => 
        {
            GuildM.find({gID:guild.id}).then(guild=>
                {
                    if(guild.length==0) return sendMessage(false, 'ID not on the list.')
                    GuildM.findOneAndRemove(guild.id, (err)=>
                    {
                        if(err) console.log('Server: Deleting guild from DB: There was an error:', err)
                    })
                })
        });


    }
    /**
     * Register a group of commands under the folder commands
     * @param {String} name - Name of the group.
     * @param {String} folderName - Name of the folder.
     */
    async enableCommands()
    {
        this.on("messageCreate", message=>
        {
            this.checkForInvites(message);
            if(message.content.startsWith(process.env.prefix)) //Test prefix t!
            {
                let commandName=message.content.split('!')[1].split(' ')[0];
                let args = message.content.split(' ');
                args = args.slice(1);
                if(this.commands.get(commandName))
                {
                    let needsAdmin = this.commands.get(commandName).needsAdmin;
                    let isAdmin =false;
                    try 
                    {
                        isAdmin = this.guilds.cache.get(message.guild.id).members.cache.get(message.author.id).permissions.has('ADMINISTRATOR');
                    } catch (error) 
                    {
                        console.log('Lib: Command: Permission Verification: Failed')
                    }
                    let command = this.commands.get(commandName).command
                    if((needsAdmin && isAdmin) || !needsAdmin) return   new command(this).run(message, args);
                    if(needsAdmin && !isAdmin) return new ErrorMessage(this).send(ErrorType.Permissions, message);
                }
                new ErrorMessage(this).send(ErrorType.NotOnTheList,message)
            }
        })
    }
    /**
     * 
     * @param {String} name - Command Name
     * @returns {command}
     */
    async RoleSetter ()
    {
        this.on('messageReactionAdd', (reaction, user)=>
            {
                console.log('Lib: Message: Reaction: Added');
                (async ()=>
                {
                    const rule = await roleRulesM.findOne({mID:reaction.message.id,roleEmoji:reaction._emoji.id}).then(rule =>{return rule}).catch(err=>{return null})
                    if(rule)
                    {
                        console.log(`RoleAssignment: Guild:${this.guilds.cache.get(rule.gID)}: Adding role ${rule.roleName} given to ${user.username}`)
                        this.channels.cache.get(reaction.message.channelId).members.get(user.id).roles.add(rule.roleID)
                    }
                })()
            })
            this.on('messageReactionRemove', (reaction, user)=>
            {
                (async ()=>
                {
                    const rule = await roleRulesM.findOne({mID:reaction.message.id,roleEmoji:reaction._emoji.id}).then(rule =>{return rule}).catch(err=>{return null})
                    if(rule)
                    {
                        console.log(`RoleAssignment: Guild:${this.guilds.cache.get(rule.gID)}: Removing ${user.username}'s ${rule.roleName} role. `)
                        this.channels.cache.get(reaction.message.channelId).members.get(user.id).roles.remove(rule.roleID)
                    }
                })()
            })
    }
    async YTFeed()
    {
        const feeds = await feedM.find().then(feeds=>{return feeds})
        if(feeds.length<1) return 
        for(var feed of feeds)
        {
            (async (feed)=>{
                if(!feed.YTChannelId) return
                if(feed.YTChannelId=='false') return
                var res = await fetch(`https://www.youtube.com/feeds/videos.xml?channel_id=${feed.YTChannelId}`)
                                .then(handleResponse)
                                .then(handleData)
                                .catch(handleError);
                                function handleResponse(response) 
                                {
                                return response.text()
                                }
                                function handleError(error) 
                                {
                                return error;
                                }
                                function handleData(data) 
                                {
                                    data = xmlparser.xml2json(data,
                                                    {
                                                        compact: true,
                                                        space: 4
                                                    });
                                    data = JSON.parse(data);
                                    return {
                                                link:data.feed.entry[0].link._attributes.href,
                                                published:data.feed.entry[0].published._text,
                                                link:data.feed.entry[0].link._attributes.href,
                                                image:data.feed.entry[0].link._attributes.href.split('=')[1],
                                                title:data.feed.entry[0].title._text,
                                                author:
                                                {
                                                    name:data.feed.entry[0].author.name._text,
                                                    url:data.feed.entry[0].author.uri._text
                                                }        
                                            }
                                } 
                if(!res) return false;
                const channel = this.channels.cache.get(feed.ChannelId);
                if(!channel) return false;
                var ytChannelName = await fetch('https://www.youtube.com/feeds/videos.xml?channel_id='+feed.YTChannelId,
                                        {
                                            method: "GET",
                                            mode: "cors", 
                                        })
                                        .then(response=>
                                            {
                                                if(response.ok) return response.text();
                                            })
                                        .then(data=>
                                            {
                                                data = xmlparser.xml2json(data,
                                                    {
                                                        compact: true,
                                                        space: 4
                                                    });
                                                    data = JSON.parse(data);
                                                return data.feed.author.name._text
                                            })
                                        .catch(error=>
                                            {
                                                return error;
                                            });
                console.log('Lib: YTFeed:',ytChannelName, res?.author?.name, channel.name,feed?.ChannelId ,channel.id)
                var aux = res;
                const lastSentMessage= await channel.messages.fetch().then(res=>
                    {
                        for(var item of res)
                        {
                            if(item[1].embeds)
                            {
                                if(item[1].embeds[0])
                                {
                                    var fields = item[1].embeds[0].fields.filter(x=>x.name === 'PublishedTimeStamp');
                                    var isFeed = fields.length>0;
                                    if(!aux.author) return null;
                                    if(isFeed && item[1].embeds[0].title == aux.author.name) return item[1].embeds[0];
                                }
                            }
                        }
                    })
                if(!lastSentMessage) return sendMessage(res, feed, channel);
                const lastSentMessagePublished = lastSentMessage.fields.filter(x=>x.name === 'PublishedTimeStamp')[0].value;
                // console.log(lastSentMessagePublished != res.published , 'lastSentPublished',lastSentMessagePublished,'published', res.published)
                if(!lastSentMessagePublished) sendMessage(res, feed, channel)
                else if(lastSentMessagePublished != res.published) sendMessage(res, feed, channel);
            })(feed);
        }
        function sendMessage(res, feed, channel)
        {
            if(!res?.author?.name) return 
            console.log('Lib: YTFeed: SendMessage: Res: Author: Name:', res?.author?.name)
            const embed = new EmbedBuilder()
            embed.setFooter({text:'Rem-chan on ', iconURL:"https://i.imgur.com/g6FSNhL.png"})
            embed.setAuthor({name:"Rem-chan", iconURL:"https://i.imgur.com/g6FSNhL.png",url:'https://rem.wordfights.com/addtodiscord'}); 
            embed.setColor(0x110809);
            embed.setTimestamp();
            embed.setURL(res?.author?.url)
            embed.setTitle(res?.author?.name);
            embed.setImage(`https://i3.ytimg.com/vi/${res?.image}/maxresdefault.jpg`)
            embed.setDescription(feed?.CostumMessage);
            embed.addFields({name:res?.title, value:' '},
                            {name:'PublishedTimeStamp', value:res?.published},
                            {name:'Link:', value:res?.link});
            channel.send({embeds:[embed]});
        }
    }
    async checkForInvites(message)
    {
        let content = message.content;
        let guild = message.guildId;
        if(!content?.includes('discord.gg')) return 

        await GuildM.find({gID:guild})
        .then((g, err)=>
        {
            if(err) return;
            if(!g.allowInvites) 
            {
                message.delete();
            }
            return;
        });
    }
    async music()
    {
        const ServersWithMusicOn = await this.serversWithMusicOn();
        const ISGONNABEOFFFORAWHILE = true
        if(!ServersWithMusicOn && !ISGONNABEOFFFORAWHILE) return 
        for(var server of ServersWithMusicOn)
        {
            if(!Childs[server.name])
            {
                console.log('Index: Starting Music Worker:', server.name,`(${server.gID})`)
                Childs[server.name] = Spawner.fork('./musicWorker.js',[server.gID, server.name]);
            }
            else
            {       //Health check
            (function (server)
            {
                    Childs[server.name].on('exit', ()=>
                    {
                        console.log('Child DIED, spawning another.')
                        Childs[server.name] = Spawner.fork('./musicWorker.js',[server.gID, server.name]);
                    })
                })(server);
            }

        } 
    }
    async serversWithMusicOn()
    {
        return await GuildM.find({music:true}).then(g=>{return g})
    }

}module.exports.Client = _Client;
const ErrorType = 
{
    Permissions: "Permissions",
    Arguments: "Arguments",
    NoArguments: "No Arguments Given",
    NotOnTheList: "Unknown Command",
    OldMessages: "Can't delete this messages."

}
module.exports.ErrorType = ErrorType;

class Channel 
{
    /**
     * 
     * @param {String} channelID
     */
    constructor(channelID)
    {
        this.channel = channelID;
    }

}

module.exports.Channel = Channel;
/**
 * 
 * @param {Number} length 
 * @returns {String}
 */
function  Random(length) 
{
    length?length:length=5;
    var result           = '';
    var characters       = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
    var charactersLength = characters.length;
    for ( var i = 0; i < length; i++ ) {
        result += characters.charAt(Math.floor(Math.random() * charactersLength));
    }
    return result;
}module.exports.Random = Random;

class ErrorMessage 
{
    /**
     * @param {_Client} client 
     */
    constructor(client)
    {
        this.client = client;
    }
    /**
     * 
     * @param {ErrorType} errorType 
     * @param {Message} message 
     * @param {[String]} extraMessages 
     */
   
    async send(errorType,message, extraMessages)
    {
        if (!message.channel.id) return;
        const embed = new EmbedBuilder()
        .setColor(0x4d0000)
        .setAuthor({name:"Rem-chan", iconURL:"https://i.imgur.com/g6FSNhL.png",url:'https://rem.wordfights.com/addtodiscord'})
        .setTimestamp()
        .setFooter({text:'Rem-chan on ', iconURL:"https://i.imgur.com/g6FSNhL.png"})
        .addFields({name: 'Error:', value: 'something'})
        .addFields({name: 'Try:', value: 'something'});
        
        switch (errorType) {
            case ErrorType.Arguments:
                embed.data.fields[0].value =ErrorType.Arguments;
                embed.data.fields[1].value = 'Verify the arguments provided.'
                break;
            case ErrorType.NoArguments:
                embed.data.fields[0].value =ErrorType.NoArguments;
                embed.data.fields[1].value = 'Provide the required arguments for this command.'
                break;
            case ErrorType.Permissions:
                embed.data.fields[0].value =ErrorType.Permissions;
                embed.data.fields[1].value = 'Ask this servers administrator to use the command.'
                break;
            case ErrorType.NotOnTheList:
                embed.data.fields[0].value =ErrorType.NotOnTheList;
                embed.data.fields[1].value = 'Use !help for the list of available.'
                break;
            case ErrorType.OldMessages:
                embed.data.fields[0].value =ErrorType.OldMessages;
                break;
            default:
                break;
        }
        if(extraMessages)
        {
            for(var i = 0; i<extraMessages.length; i++)
            {
                embed.addFields({name:'Tip:', value:extraMessages[i]});
            }
        }
        const randomID =  Random();
        const row = new ActionRowBuilder()
        .addComponents(
                new ButtonBuilder()
                .setCustomId(randomID)
                .setLabel('Remove this.')
                .setStyle(ButtonStyle.Primary)
                .setEmoji('❌')
        );
        await this.client.channels.cache.get(message.channel.id).send({ephemeral: true, embeds: [embed], components: [row] }); 
    
        const filter = i => i.customId === randomID;

        const collector = message.channel.createMessageComponentCollector({ filter, time: 60000 });

        collector.on('collect', async m => 
        {
            message.delete().then(()=>m.message.delete())
            .catch(()=>m.message.delete());
        });
    }

}module.exports.ErrorMessage = ErrorMessage;

/**
 * Anilist Client -> Search Functions
 */
class AnimeInfo
{
    /**
     * 
     * @param {String} title 
     * @param {String} status 
     * @param {String} url
     * @param {String} episodes 
     * @param {String} trailer 
     * @param {String} description 
     * @param {String} coverImage 
     */
    constructor(title, status, episodes, url , trailer, description, coverImage)
    {
        this.title = title; 
        this.status = status;
        this.url = url
        this.episodes=episodes;
        this.trailer = trailer;
        this.description = description;
        this.coverImage = coverImage;
    }
}
class CharInfo
{
    /**
     * 
     * @param {String} name 
     * @param {String} gender 
     * @param {String} image 
     * @param {String} url
     * @param {String} description 
     */
    constructor(name, gender, image, description)
    {
        this.name = name; 
        this.gender = gender;
        this.url = url
        this.image=image;
        this.description = description;
    }
}
class MangaInfo
{
    /**
     * 
     * @param {String} title 
     * @param {String} status 
     * @param {String} url
     * @param {String} description 
     * @param {String} coverImage 
     */
    constructor(title, status, description, coverImage)
    {
        this.title = title; 
        this.status = status;
        this.url = url
        this.description = description;
        this.coverImage = coverImage;  
    }
}
class Options
{
    /**
     * 
     * @param {String} method 
     * @param {String} headers 
     * @param {String} body 
     */
    constructor(method, headers, body)
    {

        this.method = method;
        this.headers = headers;
        this.body = body;  
    }
}

class AnilistCli {
    /**
     * 
     * @param {Options} options 
     * @returns {Info}
     */
    async getInfo(options)
    {
        return await fetch('https://graphql.Anilist.co', options).then(handleResponse)
                     .then(handleData)
                     .catch(handleError);
          function handleResponse(response) 
          {
              return response.json().then(function (json) 
              {
                  return response.ok ? json : Promise.reject(json);
              });
          }
          function handleData(data)
          {
            return data
          }
          function handleError(error) {
              return error;
          }
    }
    /**
     * 
     * @param {String} ss - Search Query
     * @returns {AnimeInfo} 
     */
    async searchAnime(ss)
    {
        var variables = {
            ss,
            "page": 0,
            "perPage": 4
        };
        var query = `query ($ss: String, $page: Int, $perPage: Int) {
            Page(page: $page, perPage: $perPage) {
              media(search: $ss, type: ANIME) {
                id
                title {
                  romaji
                  english
                }
                coverImage {
                    medium
                }
              }
            }
          }
          `
          var options = {
              method: 'POST',
              headers: {
                  'Content-Type': 'application/json',
                  'Accept': 'application/json', 
              },
              body: JSON.stringify({
                  query,
                  variables
              })
          };
        var data =  await this.getInfo(options);

        data = data.data.Page.media
        if(!data[0]) return 'Error'
        var aux = [];
        for(var anime of data)
        {
            aux.push({
                title:anime.title,
                id:anime.id,
                coverImage:anime.coverImage.medium
            });
        }
            return aux;
    }   
    async getAnimeInfo(id)
    {
        var variables = {
            id
        };
        var query = `query ($id: Int) {
            Media(id:$id) {
              id
              idMal
              title {
                romaji
                english
                native
              }
              siteUrl
              status
              episodes
              season
              seasonYear
              trailer {
                site
                id
              }
              description(asHtml:false)
              coverImage {
                large
              }
            }
          }`;
        var options = {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'Accept': 'application/json', 
            },
            body: JSON.stringify({
                query,
                variables
            })
        };
        var data =  await this.getInfo(options);
        data = data.data;
        return {
            title:data.Media.title,
            malLink:'https://myanimelist.net/anime/'+data.Media.idMal,
            status:data.Media.status,
            url:data.Media.siteUrl,
            episodes:data.Media.episodes,
            trailer:data.Media.trailer,
            description:data.Media.description,
            coverImage:data.Media.coverImage.large,
            season: (data.Media.season?data.Media.season:'') + ' ' + (data.Media.seasonYear?data.Media.seasonYear:'')
        }        
    }
    /**
     * 
     * @param {String} ss - Search Query
     * @returns {CharInfo}
     */
    async searchChar(ss)
    {
        var variables = {
            ss,
            "page": 0,
            "perPage": 4
        };
        var query = `query ($ss: String, $page: Int, $perPage: Int) {
            Page(page: $page, perPage: $perPage) {
              characters(search: $ss, sort: SEARCH_MATCH) {
                id
                name {
                  full
                  native
                }
                siteUrl
                gender
                image {
                  medium
                }
              }
            }
          }
          `;
        var options = {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'Accept': 'application/json',
            },
            body: JSON.stringify({
                query,
                variables
            })
        };
        var data =  await this.getInfo(options);
        var chars = data.data.Page.characters
        var aux = [];
        if(!chars[0]) return 'Error'
        for(var char of chars)
        {
            aux.push({
                id:char.id,
                name:char.name,
                gender:char.gender,
                url:char.siteUrl,
                image:char.image.medium
            })
        }
        return aux;
    }
    async getCharInfo(id)
    {
        
        var variables = {
            id
        };
        var query = `query ($id: Int) {
            Character(id: $id, ) {
              id
              name {
                full
                native
              }
              gender
              age
              siteUrl
              image {
                medium
              }
              description(asHtml:false)
            }
          }
          `;
        var options = {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'Accept': 'application/json',
            },
            body: JSON.stringify({
                query,
                variables
            })
        };
        var data =  await this.getInfo(options);
        data = data.data;
        return {
            name:data.Character.name,
            gender:data.Character.gender,
            url:data.Character.siteUrl,
            image:data.Character.image.medium,
            description:data.Character.description,
            age:data.Character.age,
        }

        
    }
    /**
     * 
     * @param {String} ss - Search Query
     * @returns {MangaInfo}
     */
    async searchManga(ss)
    {
        var variables = 
        {
            ss,
            "page": 0,
            "perPage": 4
        }
        var query = `query ($ss: String, $page: Int, $perPage: Int) {
            Page(page: $page, perPage: $perPage) {
              media(search: $ss, type: MANGA) {
                id
                title {
                  romaji
                  english
                }
                coverImage {
                  medium
                }
              }
            }
          }`
        var options = {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'Accept': 'application/json',
            },
            body: JSON.stringify({
                query,
                variables
            })
        };
        var data =  await this.getInfo(options);
        var mangas = data.data.Page.media;
        var aux = [];
        if(!mangas[0]) return 'Error'
        for(var manga of mangas)
        {
            aux.push({
                id:manga.id,
                title:manga.title,
                image:manga.coverImage.medium
            })
        }
        return aux;
        
    }   
    async getMangaInfo(id)
    {  
        var variables = {
            id
        };
        var query = `query ($id: Int) {
            Media(id: $id) {
              title {
                romaji
                english
                native
              }
              siteUrl
              status
              description(asHtml: false)
              coverImage {
                large
              }
            }
          }
          `;
        var options = {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'Accept': 'application/json',
            },
            body: JSON.stringify({
                query,
                variables
            })
        };
        var data =  await this.getInfo(options);
        data = data.data;
        return {
            title:data.Media.title,
            status:data.Media.status,
            url:data.Media.siteUrl,
            description:data.Media.description,
            coverImage:data.Media.coverImage.large,
        }     
    }

}module.exports.Anilist = AnilistCli;

const channelM = require('./models/channels');

class rustCommits
{
    /**
     * 
     * @param {_Client} client 
     */
    constructor(client)
    {
        this.client = client;
        this.update();
        setInterval(() => {
            this.update();
        }, 3*60*1000);
        this.channels;
    }
    async update ()
    {
        const channels = await channelM.find({for:'rust'}).then(channels=>{return channels});
        this.channels = await this.resolveChannels(channels);
        const Res = await fetch("https://commits.facepunch.com/?format=json").then(handleResponse)
                   .then(handleData)
                   .catch(handleError);
        function handleResponse(response) 
        {
            return response.json().then(function (json) 
            {
                return response.ok ? json : Promise.reject(json);
            });
        }
        function handleError(error) {
            return error;
        }
        function handleData(data) {
            data.results.splice(30, 20);
            return data.results.filter(x=>x.repo.search(/rust/i)!=-1)
        }
        if(!Res.length)
        {
            //console.log('Framework: Rustcommits: Res', Res);
            return this.update();
        }
        for(var j = 0; j<this.channels.length; j++)
        {
            if(!this.channels[j].lastid) this.channels[j].lastid = 0;
            const Pos = Res.map(e => e.id).indexOf(this.channels[j].lastid);
            Res.splice(Pos, Res.length-Pos);
            for(var i = Res.length-1; i>0; i--)
            {
                if(Res[i].id > this.channels[j].lastid)
                {
                    send(
                    {
                        Author:Res[i].user.name,
                        Avatar:Res[i].user.avatar,
                        Time:Res[i].created.split("T")[1]+ " of "+ Res[i].created.split("T")[0],
                        Content:Res[i].message,
                        ID:Res[i].id,
                        Repo:Res[i].repo,
                        Branch:Res[i].branch
                    }, this.channels[j].cID, this.client);
                }
            }
        }
        /**
         * 
         * @param {RustCommit} commit 
         * @param {Channel} channel 
         */
        function send(commit, channel, client)
        {
            client.channels.cache.get(channel).send({embeds:[
                new EmbedBuilder()
                .setColor(0xc23811)
                .setTitle(commit.Time)
                .setURL('https://commits.facepunch.com')
                .setAuthor({name:commit.Author, iconURL:commit.Avatar==''?"https://i.imgur.com/g6FSNhL.png":commit.Avatar,url:`https://commits.facepunch.com/${commit.Author.split(' ').join('')}`})
                .setDescription(commit.Content)
                .addFields(
                    { name:`${commit.Repo}`, value:`[${commit.Repo}/${commit.Branch}](https://commits.facepunch.com/r/${commit.Repo}/${commit.Branch.split(' ')? commit.Branch.split(' ').join('%20'):commit.Branch} 'Branch Link')`},
                    { name: 'ID', value: commit.ID.toString() },
                    )
                .setTimestamp()
                .setFooter({text:'Rem-chan on ', iconURL:"https://i.imgur.com/g6FSNhL.png"})
            ]});
        }
        
    }
     /**
     * 
     * @param {channelM} channels 
     */
      async resolveChannels(channels)
      {
          for await(var channel of channels )
          {
              if(this.client.channels.cache.get(channel.cID))
              {
                  const Channel = this.client.channels.cache.get(channel.cID)
                  const lastid = await Channel.messages.fetch({limit:1}).then(message=>
                      {
                            if(!message) return 0
                            const [content] = message.values();
                            if(!content||!content.embeds||!content.embeds.length>0) return 0;
                            return parseInt(content.embeds[0].fields[1].value)
                      })
                      channel['lastid'] = lastid;  
              }
          }
          return channels;
      }
}
class RustCommit 
{
    /**
     * 
     * @param {String} Author 
     * @param {String} Avatar - IMAGE URL
     * @param {String} Time 
     * @param {String} Content 
     * @param {Number} ID 
     * @param {String} Repo 
     * @param {String} Branch 
     */
    constructor(Author, Avatar, Time, Content, ID, Repo, Branch)
    {
        this.Author = Author
        this.Avatar = Avatar
        this.Time = Time
        this.Content = Content
        this.ID = ID
        this.Repo = Repo
        this.Branch = Branch
    }
}
class FreeGames
{
    /**
     * 
     * @param {_Client} client 
     */
    constructor(client)
    {
        this.client = client;
        this.update()
        setTimeout(() => {
            this.update();
        }, 3*60*1000);
    }
    async update()
    {
        const channels = await channelM.find({for:'freegames'}).then(channels=>{return channels});
        this.channels = await this.resolveChannels(channels);
        
        const Res = await fetch('https://www.gamerpower.com/api/giveaways').then(handleResponse)
            .then(handleData)
            .catch(handleError);
        function handleResponse(response) 
        {
            return response.json().then(function (json) 
            {
                    return response.ok ? json : Promise.reject(json);
            });
        }
        function handleError(error) {
            return error;
        }
        function handleData(data) {
            data.splice(10, data.length-10);
            return data
        }
        //if(!Res.length) console.log('FrameWork -> FreeGames: Res:',Res);
        if(!Res.length) return setTimeout(() => 
        {
            this.update();
        }, 60*1000); 
        for(var j = 0; j<this.channels.length; j++)
        { 
            const Pos = Res.map(e => e.id).indexOf(this.channels[j].lastid);
            Res.splice(Pos, Res.length-Pos)

            for(var i = Res.length-1; i>0; i--)
            {
                if(Res[i].id > this.channels[j].lastid)
                {
                    send(
                    {
                        ID:Res[i].id, //
                        Title:Res[i].title, //
                        Type:Res[i].type, //
                        Thumb:Res[i].thumbnail, //
                        Image:Res[i].image, //
                        Description:Res[i].description, //
                        Instructions:Res[i].instructions,//
                        URL:Res[i].open_giveaway_url,//
                        Platform:Res[i].platforms,//
                        EndDate:Res[i].end_date,
                        
                    }, this.channels[j].cID, this.client);
                }
            }
        }
        /**
         * 
         * @param {FreeGameModel} game 
         * @param {Channel} channel 
         */
        function send(game, channel, client)
        {
            client.channels.cache.get(channel).send({embeds:[
                new EmbedBuilder()
                .setColor(0x2d9134)
                .setURL(game.URL)
                .setTitle(game.Title)
	            .setAuthor({name:"Rem-chan", iconURL:"https://i.imgur.com/g6FSNhL.png",url:'https://rem.wordfights.com/addtodiscord'})
                .setDescription(game.Description)
                .setImage(game.Image)
                .setThumbnail(game.Thumb)
                .addFields(
                    { name: 'Platforms', value: game.Platform},
                    { name: 'Type of offer:', value: game.Type},
                    { name:'Instructions:', value:game.Instructions},
                    { name:`End date:`, value:`${game.EndDate}`},
                    )
                .setTimestamp()
                .setFooter({text:`(${game.ID}) Rem-chan on `, iconURL:"https://i.imgur.com/g6FSNhL.png"})
            ]});
        }
    }
    /**
     * 
     * @param {channelM} channels 
     */
    async resolveChannels(channels)
    {
        for await(var channel of channels )
        {
            if(this.client.channels.cache.get(channel.cID))
            {
                const Channel = this.client.channels.cache.get(channel.cID)
                const lastid = await Channel.messages.fetch({limit:1}).then(message=>
                    {
                        if(!message) return 0
                        const [content] = message.values();
                        if(!content||!content.embeds||!content.embeds.length>0) return 0;
                        return parseInt(content.embeds[0].footer.text.split('(')[1].split(')')[0])
                    })
                    channel.lastid = lastid;  
            }
        }
        return channels;
    }
}

class FreeGameModel
{
    /** 
     * @param {String} ID 
     * @param {String} Title 
     * @param {String} Thumb 
     * @param {String} Type
     * @param {String} Image 
     * @param {String} Description 
     * @param {String} Instructions 
     * @param {String} URL 
     * @param {String} Platform 
     * @param {Date} EndDate 
     */
    constructor(ID, Title, Thumb,Type, Image, Description, Instructions, URL, Platform, EndDate)
    {
        this.ID = ID;
        this.Title = Title;
        this.Type = Type;
        this.Thumb = Thumb;
        this.Image = Image;
        this.Description = Description;
        this.Instructions = Instructions;
        this.URL = URL;
        this.Platform = Platform;
        this.EndDate = EndDate;
    }
}
const strikesM = require('./models/strikes');
const guildsM = require('./models/guilds');
class Strikes
{
    constructor(client)
    {
        this.client = client;
        this.check();
         setInterval(() => {
             this.check();
         }, 5*60*1000);

        //TODO: Handle old strikes
        //* Strikes life time should be difined by guild
        //* A default should be set
        //? 15 days

    }
    async check()
    {
        const Strikes = await strikesM.find().then(s=>{return s});
        //console.log(Strikes);
        const guilds = await guildsM.find({strikes:true}).then(g=>{return g});
        if(!guilds) return console.log('Striker: No Guilds')
        var guildIds = [];
        for(var i = 0; i<guilds.length; i++)
        {
            guildIds.push(guilds[i].gID);
        }
        var filtered = Strikes.filter(
            (e) => {
              return guildIds.indexOf(e.guildID)>=0;
            }            
          )
        guildIds.forEach(guild=>
            {
                filtered.forEach(strike=>
                    {
                        (async (guild, strike)=>
                        {
                            const ammount = await strikesM.find({guildID:guild, strokedID:strike.strokedID}).then(res=>{return res.length}); 
                            if(ammount>2) 
                            {
                                this.handleStroked(strike.strokedID, guild);
                            }
                        })(guild, strike)
                    })
            })
    }
    handleStroked(user, guild)
    {    
        strikesM.deleteMany({ guildID: guild, strokedID: user }, (err, res) => {
            if (!err) return console.log('Strikes Deleted')
          });
        // If the user is found, kick them from the server
        const member = this.client.guilds.cache.get(guild).members.cache.get(user);
        if (!member) return console.log('Strikes: Handle Strikened: Member not valid')
        console.log(member.user.username)
        member.kick('Optional reason that will be displayed in the audit logs')
            .then(() => console.log(`Successfully kicked ${user.user.username}`))
            .catch(err => console.log(`Unable to kick ${user.user.username}: ${err}`));
    } 
}