392 lines
19 KiB
Python
392 lines
19 KiB
Python
import asyncio
|
|
import datetime
|
|
import logging
|
|
import os
|
|
import sys
|
|
from json import JSONDecodeError
|
|
|
|
import discord
|
|
import pytz
|
|
import requests
|
|
from discord.ext import commands
|
|
from dotenv import load_dotenv
|
|
from sqlite_utils.db import NotFoundError
|
|
|
|
from db import DiscordDB
|
|
|
|
APP_NAME = "discord_bot"
|
|
|
|
os.chdir(os.path.abspath(os.path.dirname(sys.argv[0])))
|
|
load_dotenv()
|
|
logging.basicConfig(level=logging.WARNING, filename="logging.log",
|
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
|
logger = logging.getLogger(APP_NAME)
|
|
logger.setLevel(logging.DEBUG)
|
|
logger.propagate = False
|
|
fh = logging.FileHandler(f"./logging2.log", "w")
|
|
fh.setLevel(logging.DEBUG)
|
|
logger.addHandler(fh)
|
|
keep_fds = [fh.stream.fileno()]
|
|
|
|
pidfile = f"pid"
|
|
|
|
DISCORD_TOKEN = os.getenv("DISCORD_TOKEN")
|
|
DB_NAME = os.getenv('DB_NAME', 'discord.db')
|
|
DB = DiscordDB(DB_NAME)
|
|
|
|
COUNTRIES = {1: 'Romania', 9: 'Brazil', 10: 'Italy', 11: 'France', 12: 'Germany', 13: 'Hungary', 14: 'China',
|
|
15: 'Spain', 23: 'Canada', 24: 'USA', 26: 'Mexico', 27: 'Argentina', 28: 'Venezuela', 29: 'United Kingdom',
|
|
30: 'Switzerland', 31: 'Netherlands', 32: 'Belgium', 33: 'Austria', 34: 'Czech Republic', 35: 'Poland',
|
|
36: 'Slovakia', 37: 'Norway', 38: 'Sweden', 39: 'Finland', 40: 'Ukraine', 41: 'Russia', 42: 'Bulgaria',
|
|
43: 'Turkey', 44: 'Greece', 45: 'Japan', 47: 'South Korea', 48: 'India', 49: 'Indonesia', 50: 'Australia',
|
|
51: 'South Africa', 52: 'Republic of Moldova', 53: 'Portugal', 54: 'Ireland', 55: 'Denmark', 56: 'Iran',
|
|
57: 'Pakistan', 58: 'Israel', 59: 'Thailand', 61: 'Slovenia', 63: 'Croatia', 64: 'Chile', 65: 'Serbia',
|
|
66: 'Malaysia', 67: 'Philippines', 68: 'Singapore', 69: 'Bosnia and Herzegovina', 70: 'Estonia',
|
|
71: 'Latvia', 72: 'Lithuania', 73: 'North Korea', 74: 'Uruguay', 75: 'Paraguay', 76: 'Bolivia', 77: 'Peru',
|
|
78: 'Colombia', 79: 'Republic of Macedonia (FYROM)', 80: 'Montenegro', 81: 'Republic of China (Taiwan)',
|
|
82: 'Cyprus', 83: 'Belarus', 84: 'New Zealand', 164: 'Saudi Arabia', 165: 'Egypt',
|
|
166: 'United Arab Emirates', 167: 'Albania', 168: 'Georgia', 169: 'Armenia', 170: 'Nigeria', 171: 'Cuba'}
|
|
|
|
FLAGS = {1: 'flag_ro', 9: 'flag_br', 10: 'flag_it', 11: 'flag_fr', 12: 'flag_de', 13: 'flag_hu', 14: 'flag_cn',
|
|
15: 'flag_es', 23: 'flag_ca', 24: 'flag_us', 26: 'flag_mx', 27: 'flag_ar', 28: 'flag_ve', 29: 'flag_gb',
|
|
30: 'flag_ch', 31: 'flag_nl', 32: 'flag_be', 33: 'flag_at', 34: 'flag_cz', 35: 'flag_pl', 36: 'flag_sk',
|
|
37: 'flag_no', 38: 'flag_se', 39: 'flag_fi', 40: 'flag_ua', 41: 'flag_ru', 42: 'flag_bg', 43: 'flag_tr',
|
|
44: 'flag_gr', 45: 'flag_jp', 47: 'flag_kr', 48: 'flag_in', 49: 'flag_id', 50: 'flag_au', 51: 'flag_za',
|
|
52: 'flag_md', 53: 'flag_pt', 54: 'flag_ie', 55: 'flag_de', 56: 'flag_ir', 57: 'flag_pk', 58: 'flag_il',
|
|
59: 'flag_th', 61: 'flag_si', 63: 'flag_hr', 64: 'flag_cl', 65: 'flag_rs', 66: 'flag_my', 67: 'flag_ph',
|
|
68: 'flag_sg', 69: 'flag_ba', 70: 'flag_ee', 71: 'flag_lv', 72: 'flag_lt', 73: 'flag_kp', 74: 'flag_uy',
|
|
75: 'flag_py', 76: 'flag_bo', 77: 'flag_pe', 78: 'flag_co', 79: 'flag_mk', 80: 'flag_me', 81: 'flag_tw',
|
|
82: 'flag_cy', 83: 'flag_by', 84: 'flag_nz', 164: 'flag_sa', 165: 'flag_eg', 166: 'flag_ae', 167: 'flag_al',
|
|
168: 'flag_ge', 169: 'flag_am', 170: 'flag_ng', 171: 'flag_cu'}
|
|
|
|
|
|
__last_battle_response = None
|
|
__last_battle_update_timestamp = 0
|
|
|
|
|
|
def timestamp_to_datetime(timestamp: int) -> datetime.datetime:
|
|
return datetime.datetime.fromtimestamp(timestamp)
|
|
|
|
|
|
def get_battle_page():
|
|
global __last_battle_update_timestamp, __last_battle_response
|
|
if int(datetime.datetime.now().timestamp()) >= __last_battle_update_timestamp + 60:
|
|
r = requests.get('https://erep.lv/battles.json')
|
|
__last_battle_response = r.json()
|
|
__last_battle_update_timestamp = __last_battle_response.get('last_updated', int(datetime.datetime.now().timestamp()))
|
|
d = timestamp_to_datetime(__last_battle_update_timestamp//3600*3600)
|
|
os.makedirs(f"{d:%F/%H}/", exist_ok=True)
|
|
with open(f"{d:%F/%H}/{__last_battle_update_timestamp}.json", 'w') as f:
|
|
f.write(r.text)
|
|
return __last_battle_response
|
|
|
|
|
|
def check_player(player_id: int) -> bool:
|
|
try:
|
|
player_id = int(player_id)
|
|
except ValueError:
|
|
return False
|
|
if not DB.get_player(player_id):
|
|
try:
|
|
r = requests.get(f'https://www.erepublik.com/en/main/citizen-profile-json/{player_id}').json()
|
|
except JSONDecodeError:
|
|
return False
|
|
if r.get('error'):
|
|
return False
|
|
DB.add_player(player_id, r.get('citizen').get('name'))
|
|
|
|
return True
|
|
|
|
|
|
def get_medals(division: int):
|
|
r = get_battle_page()
|
|
if r.get('battles'):
|
|
request_time = timestamp_to_datetime(r.get('last_updated'))
|
|
for battle_id, battle in r.get('battles').items():
|
|
start_time = timestamp_to_datetime(battle.get('start'))
|
|
if start_time - datetime.timedelta(seconds=30) < request_time:
|
|
for division_data in battle.get('div', {}).values():
|
|
if not division_data.get('end') and division_data.get('div') == division:
|
|
for side, stat in division_data['stats'].items():
|
|
data = dict(id=battle.get('id'), country_id=battle.get(side).get('id'),
|
|
time=request_time - start_time, dmg=0)
|
|
if stat:
|
|
data.update(dmg=division_data['stats'][side]['damage'])
|
|
yield data
|
|
else:
|
|
yield data
|
|
|
|
|
|
class MyClient(discord.Client):
|
|
erep_tz = pytz.timezone('US/Pacific')
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
# create the background task and run it in the background
|
|
self.bg_task = self.loop.create_task(self.report_medals())
|
|
|
|
@property
|
|
def timestamp(self):
|
|
return int(datetime.datetime.now().timestamp())
|
|
|
|
async def on_ready(self):
|
|
print('Client loaded')
|
|
print('------')
|
|
|
|
async def report_medals(self):
|
|
await self.wait_until_ready()
|
|
while not self.is_closed():
|
|
try:
|
|
r = get_battle_page()
|
|
hunted_ids = DB.get_hunted_player_ids()
|
|
protected_ids = DB.get_protected_player_ids()
|
|
for bid, battle in r.get('battles', {}).items():
|
|
for div in battle.get('div', {}).values():
|
|
if div['stats'] and not div['end']:
|
|
for side, side_data in div['stats'].items():
|
|
if side_data:
|
|
pid = side_data['citizenId']
|
|
if pid in hunted_ids:
|
|
hunted_medal_key = (pid, battle['id'], div['id'],
|
|
battle[side]['id'], side_data['damage'])
|
|
if not DB.check_medal(*hunted_medal_key):
|
|
for hunt_row in DB.get_members_to_notify(pid):
|
|
format_data = dict(author=hunt_row['member_id'],
|
|
player=DB.get_player(pid)['name'],
|
|
battle=bid,
|
|
region=battle.get('region').get('name'),
|
|
division=div['div'], dmg=side_data['damage'],
|
|
side=COUNTRIES[battle[side]['id']])
|
|
|
|
await self.get_channel(hunt_row['channel_id']).send(
|
|
"<@{author}> **{player}** detected in battle for {region} on {side} "
|
|
"side in d{division} with {dmg:,d}dmg\n"
|
|
"https://www.erepublik.com/en/military/battlefield/{battle}".format(
|
|
**format_data))
|
|
DB.add_reported_medal(*hunted_medal_key)
|
|
|
|
protected_medal_key = (pid, div['id'], battle[side]['id'])
|
|
protected_medal_status = DB.check_protected_medal(*protected_medal_key)
|
|
if protected_medal_status == False:
|
|
medal = DB.get_protected_medal(div['id'], battle[side]['id'])
|
|
for protected in DB.get_protected_members_to_notify(medal['player_id']):
|
|
await self.get_channel(protected['channel_id']).send(
|
|
"<@{author}> Medal for **{player}** in battle for {region} on"
|
|
" {side} side in d{division} has been taken!\n"
|
|
"https://www.erepublik.com/en/military/battlefield/{battle}".format(
|
|
author=protected['member_id'],
|
|
player=DB.get_player(medal['player_id'])['name'],
|
|
battle=bid, region=battle.get('region').get('name'),
|
|
division=div['div'], side=COUNTRIES[battle[side]['id']]
|
|
))
|
|
DB.delete_protected_medals([medal['division_id']])
|
|
else:
|
|
if protected_medal_status is None and pid in protected_ids:
|
|
DB.add_protected_medal(*protected_medal_key)
|
|
logger.info(f"Added medal for protection {protected_medal_key}")
|
|
sleep_seconds = r.get('last_updated') + 60 - self.timestamp
|
|
await asyncio.sleep(sleep_seconds if sleep_seconds > 0 else 0)
|
|
except Exception as e:
|
|
await self.get_channel(603527159109124096).send("<@220849530730577920> Something bad has happened with"
|
|
" medal hunter!")
|
|
logger.error("Discord bot's eRepublik medal watcher died!", exc_info=e)
|
|
try:
|
|
with open(f"{self.timestamp}.json", 'w') as f:
|
|
f.write(r.text)
|
|
except NameError:
|
|
logger.error("There was no Response object!", exc_info=e)
|
|
await asyncio.sleep(10)
|
|
|
|
|
|
loop = asyncio.get_event_loop()
|
|
client = MyClient(loop=loop)
|
|
bot = commands.Bot(command_prefix='!')
|
|
|
|
|
|
@bot.event
|
|
async def on_ready():
|
|
print('Bot loaded')
|
|
# print(bot.user.name)
|
|
# print(bot.user.id)
|
|
print('------')
|
|
|
|
|
|
@bot.command(description="Parādīt lētos d1 BH, kuru dmg ir zem 5m vai Tevis ievadīta vērtībā", help="Lētie d1 BH",
|
|
category="Cheap medals")
|
|
async def bh1(ctx, max_damage: int = 5_000_000):
|
|
await _send_medal_info(ctx, 1, max_damage)
|
|
|
|
|
|
@bot.command(description="Parādīt lētos d2 BH, kuru dmg ir zem 10m vai Tevis ievadīta vērtībā", help="Lētie d2 BH",
|
|
category="Cheap medals")
|
|
async def bh2(ctx, max_damage: int = 10_000_000):
|
|
await _send_medal_info(ctx, 2, max_damage)
|
|
|
|
|
|
@bot.command(description="Parādīt lētos d3 BH, kuru dmg ir zem 15m vai Tevis ievadīta vērtībā", help="Lētie d3 BH",
|
|
category="Cheap medals")
|
|
async def bh3(ctx, max_damage: int = 15_000_000):
|
|
await _send_medal_info(ctx, 3, max_damage)
|
|
|
|
|
|
@bot.command(description="Parādīt lētos d4 BH, kuru dmg ir zem 50m vai Tevis ievadīta vērtībā", help="Lētie d4 BH",
|
|
category="Cheap medals")
|
|
async def bh4(ctx, max_damage: int = 50_000_000):
|
|
await _send_medal_info(ctx, 4, max_damage)
|
|
|
|
|
|
@bot.command(description="Parādīt lētos SH, kuru dmg ir zem 50k vai Tevis ievadīta vērtībā", help="Lētie SH",
|
|
category="Cheap medals")
|
|
async def sh(ctx, min_damage: int = 50_000):
|
|
await _send_medal_info(ctx, 11, min_damage)
|
|
|
|
|
|
@bh1.error
|
|
@bh2.error
|
|
@bh3.error
|
|
@bh4.error
|
|
@sh.error
|
|
async def damage_error(ctx, error):
|
|
if isinstance(error, commands.BadArgument):
|
|
await ctx.send('Damage vērtībai ir jābūt veselam skaitlim')
|
|
|
|
|
|
async def _send_medal_info(ctx, division: int, damage: int):
|
|
cheap_bhs = [] # Battle id, Side, damage, round time
|
|
for division_data in get_medals(division):
|
|
if division_data['dmg'] < damage:
|
|
division_data['flag'] = FLAGS[division_data['country_id']]
|
|
division_data['country'] = COUNTRIES[division_data['country_id']]
|
|
cheap_bhs.append(division_data)
|
|
|
|
if cheap_bhs:
|
|
cheap_bhs = sorted(cheap_bhs, key=lambda _: _['time'])
|
|
cheap_bhs.reverse()
|
|
msg = "\n".join(["{dmg:,d}dmg for :{flag}: {country}, {time} round time "
|
|
"https://www.erepublik.com/en/military/battlefield/{id}".format(**bh) for bh in cheap_bhs])
|
|
if len(msg) > 2000:
|
|
msg = "\n".join(msg[:2000].split('\n')[:-1])
|
|
await ctx.send(msg)
|
|
else:
|
|
await ctx.send("No medals under {:,d} damage found!".format(damage))
|
|
|
|
|
|
@bot.command(description="Informēt par spēlētāja mēģinājumiem ņemt medaļas",
|
|
help="Piereģistrēties uz spēlētāja medaļu paziņošanu", category="Hunting")
|
|
async def hunt(ctx, player_id: int):
|
|
if not check_player(player_id):
|
|
await ctx.send(f"{ctx.author.mention} didn't find any player with `id: {player_id}`!")
|
|
else:
|
|
player_name = DB.get_player(player_id).get('name')
|
|
try:
|
|
local_member_id = DB.get_member(ctx.author.id).get('id')
|
|
except NotFoundError:
|
|
local_member_id = DB.add_member(ctx.author.id, ctx.author.name).get('id')
|
|
if ctx.channel.type.value == 1:
|
|
await ctx.send(f"{ctx.author.mention}, sorry, but currently I'm unable to notify You in DM channel!")
|
|
elif DB.add_hunted_player(player_id, local_member_id, ctx.channel.id):
|
|
await ctx.send(f"{ctx.author.mention} You'll be notified for **{player_name}** medals in this channel")
|
|
else:
|
|
await ctx.send(f"{ctx.author.mention} You are already being notified for **{player_name}** medals")
|
|
|
|
|
|
@bot.command(description="Show list of hunted players",
|
|
help="Parādīt visus spēlētajus, kurus es medīju", category="Hunting")
|
|
async def my_hunt(ctx):
|
|
msgs = []
|
|
for hunted_player in DB.get_member_hunted_players(ctx.author.id):
|
|
msgs.append(f"`{hunted_player['id']}` - **{hunted_player['name']}**")
|
|
if msgs:
|
|
msg = "\n".join(msgs)
|
|
await ctx.send(f"{ctx.author.mention} You are hunting:\n{msg}")
|
|
else:
|
|
await ctx.send(f"{ctx.author.mention} You're not hunting anyone!")
|
|
|
|
|
|
@bot.command(description="Beigt informēt par spēlētāja mēģinājumiem ņemt medaļas",
|
|
help="Atreģistrēties no spēlētāja medaļu paziņošanas", category="Hunting")
|
|
async def remove_hunt(ctx, player_id: int):
|
|
if not check_player(player_id):
|
|
await ctx.send(f"{ctx.author.mention} didn't find any player with `id: {player_id}`!")
|
|
else:
|
|
player_name = DB.get_player(player_id).get('name')
|
|
try:
|
|
local_member_id = DB.get_member(ctx.author.id).get('id')
|
|
except NotFoundError:
|
|
local_member_id = DB.add_member(ctx.author.id, ctx.author.name).get('id')
|
|
if DB.remove_hunted_player(player_id, local_member_id):
|
|
await ctx.send(f"{ctx.author.mention} You won't be notified for **{player_name}** medals")
|
|
else:
|
|
await ctx.send(f"{ctx.author.mention} You were not hunting **{player_name}** medals")
|
|
|
|
|
|
@bot.command(description="Informēt par mēģinājiem nozagt medaļu",
|
|
help="Piereģistrēties uz medaļu sargāšanas paziņošanu", category="Protection")
|
|
async def protect(ctx, player_id: int):
|
|
if not check_player(player_id):
|
|
await ctx.send(f"{ctx.author.mention} didn't find any player with `id: {player_id}`!")
|
|
else:
|
|
player_name = DB.get_player(player_id).get('name')
|
|
try:
|
|
local_member_id = DB.get_member(ctx.author.id).get('id')
|
|
except NotFoundError:
|
|
local_member_id = DB.add_member(ctx.author.id, ctx.author.name).get('id')
|
|
if ctx.channel.type.value == 1:
|
|
await ctx.send(f"{ctx.author.mention}, sorry, but currently I'm unable to notify You in DM channel!")
|
|
elif DB.add_protected_player(player_id, local_member_id, ctx.channel.id):
|
|
await ctx.send(f"{ctx.author.mention} You'll be notified in this channel when anyone passes "
|
|
f"**{player_name}**'s medals")
|
|
else:
|
|
await ctx.send(f"{ctx.author.mention} You are already being notified for **{player_name}** medals")
|
|
|
|
|
|
@bot.command(description="Show players whose medals I'm protecting",
|
|
help="Parādīt sargājamo spēlētāju sarakstu ", category="Protection")
|
|
async def my_protected(ctx):
|
|
msgs = []
|
|
for protected_player in DB.get_member_protected_players(ctx.author.id):
|
|
msgs.append(f"`{protected_player['id']}` - **{protected_player['name']}**")
|
|
if msgs:
|
|
msg = "\n".join(msgs)
|
|
await ctx.send(f"{ctx.author.mention} You are protecting:\n{msg}")
|
|
else:
|
|
await ctx.send(f"{ctx.author.mention} You're not protecting anyone!")
|
|
|
|
|
|
@bot.command(description="Beigt informēt par spēlētāju mēģinājumiem noņemt medaļas",
|
|
help="Atreģistrēties no spēlētāja medaļu sargāšanas paziņošanas", category="Protection")
|
|
async def remove_protection(ctx, player_id: int):
|
|
if not check_player(player_id):
|
|
await ctx.send(f"{ctx.author.mention} didn't find any player with `id: {player_id}`!")
|
|
else:
|
|
player_name = DB.get_player(player_id).get('name')
|
|
try:
|
|
local_member_id = DB.get_member(ctx.author.id).get('id')
|
|
except NotFoundError:
|
|
local_member_id = DB.add_member(ctx.author.id, ctx.author.name).get('id')
|
|
if DB.remove_protected_player(player_id, local_member_id):
|
|
await ctx.send(f"{ctx.author.mention} You won't be notified for **{player_name}** medals")
|
|
else:
|
|
await ctx.send(f"{ctx.author.mention} You were not protecting **{player_name}** medals")
|
|
|
|
|
|
@hunt.error
|
|
@remove_hunt.error
|
|
async def hunt_error(ctx, error):
|
|
if isinstance(error, commands.BadArgument):
|
|
await ctx.send('spēlētāja identifikators jāpadod kā skaitliska vērtība, piemēram, 1620414')
|
|
|
|
|
|
def main():
|
|
global loop
|
|
loop.create_task(bot.start(DISCORD_TOKEN))
|
|
loop.create_task(client.start(DISCORD_TOKEN))
|
|
loop.run_forever()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|
|
# daemon = daemonize.Daemonize(APP_NAME, pidfile, main)
|
|
# daemon.start()
|
|
|