UNPKG

albion-guildbot

Version:

A discord bot that posts interesting events for a guild.

322 lines (267 loc) 10.9 kB
'use strict'; require('babel-polyfill'); const Discord = require('discord.js'); const FileSync = require('lowdb/adapters/FileSync'); const logger = require('winston'); const low = require('lowdb'); const Albion = require('./AlbionApi'); const Battle = require('./Battle').default; const { createImage, getItemUrl } = require('./createImage'); const config = require('../config'); const adapter = new FileSync('.db.json'); const db = low(adapter); db.defaults({ recents: { battleId: 0, eventId: 0 } }).write(); // Heroku will crash if we're not listenining on env.PORT. if (process.env.HEROKU) { const Express = require('express'); const app = new Express(); app.listen(process.env.PORT || 1337); } // Configure logger settings logger.remove(logger.transports.Console); logger.add(logger.transports.Console, { colorize: true }); logger.level = 'debug'; // Read eventID file to get a list of all posted events // If this fails, we cannot continue, so throw an exception. let lastBattleId = db.get('recents.battleId').value(); let lastEventId = db.get('recents.eventId').value(); let lastAlbionStatus = db.get('recents.albionStatus').value(); let lastAlbionStatusMsg = db.get('recents.albionStatusMsg').value(); // Initialize Discord Bot const bot = new Discord.Client(); bot.on('ready', () => { logger.info('Connected'); logger.info(`Logged in as: ${bot.user.username} - (${bot.user.id})`); if (config.discord.statusChannelId) { checkServerStatus(); setInterval(checkServerStatus, 60000); } checkBattles(); checkKillboard(); setInterval(checkBattles, 60000); setInterval(checkKillboard, 30000); }); function checkBattles() { logger.info('Checking battles...'); Albion.getBattles({ limit: 20, offset: 0 }).then(battles => { battles // Filter out battles that have already been processed .filter(battleData => battleData.id > lastBattleId) // Format the raw battle data into a more useful Battle object .map(battleData => new Battle(battleData)) // Filter out battles with insigificant amounts of players .filter(battle => battle.players.length >= config.battle.minPlayers) // Filter out battles that don't involve a relevent number of guildmates .filter(battle => { const relevantPlayerCount = config.guild.guilds.reduce((total, guildName) => { return total + (battle.guilds.has(guildName) ? battle.guilds.get(guildName).players.length : 0); }, 0); return relevantPlayerCount >= config.battle.minRelevantPlayers; }).forEach(battle => sendBattleReport(battle)); }); } function sendBattleReport(battle, channelId) { if (battle.id > lastBattleId) { lastBattleId = battle.id; db.set('recents.battleId', lastBattleId).write(); } const title = battle.rankedFactions.slice() .sort((a, b) => b.players.length - a.players.length) .map(({ name, players }) => `${name}(${players.length})`) .join(' vs '); const thumbnailUrl = battle.players.length >= 100 ? 'https://storage.googleapis.com/albion-images/static/PvP-100.png' : battle.players.length >= 40 ? 'https://storage.googleapis.com/albion-images/static/PvP-40.png' : battle.is5v5 ? 'https://storage.googleapis.com/albion-images/static/5v5-3.png' : 'https://storage.googleapis.com/albion-images/static/PvP-10.png'; let fields = battle.rankedFactions.map(({ name, kills, deaths, killFame, factionType }, i) => { return { name: `${i + 1}. ${name} - ${killFame.toLocaleString()} Fame`, inline: true, value: [ `Kills: ${kills}`, `Deaths: ${deaths}`, factionType === 'alliance' ? '\n__**Guilds**__' : '', Array.from(battle.guilds.values()) .filter(({ alliance }) => alliance === name) .sort((a, b) => battle.guilds.get(b.name).players.length > battle.guilds.get(a.name).players.length) .map(({ name }) => `${name} (${battle.guilds.get(name).players.length})`) .join('\n'), ].join('\n') }; }); if (battle.is5v5) { fields = battle.rankedFactions.map(({ name, kills, players }) => { return { name: `${name} [Kills: ${kills}]`, inline: true, value: players .sort((a, b) => a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1) .sort((a, b) => b.kills > a.kills) .map(({ name, kills, deaths }) => `${deaths ? '~~' : ''}${name}${deaths ? '~~' : ''}: ${kills} Kills`) .join('\n') }; }); } const didWin = battle.rankedFactions[0].name === config.guild.alliance; const embed = { url: `https://albiononline.com/en/killboard/battles/${battle.id}`, description: battle.is5v5 ? `Winner's Fame: ${battle.rankedFactions[0].killFame.toLocaleString()}` : `Players: ${battle.players.length}, Kills: ${battle.totalKills}, Fame: ${battle.totalFame.toLocaleString()}`, title: battle.is5v5 ? (didWin ? `We wrecked ${battle.rankedFactions[1].name} in a 5v5!` : `We lost to ${battle.rankedFactions[0].name} in a 5v5!`) : title, color: didWin ? 65280 : 16711680, timestamp: battle.endTime, thumbnail: { url: thumbnailUrl }, image: { url: 'https://storage.googleapis.com/albion-images/static/spacer.png' }, fields, }; bot.channels.get(channelId || config.discord.feedChannelId).send({ embed }).then(() => { logger.info(`Successfully posted log of battle between ${title}.`); }).catch(err => { logger.error(err); }); } function sendKillReport(event, channelId) { const isFriendlyKill = config.guild.guilds.indexOf(event.Killer.GuildName) !== -1; createImage('Victim', event).then(imgBuffer => { const participants = parseInt(event.numberOfParticipants || event.GroupMembers.length, 10); const assists = participants - 1; const embed = { url: `https://albiononline.com/en/killboard/kill/${event.EventId}`, title: `${event.Killer.Name} (${assists ? '+' + assists : 'Solo!'}) just killed ${event.Victim.Name}!`, description: `From guild: ${createGuildTag(event[isFriendlyKill ? 'Victim' : 'Killer'])}`, color: isFriendlyKill ? 65280 : 16711680, image: { url: 'attachment://kill.png' }, }; if (event.TotalVictimKillFame > config.kill.minFame) { Object.assign(embed, { thumbnail: { url: getItemUrl(event.Killer.Equipment.MainHand) }, title: `${event.Killer.Name} just killed ${event.Victim.Name}!`, description: assists ? `Assisted by ${assists} other player${assists > 1 ? 's' : ''}.` : 'Solo kill!', fields: [{ name: isFriendlyKill ? 'Victim\'s Guild' : 'Killer\'s Guild', value: createGuildTag(event[isFriendlyKill ? 'Victim' : 'Killer']), inline: true, }], timestamp: event.TimeStamp, }); } const files = [{ name: 'kill.png', attachment: imgBuffer }]; return bot.channels.get((channelId || config.discord.feedChannelId)).send({ embed, files }); }).then(() => { logger.info(`Successfully posted log of ${createDisplayName(event.Killer)} killing ${createDisplayName(event.Victim)}.`); }); } function checkKillboard() { logger.info('Checking killboard...'); Albion.getEvents({ limit: 51, offset: 0 }).then(events => { if (!events) { return; } events.sort((a, b) => a.EventId - b.EventId) .filter(event => event.EventId > lastEventId) .forEach(event => { lastEventId = event.EventId; const isFriendlyKill = config.guild.guilds.indexOf(event.Killer.GuildName) !== -1; const isFriendlyDeath = config.guild.guilds.indexOf(event.Victim.GuildName) !== -1; if (!(isFriendlyKill || isFriendlyDeath) || event.TotalVictimKillFame < 10000) { return; } sendKillReport(event); }); db.set('recents.eventId', lastEventId).write(); }); } function createGuildTag(player) { const allianceTag = player.AllianceName ? `[${player.AllianceName}]` : ''; return player.GuildName ? `${allianceTag} ${player.GuildName}` : 'N/A'; } function createDisplayName(player) { const allianceTag = player.AllianceName ? `[${player.AllianceName}]` : ''; return `**<${allianceTag}${player.GuildName || 'Unguilded'}>** ${player.Name}`; } function sendServerStatus(channelId, isCmd) { let now = new Date(); const embed = { url: 'https://albiononline.statuspage.io', title: 'Albion Status Information', description: isCmd ? `Current server status is **${lastAlbionStatus}**` : `Server status just changed to **${lastAlbionStatus}**`, color: lastAlbionStatus === 'offline' ? 0xff2600 : 0x00f900, fields: [{ name: 'Message', value: lastAlbionStatusMsg, inline: true, }], timestamp: now.toISOString(), }; bot.channels.get(channelId || config.discord.statusChannelId).send({ embed }).then(() => { logger.info(`Successfully posted albion status: ${lastAlbionStatus}`); }).catch(err => { logger.error(err); }); } function checkServerStatus(channelId) { logger.info('Checking server status...'); Albion.serverStatusRequest().then(currentAlbionStatus => { if (lastAlbionStatus !== currentAlbionStatus.status || lastAlbionStatusMsg !== currentAlbionStatus.message) { lastAlbionStatus = currentAlbionStatus.status; lastAlbionStatusMsg = currentAlbionStatus.message; sendServerStatus(channelId); db.set('recents.albionStatus', currentAlbionStatus.status).write(); db.set('recents.albionStatusMsg', currentAlbionStatus.message).write(); } }); } bot.on('message', msg => { let message = msg.content; let channelID = msg.channel.id; let matches = message.match(/^https:\/\/albiononline\.com\/en\/killboard\/kill\/(\d+)/); if (matches && matches.length) { Albion.getEvent(matches[1]).then(event => { sendKillReport(event, channelID); }); return; } matches = message.match(/^https:\/\/albiononline\.com\/en\/killboard\/battles\/(\d+)/); if (matches && matches.length) { Albion.getBattle(matches[1]).then(battle => { sendBattleReport(new Battle(battle), channelID); }); return; } if (message.substring(0, 1) !== '!') { return; } const args = message.substring(1).split(' '); const [cmd, id] = args; if (!cmd) { return; } // cmd without parameter switch (cmd) { case 'showStatus': sendServerStatus(channelID, 1); break; } if (!id) { return; } // cmd with parameter switch (cmd) { case 'showBattle': Albion.getBattle(id).then(battle => { sendBattleReport(new Battle(battle), channelID); }); break; case 'showKill': Albion.getEvent(id).then(event => { sendKillReport(event, channelID); }); break; } }); bot.login(config.discord.token);