Refactoring
This commit is contained in:
parent
16a5e88232
commit
b8b2093c73
14
.editorconfig
Normal file
14
.editorconfig
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
indent_size = 4
|
||||||
|
indent_style = space
|
||||||
|
insert_final_newline = true
|
||||||
|
max_line_length = 180
|
||||||
|
tab_width = 4
|
||||||
|
|
||||||
|
[{*.bash,*.sh,*.zsh}]
|
||||||
|
indent_size = 2
|
||||||
|
tab_width = 2
|
@ -8,6 +8,8 @@ RUN groupadd -g 1000 discordbot \
|
|||||||
|
|
||||||
USER discordbot
|
USER discordbot
|
||||||
COPY requirements.txt /app/requirements.txt
|
COPY requirements.txt /app/requirements.txt
|
||||||
RUN pip install -r requirements.txt
|
COPY ./run.sh /run.sh
|
||||||
|
RUN pip install -r requirements.txt && chmod +x /run.sh
|
||||||
|
|
||||||
CMD python discord_bot.py
|
#CMD python discord_bot.py
|
||||||
|
ENTRYPOINT ['/run.sh', 'docker']
|
||||||
|
255
dbot/constants.py
Normal file
255
dbot/constants.py
Normal file
@ -0,0 +1,255 @@
|
|||||||
|
import re
|
||||||
|
from typing import NamedTuple
|
||||||
|
|
||||||
|
from erepublik.constants import COUNTRIES
|
||||||
|
|
||||||
|
__all__ = ["events", COUNTRIES, "FLAGS", "UTF_FLAG"]
|
||||||
|
|
||||||
|
region = r"[\w\(\)\- ']+"
|
||||||
|
country = r"(Resistance force of )?[\w\(\)\- ]+"
|
||||||
|
citizen = r"[\w\(\)\-\. \d]+"
|
||||||
|
|
||||||
|
|
||||||
|
class EventKind(NamedTuple):
|
||||||
|
name: str
|
||||||
|
regex: re.Pattern
|
||||||
|
format: str
|
||||||
|
|
||||||
|
|
||||||
|
events = [
|
||||||
|
EventKind("Region attacked", re.compile(rf"(?P<invader>{country}) attacked (?P<region>{region}), (?P<defender>{country})"), "{invader} attacked {defender} ({region})"),
|
||||||
|
EventKind(
|
||||||
|
"Region secured",
|
||||||
|
re.compile(rf"(?P<region>{region}) was secured by (?P<defender>{country}) in the war versus (?P<invader>{country})?"),
|
||||||
|
"{defender} defended {invader}'s attack ({region})",
|
||||||
|
),
|
||||||
|
EventKind(
|
||||||
|
"Region conquered",
|
||||||
|
re.compile(rf"(?P<region>{region}) was conquered by (?P<invader>{country}) in the war versus (?P<defender>{country})"),
|
||||||
|
"{invader} conquered {region} from {defender}",
|
||||||
|
),
|
||||||
|
EventKind("War declared", re.compile(rf"(?P<invader>{country}) declared war on (?P<defender>{country})"), "{invader} declared war against {defender}"),
|
||||||
|
EventKind(
|
||||||
|
"War declaration",
|
||||||
|
re.compile(rf"President of (?P<invader>{country}) proposed a war declaration against (?P<defender>{country})"),
|
||||||
|
"{invader} proposed a war declaration on {defender}",
|
||||||
|
),
|
||||||
|
EventKind(
|
||||||
|
"War rejected", re.compile(rf"The proposal for declaring war against (?P<defender>{country}) was rejected."), "{current_country} rejected war declaration on {defender}"
|
||||||
|
),
|
||||||
|
EventKind("MPP proposed", re.compile(rf"President of (?P<country>{country}) proposed an alliance with (?P<partner>{country})"), "{country} proposed MPP with {partner}"),
|
||||||
|
EventKind("MPP approved", re.compile(rf"(?P<country>{country}) signed an alliance with (?P<partner>{country})"), "{country} signed a MPP with {partner}"),
|
||||||
|
EventKind(
|
||||||
|
"MPP rejected", re.compile(rf"The alliance between (?P<country>{country}) and (?P<partner>{country}) was rejected"), "MPP between {country} and {partner} was rejected"
|
||||||
|
),
|
||||||
|
EventKind(
|
||||||
|
"Airstrike proposed",
|
||||||
|
re.compile(rf"President of (?P<invader>{country}) proposed an airstrike against (?P<defender>{country})"),
|
||||||
|
"{invader} proposed an airstrike against {defender}",
|
||||||
|
),
|
||||||
|
EventKind("Airstrike approved", re.compile(rf"(?P<invader>{country}) prepares an airstrike on (?P<defender>{country})"), "{invader} approved an airstrike against {defender}"),
|
||||||
|
EventKind("Airstrike rejected", re.compile(rf"The airstrike on (?P<defender>{country}) was rejected"), "{current_country} rejected the airstrike against {defender}"),
|
||||||
|
EventKind(
|
||||||
|
"NE proposed",
|
||||||
|
re.compile(rf"(?P<invader>{country}) has declared (?P<defender>{country}) as a Natural Enemy"),
|
||||||
|
"{invader} proposed Natural Enemy declaration against {defender}",
|
||||||
|
),
|
||||||
|
EventKind("NE approved", re.compile(rf"(?P<defender>{country}) has been proposed as Natural Enemy"), "{current_country} declared {defender} as Natural Enemy"),
|
||||||
|
EventKind("NE rejected", re.compile(rf"(?P<defender>{country}) as new Natural Enemy proposal has been rejected"), "{current_country} rejected {defender} as Natural Enemy"),
|
||||||
|
EventKind("NE stopped", re.compile(rf"(?P<defender>{country}) is no longer a Natural Enemy for (?P<invader>{country})"), "{invader} removed Natural Enemy from {defender}"),
|
||||||
|
EventKind("NE cleared", re.compile(rf"(?P<country>{country}) no longer has a Natural Enemy"), "{country} no longer has a Natural Enemy"),
|
||||||
|
EventKind("NE reset", re.compile("No Natural Enemy law has been proposed."), "{current_country} has proposed to clear Natural Enemy"),
|
||||||
|
EventKind(
|
||||||
|
"Peace proposal",
|
||||||
|
re.compile(rf"President of (?P<defender>{country}) proposed a peace in the war against (?P<invader>{country})"),
|
||||||
|
"{defender} proposed peace against {invader}",
|
||||||
|
),
|
||||||
|
EventKind("Peace proposal", re.compile(rf"(?P<defender>{country}) proposed peace in the war against (?P<invader>{country})"), "{defender} proposed peace against {invader}"),
|
||||||
|
EventKind("Peace approved", re.compile(rf"(?P<invader>{country}) signed a peace treaty with (?P<defender>{country})"), "{invader} and {defender} is not in peace"),
|
||||||
|
EventKind(
|
||||||
|
"Peace rejected",
|
||||||
|
re.compile(rf"The proposed peace treaty between (?P<defender>{country}) and (?P<invader>{country}) was rejected"),
|
||||||
|
"{defender} and {invader} did not sign a peace treaty",
|
||||||
|
),
|
||||||
|
EventKind(
|
||||||
|
"Embargo proposed", re.compile(rf"President of (?P<major>{country}) proposed to stop the trade with (?P<minor>{country})"), "{major} proposed trade embargo against {minor}"
|
||||||
|
),
|
||||||
|
EventKind("Embargo approved", re.compile(rf"(?P<major>{country}) stopped trading with (?P<minor>{country})"), "{major} declared trade ambargo against {minor}"),
|
||||||
|
EventKind("Donation proposed", re.compile(rf"A congress donation to (?P<org>{citizen}) was proposed"), "{current_country} proposed a donation to {org}"),
|
||||||
|
EventKind("Donation approved", re.compile(rf"(?P<country>{country}) made a donation to (?P<org>{citizen})"), "{current_country} approved a donation to {org}"),
|
||||||
|
EventKind("Donation rejected", re.compile(rf"The proposal for a congress donation to (?P<org>{citizen}) was rejected"), "{current_country} rejected a donation to {org}"),
|
||||||
|
EventKind("RW started", re.compile(rf"A resistance has started in (?P<region>{region})"), "Resistance war was opened in {region} ({current_country})"),
|
||||||
|
EventKind(
|
||||||
|
"Res Concession",
|
||||||
|
re.compile(
|
||||||
|
rf"A Resource Concession law to //www.erepublik.com<b>(?P<target>{country})</b> "
|
||||||
|
+ rf'<a href="(?P<link>((https?:)?//www\.erepublik\.com)?/en/main/law/(?P<source>{country}/\d+))">has been (?P<result>.*?)</a>'
|
||||||
|
),
|
||||||
|
"Resource Concession law between {current_country} and {target} has been {result}",
|
||||||
|
),
|
||||||
|
EventKind(
|
||||||
|
"CP impeachment", re.compile(rf"A president impeachment against (?P<cp>{citizen}) was proposed"), "Impeachment against {cp} president of {current_country} was proposed"
|
||||||
|
),
|
||||||
|
EventKind("CP impeachment", re.compile("The president impeachment proposal has been rejected"), "Impeachment against president of {current_country} was rejected"),
|
||||||
|
EventKind("Minimum Wage", re.compile("A new minimum wage was proposed"), "A new minimum wage in {current_country} was proposed"),
|
||||||
|
EventKind("Minimum Wage", re.compile("The proposal for a minimum wage change was rejected"), "The new minimum wage proposal in {current_country} was rejected"),
|
||||||
|
EventKind("WorkTax", re.compile(rf"(?P<country>{country}) now has a new Work Tax"), "{country} has new Work Tax"),
|
||||||
|
EventKind("WorkTax", re.compile("A new Work Tax was proposed"), "{country} proposed a new Work Tax"),
|
||||||
|
EventKind("WorkTax", re.compile("The proposal for a new Work Tax was rejected"), "{country} rejected new Work Tax"),
|
||||||
|
EventKind("Product Tax", re.compile(r"Taxes for (?P<product>[\w ]+) changed"), "{current_country} changed taxes for {product}"),
|
||||||
|
EventKind("Product Tax", re.compile(r"Tax proposal of tax changes for (?P<product>[\w ]+) were rejected"), "{current_country} rejected new taxes for {product}"),
|
||||||
|
EventKind("Product Tax", re.compile(r"New taxes for (?P<product>[\w ]+) were proposed"), "{current_country} proposed new taxes for {product}"),
|
||||||
|
]
|
||||||
|
|
||||||
|
UTF_FLAG = {
|
||||||
|
1: "🇷🇴",
|
||||||
|
9: "🇧🇷",
|
||||||
|
10: "🇮🇹",
|
||||||
|
11: "🇫🇷",
|
||||||
|
12: "🇩🇪",
|
||||||
|
13: "🇭🇺",
|
||||||
|
14: "🇨🇳",
|
||||||
|
15: "🇪🇸",
|
||||||
|
23: "🇨🇦",
|
||||||
|
24: "🇺🇸",
|
||||||
|
26: "🇲🇽",
|
||||||
|
27: "🇦🇷",
|
||||||
|
28: "🇻🇪",
|
||||||
|
29: "🇬🇧",
|
||||||
|
30: "🇨🇭",
|
||||||
|
31: "🇳🇱",
|
||||||
|
32: "🇧🇪",
|
||||||
|
33: "🇦🇹",
|
||||||
|
34: "🇨🇿",
|
||||||
|
35: "🇵🇱",
|
||||||
|
36: "🇸🇰",
|
||||||
|
37: "🇳🇴",
|
||||||
|
38: "🇸🇪",
|
||||||
|
39: "🇫🇮",
|
||||||
|
40: "🇺🇦",
|
||||||
|
41: "🇷🇺",
|
||||||
|
42: "🇧🇬",
|
||||||
|
43: "🇹🇷",
|
||||||
|
44: "🇬🇷",
|
||||||
|
45: "🇯🇵",
|
||||||
|
47: "🇰🇷",
|
||||||
|
48: "🇮🇳",
|
||||||
|
49: "🇮🇩",
|
||||||
|
50: "🇦🇺",
|
||||||
|
51: "🇿🇦",
|
||||||
|
52: "🇲🇩",
|
||||||
|
53: "🇵🇹",
|
||||||
|
54: "🇮🇪",
|
||||||
|
55: "🇩🇰",
|
||||||
|
56: "🇮🇷",
|
||||||
|
57: "🇵🇰",
|
||||||
|
58: "🇮🇱",
|
||||||
|
59: "🇹🇭",
|
||||||
|
61: "🇸🇮",
|
||||||
|
63: "🇭🇷",
|
||||||
|
64: "🇨🇱",
|
||||||
|
65: "🇷🇸",
|
||||||
|
66: "🇲🇾",
|
||||||
|
67: "🇵🇭",
|
||||||
|
68: "🇸🇬",
|
||||||
|
69: "🇧🇦",
|
||||||
|
70: "🇪🇪",
|
||||||
|
71: "🇱🇻",
|
||||||
|
72: "🇱🇹",
|
||||||
|
73: "🇰🇵",
|
||||||
|
74: "🇺🇾",
|
||||||
|
75: "🇵🇾",
|
||||||
|
76: "🇧🇴",
|
||||||
|
77: "🇵🇪",
|
||||||
|
78: "🇨🇴",
|
||||||
|
79: "🇲🇰",
|
||||||
|
80: "🇲🇪",
|
||||||
|
81: "🇹🇼",
|
||||||
|
82: "🇨🇾",
|
||||||
|
83: "🇧🇾",
|
||||||
|
84: "🇳🇿",
|
||||||
|
164: "🇸🇦",
|
||||||
|
165: "🇪🇬",
|
||||||
|
166: "🇦🇪",
|
||||||
|
167: "🇦🇱",
|
||||||
|
168: "🇬🇪",
|
||||||
|
169: "🇦🇲",
|
||||||
|
170: "🇳🇬",
|
||||||
|
171: "🇨🇺",
|
||||||
|
}
|
||||||
|
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",
|
||||||
|
}
|
@ -23,11 +23,15 @@ class DiscordDB:
|
|||||||
if "epic" not in self._db.table_names():
|
if "epic" not in self._db.table_names():
|
||||||
self._db.create_table("epic", {"id": int, "fake": bool}, pk="id", not_null={"id"}, defaults={"fake": False})
|
self._db.create_table("epic", {"id": int, "fake": bool}, pk="id", not_null={"id"}, defaults={"fake": False})
|
||||||
|
|
||||||
|
if "rss_feed" not in self._db.table_names():
|
||||||
|
self._db.create_table("rss_feed", {"id": int, "timestamp": float}, pk="id", not_null={"id", "timestamp"})
|
||||||
|
|
||||||
self._db.vacuum()
|
self._db.vacuum()
|
||||||
|
|
||||||
self.member = self._db.table("member")
|
self.member = self._db.table("member")
|
||||||
self.player = self._db.table("player")
|
self.player = self._db.table("player")
|
||||||
self.epic = self._db.table("epic")
|
self.epic = self._db.table("epic")
|
||||||
|
self.rss_feed = self._db.table("rss_feed")
|
||||||
|
|
||||||
# Player methods
|
# Player methods
|
||||||
|
|
||||||
@ -133,3 +137,15 @@ class DiscordDB:
|
|||||||
self.epic.insert({"id": division_id})
|
self.epic.insert({"id": division_id})
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def get_rss_feed_timestamp(self, country_id: int) -> float:
|
||||||
|
try:
|
||||||
|
return self.rss_feed.get(country_id)["timestamp"]
|
||||||
|
except NotFoundError:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def set_rss_feed_timestamp(self, country_id: int, timestamp: float):
|
||||||
|
if self.get_rss_feed_timestamp(country_id):
|
||||||
|
self.rss_feed.update(country_id, {"timestamp": timestamp})
|
||||||
|
else:
|
||||||
|
self.rss_feed.insert({"id": country_id, "timestamp": timestamp})
|
@ -4,197 +4,53 @@ import json
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import time
|
||||||
from json import JSONDecodeError
|
from json import JSONDecodeError
|
||||||
from typing import Union
|
from typing import Union
|
||||||
import time
|
|
||||||
|
|
||||||
import pytz
|
|
||||||
import discord
|
import discord
|
||||||
import requests
|
|
||||||
from discord.ext import commands
|
|
||||||
from dotenv import load_dotenv
|
|
||||||
import feedparser
|
import feedparser
|
||||||
|
import pytz
|
||||||
|
import requests
|
||||||
|
from constants import UTF_FLAG, events
|
||||||
from db import DiscordDB
|
from db import DiscordDB
|
||||||
from map_events import events
|
from discord.ext import commands
|
||||||
|
from erepublik.constants import COUNTRIES
|
||||||
|
|
||||||
APP_NAME = "discord_bot"
|
APP_NAME = "discord_bot"
|
||||||
|
|
||||||
os.chdir(os.path.abspath(os.path.dirname(sys.argv[0])))
|
os.chdir(os.path.abspath(os.path.dirname(sys.argv[0])))
|
||||||
load_dotenv()
|
os.makedirs("debug", exist_ok=True)
|
||||||
|
|
||||||
logger = logging.getLogger(APP_NAME)
|
logger = logging.getLogger(APP_NAME)
|
||||||
|
logger.setLevel(logging.DEBUG)
|
||||||
formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
|
formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
|
||||||
|
|
||||||
file_logger = logging.FileHandler(f"./logging.log", "w")
|
file_logger = logging.FileHandler("debug/logging.log", "w")
|
||||||
file_logger.setLevel(logging.DEBUG)
|
file_logger.setLevel(logging.WARNING)
|
||||||
file_logger.setFormatter(formatter)
|
file_logger.setFormatter(formatter)
|
||||||
logger.addHandler(file_logger)
|
logger.addHandler(file_logger)
|
||||||
|
|
||||||
stream_logger = logging.StreamHandler()
|
stream_logger = logging.StreamHandler()
|
||||||
|
stream_logger.setLevel(logging.DEBUG)
|
||||||
stream_logger.setFormatter(formatter)
|
stream_logger.setFormatter(formatter)
|
||||||
logger.addHandler(stream_logger)
|
logger.addHandler(stream_logger)
|
||||||
|
|
||||||
os.makedirs("debug", exist_ok=True)
|
|
||||||
|
|
||||||
DISCORD_TOKEN = os.getenv("DISCORD_TOKEN")
|
DISCORD_TOKEN = os.getenv("DISCORD_TOKEN")
|
||||||
DEFAULT_CHANNEL_ID = os.getenv("DEFAULT_CHANNEL_ID", 603527159109124096)
|
DEFAULT_CHANNEL_ID = os.getenv("DEFAULT_CHANNEL_ID", 603527159109124096)
|
||||||
ADMIN_ID = os.getenv("DEFAULT_CHANNEL_ID", 220849530730577920)
|
ADMIN_ID = os.getenv("ADMIN_ID", 220849530730577920)
|
||||||
DB_NAME = os.getenv("DB_NAME", "discord.db")
|
DB_NAME = os.getenv("DB_NAME", "discord.db")
|
||||||
|
PRODUCTION = bool(os.getenv("PRODUCTION"))
|
||||||
DB = DiscordDB(DB_NAME)
|
DB = DiscordDB(DB_NAME)
|
||||||
|
|
||||||
UTF_FLAG = {
|
if PRODUCTION:
|
||||||
1: "🇷🇴",
|
logger.setLevel(logging.INFO)
|
||||||
9: "🇧🇷",
|
_ts = int(time.time())
|
||||||
10: "🇮🇹",
|
for c_id in COUNTRIES.keys():
|
||||||
11: "🇫🇷",
|
DB.set_rss_feed_timestamp(c_id, _ts)
|
||||||
12: "🇩🇪",
|
del _ts
|
||||||
13: "🇭🇺",
|
|
||||||
14: "🇨🇳",
|
logger.debug(f"Active configs:\nDISCORD_TOKEN='{DISCORD_TOKEN}'\nDEFAULT_CHANNEL_ID='{DEFAULT_CHANNEL_ID}'\nADMIN_ID='{ADMIN_ID}'\nDB_NAME='{DB_NAME}'")
|
||||||
15: "🇪🇸",
|
|
||||||
23: "🇨🇦",
|
|
||||||
24: "🇺🇸",
|
|
||||||
26: "🇲🇽",
|
|
||||||
27: "🇦🇷",
|
|
||||||
28: "🇻🇪",
|
|
||||||
29: "🇬🇧",
|
|
||||||
30: "🇨🇭",
|
|
||||||
31: "🇳🇱",
|
|
||||||
32: "🇧🇪",
|
|
||||||
33: "🇦🇹",
|
|
||||||
34: "🇨🇿",
|
|
||||||
35: "🇵🇱",
|
|
||||||
36: "🇸🇰",
|
|
||||||
37: "🇳🇴",
|
|
||||||
38: "🇸🇪",
|
|
||||||
39: "🇫🇮",
|
|
||||||
40: "🇺🇦",
|
|
||||||
41: "🇷🇺",
|
|
||||||
42: "🇧🇬",
|
|
||||||
43: "🇹🇷",
|
|
||||||
44: "🇬🇷",
|
|
||||||
45: "🇯🇵",
|
|
||||||
47: "🇰🇷",
|
|
||||||
48: "🇮🇳",
|
|
||||||
49: "🇮🇩",
|
|
||||||
50: "🇦🇺",
|
|
||||||
51: "🇿🇦",
|
|
||||||
52: "🇲🇩",
|
|
||||||
53: "🇵🇹",
|
|
||||||
54: "🇮🇪",
|
|
||||||
55: "🇩🇰",
|
|
||||||
56: "🇮🇷",
|
|
||||||
57: "🇵🇰",
|
|
||||||
58: "🇮🇱",
|
|
||||||
59: "🇹🇭",
|
|
||||||
61: "🇸🇮",
|
|
||||||
63: "🇭🇷",
|
|
||||||
64: "🇨🇱",
|
|
||||||
65: "🇷🇸",
|
|
||||||
66: "🇲🇾",
|
|
||||||
67: "🇵🇭",
|
|
||||||
68: "🇸🇬",
|
|
||||||
69: "🇧🇦",
|
|
||||||
70: "🇪🇪",
|
|
||||||
71: "🇱🇻",
|
|
||||||
72: "🇱🇹",
|
|
||||||
73: "🇰🇵",
|
|
||||||
74: "🇺🇾",
|
|
||||||
75: "🇵🇾",
|
|
||||||
76: "🇧🇴",
|
|
||||||
77: "🇵🇪",
|
|
||||||
78: "🇨🇴",
|
|
||||||
79: "🇲🇰",
|
|
||||||
80: "🇲🇪",
|
|
||||||
81: "🇹🇼",
|
|
||||||
82: "🇨🇾",
|
|
||||||
83: "🇧🇾",
|
|
||||||
84: "🇳🇿",
|
|
||||||
164: "🇸🇦",
|
|
||||||
165: "🇪🇬",
|
|
||||||
166: "🇦🇪",
|
|
||||||
167: "🇦🇱",
|
|
||||||
168: "🇬🇪",
|
|
||||||
169: "🇦🇲",
|
|
||||||
170: "🇳🇬",
|
|
||||||
171: "🇨🇺",
|
|
||||||
}
|
|
||||||
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",
|
|
||||||
}
|
|
||||||
|
|
||||||
MENTION_MAPPING = {1: "D1", 2: "D2", 3: "D3", 4: "D4", 11: "Air"}
|
MENTION_MAPPING = {1: "D1", 2: "D2", 3: "D3", 4: "D4", 11: "Air"}
|
||||||
|
|
||||||
@ -232,7 +88,7 @@ class MyClient(discord.Client):
|
|||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
# create the background task and run it in the background
|
# create the background task and run it in the background
|
||||||
self.last_event_timestamp = self.timestamp - 43200
|
self.last_event_timestamp = self.timestamp
|
||||||
self.bg_task = self.loop.create_task(self.report_epics())
|
self.bg_task = self.loop.create_task(self.report_epics())
|
||||||
self.bg_rss_task = self.loop.create_task(self.report_latvian_events())
|
self.bg_rss_task = self.loop.create_task(self.report_latvian_events())
|
||||||
|
|
||||||
@ -241,50 +97,81 @@ class MyClient(discord.Client):
|
|||||||
return int(time.time())
|
return int(time.time())
|
||||||
|
|
||||||
async def on_ready(self):
|
async def on_ready(self):
|
||||||
logger.debug("Client running")
|
logger.info("Client running")
|
||||||
logger.debug("------")
|
logger.info("------")
|
||||||
|
|
||||||
async def on_error(self, event_method, *args, **kwargs):
|
async def on_error(self, event_method, *args, **kwargs):
|
||||||
logger.warning(f"Ignoring exception in {event_method}")
|
logger.warning(f"Ignoring exception in {event_method}")
|
||||||
|
|
||||||
|
async def send_msg(self, channel_id, *args, **kwargs):
|
||||||
|
if PRODUCTION:
|
||||||
|
return self.get_channel(channel_id).send(*args, **kwargs)
|
||||||
|
else:
|
||||||
|
return logger.debug(f"Sending message to: {channel_id}\nArgs: {args}\nKwargs{kwargs}")
|
||||||
|
|
||||||
async def report_latvian_events(self):
|
async def report_latvian_events(self):
|
||||||
await self.wait_until_ready()
|
await self.wait_until_ready()
|
||||||
|
feed_response = None
|
||||||
while not self.is_closed():
|
while not self.is_closed():
|
||||||
try:
|
try:
|
||||||
for entry in reversed(feedparser.parse(f"https://www.erepublik.com/en/main/news/military/all/Latvia/0/rss").entries):
|
for country in COUNTRIES.values():
|
||||||
|
latest_ts = DB.get_rss_feed_timestamp(country.id)
|
||||||
|
rss_link = f"https://www.erepublik.com/en/main/news/military/all/{country.link}/1/rss"
|
||||||
|
feed_response = requests.get(rss_link)
|
||||||
|
feed_response.raise_for_status()
|
||||||
|
for entry in reversed(feedparser.parse(feed_response.text).entries):
|
||||||
entry_ts = time.mktime(entry["published_parsed"])
|
entry_ts = time.mktime(entry["published_parsed"])
|
||||||
if entry_ts > self.last_event_timestamp:
|
entry_link = entry["link"]
|
||||||
|
# Check if event timestamp is after latest processed event for country
|
||||||
|
if entry_ts > latest_ts:
|
||||||
|
DB.set_rss_feed_timestamp(country.id, entry_ts)
|
||||||
|
title = text = ""
|
||||||
msg = entry["summary"]
|
msg = entry["summary"]
|
||||||
title = ""
|
dont_send = False
|
||||||
for kind in events:
|
for kind in events:
|
||||||
match = kind.regex.search(msg)
|
match = kind.regex.search(msg)
|
||||||
if match:
|
if match:
|
||||||
text = kind.format.format(**dict(match.groupdict(), **{"current_country": "Latvia"}))
|
values = match.groupdict()
|
||||||
|
# Special case for Dictator/Liberation wars
|
||||||
|
if "invader" in values and not values["invader"]:
|
||||||
|
values["invader"] = values["defender"]
|
||||||
|
|
||||||
|
# Special case for resource concession
|
||||||
|
if "link" in values:
|
||||||
|
__link = values["link"]
|
||||||
|
entry_link = __link if __link.startswith("http") else f"https://www.erepublik.com{__link}"
|
||||||
|
logger.debug(kind.format.format(**dict(match.groupdict(), **{"current_country": country.name})))
|
||||||
|
|
||||||
|
if country.id == 71 or any("Latvia" in v for v in values.values()):
|
||||||
|
text = kind.format.format(**dict(match.groupdict(), **{"current_country": country.name}))
|
||||||
title = kind.name
|
title = kind.name
|
||||||
|
else:
|
||||||
|
dont_send = True
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
has_unknown = True
|
|
||||||
title = "Unable to parse"
|
|
||||||
logger.warning(f"Unable to parse: {str(entry)}")
|
logger.warning(f"Unable to parse: {str(entry)}")
|
||||||
text = msg
|
continue
|
||||||
|
|
||||||
|
if dont_send:
|
||||||
|
continue
|
||||||
|
|
||||||
self.last_event_timestamp = entry_ts
|
|
||||||
entry_datetime = datetime.datetime.fromtimestamp(entry_ts, pytz.timezone("US/Pacific"))
|
entry_datetime = datetime.datetime.fromtimestamp(entry_ts, pytz.timezone("US/Pacific"))
|
||||||
embed = discord.Embed(title=title, url=entry["link"], description=text)
|
embed = discord.Embed(title=title, url=entry_link, description=text)
|
||||||
embed.set_author(name="eLatvia", icon_url="https://www.erepublik.com/images/flags/L/Latvia.gif")
|
embed.set_author(name=country.name, icon_url=f"https://www.erepublik.com/images/flags/L/{country.link}.gif")
|
||||||
embed.set_thumbnail(url="https://www.erepublik.net/images/modules/homepage/logo.png")
|
# embed.set_thumbnail(url="https://www.erepublik.net/images/modules/homepage/logo.png")
|
||||||
embed.set_footer(text=f"{entry_datetime.strftime('%F %T')} (eRepublik time)")
|
embed.set_footer(text=f"{entry_datetime.strftime('%F %T')} (eRepublik time)")
|
||||||
|
|
||||||
await self.get_channel(DEFAULT_CHANNEL_ID).send(embed=embed)
|
logger.debug(f"Message sent: {text}")
|
||||||
|
await self.send_msg(DEFAULT_CHANNEL_ID, embed=embed)
|
||||||
|
# await self.get_channel(DEFAULT_CHANNEL_ID).send(embed=embed)
|
||||||
|
|
||||||
await asyncio.sleep((self.timestamp // 300 + 1) * 300 - self.timestamp)
|
await asyncio.sleep((self.timestamp // 300 + 1) * 300 - self.timestamp)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("eRepublik event reader ran into a problem!", exc_info=e)
|
logger.error("eRepublik event reader ran into a problem!", exc_info=e)
|
||||||
try:
|
try:
|
||||||
with open(f"debug/{self.timestamp}.rss", "w") as f:
|
with open(f"debug/{self.timestamp}.rss", "w") as f:
|
||||||
f.write(r.text)
|
f.write(feed_response.text)
|
||||||
except NameError:
|
except (NameError, AttributeError):
|
||||||
logger.error("There was no Response object!", exc_info=e)
|
logger.error("There was no Response object!", exc_info=e)
|
||||||
await asyncio.sleep(10)
|
await asyncio.sleep(10)
|
||||||
|
|
||||||
@ -311,7 +198,12 @@ class MyClient(discord.Client):
|
|||||||
url=f"https://www.erepublik.com/en/military/battlefield/{battle['id']}",
|
url=f"https://www.erepublik.com/en/military/battlefield/{battle['id']}",
|
||||||
description=f"Epic battle {UTF_FLAG[invader_id]} vs {UTF_FLAG[defender_id]}!",
|
description=f"Epic battle {UTF_FLAG[invader_id]} vs {UTF_FLAG[defender_id]}!",
|
||||||
)
|
)
|
||||||
embed.set_footer(f"Round time {s_to_human(self.timestamp - battle['start'])}")
|
embed.set_footer(text=f"Round time {s_to_human(self.timestamp - battle['start'])}")
|
||||||
|
logger.debug(
|
||||||
|
f"Epic battle {UTF_FLAG[invader_id]} vs {UTF_FLAG[defender_id]}! "
|
||||||
|
f"Round time {s_to_human(self.timestamp - battle['start'])} "
|
||||||
|
f"https://www.erepublik.com/en/military/battlefield/{battle['id']}"
|
||||||
|
)
|
||||||
await self.get_channel(DEFAULT_CHANNEL_ID).send(f"{role_mapping[MENTION_MAPPING[div['div']]]}", embed=embed)
|
await self.get_channel(DEFAULT_CHANNEL_ID).send(f"{role_mapping[MENTION_MAPPING[div['div']]]}", embed=embed)
|
||||||
DB.add_epic(div.get("id"))
|
DB.add_epic(div.get("id"))
|
||||||
|
|
||||||
@ -335,10 +227,10 @@ bot = commands.Bot(command_prefix="!")
|
|||||||
|
|
||||||
@bot.event
|
@bot.event
|
||||||
async def on_ready():
|
async def on_ready():
|
||||||
logger.debug("Bot loaded")
|
logger.info("Bot loaded")
|
||||||
# print(bot.user.name)
|
# print(bot.user.name)
|
||||||
# print(bot.user.id)
|
# print(bot.user.id)
|
||||||
logger.debug("------")
|
logger.info("------")
|
||||||
|
|
||||||
|
|
||||||
@bot.command()
|
@bot.command()
|
||||||
@ -352,7 +244,9 @@ async def exit(ctx):
|
|||||||
|
|
||||||
def main():
|
def main():
|
||||||
global loop
|
global loop
|
||||||
|
logger.info("Starting Bot loop")
|
||||||
loop.create_task(bot.start(DISCORD_TOKEN))
|
loop.create_task(bot.start(DISCORD_TOKEN))
|
||||||
|
logger.info("Starting Client loop")
|
||||||
loop.create_task(client.start(DISCORD_TOKEN))
|
loop.create_task(client.start(DISCORD_TOKEN))
|
||||||
loop.run_forever()
|
loop.run_forever()
|
||||||
|
|
40
dbot/map_events.py
Normal file
40
dbot/map_events.py
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import feedparser
|
||||||
|
from constants import COUNTRIES, events
|
||||||
|
|
||||||
|
|
||||||
|
def main(country):
|
||||||
|
page = 1
|
||||||
|
has_unknown = False
|
||||||
|
while True:
|
||||||
|
for entry in feedparser.parse(f"https://www.erepublik.com/en/main/news/military/all/{country}/{page}/rss").entries:
|
||||||
|
msg = entry["summary"]
|
||||||
|
for kind in events:
|
||||||
|
match = kind.regex.search(msg)
|
||||||
|
if match:
|
||||||
|
values = match.groupdict()
|
||||||
|
if "invader" in values and not values["invader"]:
|
||||||
|
values["invader"] = values["defender"]
|
||||||
|
has_latvia = any("Latvia" in v for v in values.values())
|
||||||
|
if has_latvia:
|
||||||
|
text = kind.format.format(**dict(match.groupdict(), **{"current_country": country}))
|
||||||
|
print(f"{kind.name:<20} -||- {text:<80} -||- {entry['link']:<64} -||- {entry['published']}")
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
has_unknown = True
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
page += 1
|
||||||
|
if page > 5:
|
||||||
|
break
|
||||||
|
continue
|
||||||
|
break
|
||||||
|
if has_unknown:
|
||||||
|
print(page, entry)
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
for c in sorted(COUNTRIES.values(), key=lambda _c: _c.id):
|
||||||
|
if c.id > 35:
|
||||||
|
main(c.link)
|
||||||
|
print("Finished", c)
|
@ -2,7 +2,7 @@ import unittest
|
|||||||
|
|
||||||
from sqlite_utils.db import NotFoundError
|
from sqlite_utils.db import NotFoundError
|
||||||
|
|
||||||
from db import DiscordDB
|
from dbot.db import DiscordDB
|
||||||
|
|
||||||
|
|
||||||
class TestDatabase(unittest.TestCase):
|
class TestDatabase(unittest.TestCase):
|
||||||
@ -38,3 +38,8 @@ class TestDatabase(unittest.TestCase):
|
|||||||
self.assertTrue(self.db.add_epic(123456))
|
self.assertTrue(self.db.add_epic(123456))
|
||||||
self.assertFalse(self.db.add_epic(123456))
|
self.assertFalse(self.db.add_epic(123456))
|
||||||
self.assertTrue(self.db.get_epic(123456))
|
self.assertTrue(self.db.get_epic(123456))
|
||||||
|
|
||||||
|
def test_rss_feed(self):
|
||||||
|
self.assertEqual(self.db.get_rss_feed_timestamp(71), 0.0)
|
||||||
|
self.db.set_rss_feed_timestamp(71, 16000000)
|
||||||
|
self.assertEqual(self.db.get_rss_feed_timestamp(71), 16000000.0)
|
@ -1,6 +1,7 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
docker rm -f discord_bot
|
docker rm -f discord_bot
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
docker build --tag discord_epicbot .
|
docker build --tag discord_epicbot .
|
||||||
docker run --detach -v $PWD:/app --restart=always --name discord_bot discord_epicbot
|
docker run --detach -v ./src:/app -v ./debug:/app/debug --env-file=".env" --restart=always --name discord_bot discord_epicbot
|
||||||
|
|
||||||
|
39
logger.py
Normal file
39
logger.py
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import datetime
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from json import JSONDecodeError
|
||||||
|
from typing import Union
|
||||||
|
import time
|
||||||
|
|
||||||
|
APP_NAME = "discord_bot"
|
||||||
|
|
||||||
|
os.chdir(os.path.abspath(os.path.dirname(sys.argv[0])))
|
||||||
|
|
||||||
|
logger = logging.getLogger(APP_NAME)
|
||||||
|
formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
|
||||||
|
|
||||||
|
file_logger = logging.FileHandler(f"./logging.log", "w")
|
||||||
|
file_logger.setLevel(logging.WARNING)
|
||||||
|
file_logger.setFormatter(formatter)
|
||||||
|
logger.addHandler(file_logger)
|
||||||
|
|
||||||
|
stream_logger = logging.StreamHandler()
|
||||||
|
stream_logger.setLevel(logging.INFO)
|
||||||
|
stream_logger.setFormatter(formatter)
|
||||||
|
logger.addHandler(stream_logger)
|
||||||
|
|
||||||
|
logger.setLevel(logging.INFO)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
logger.info('Info message')
|
||||||
|
logger.debug('Debug message')
|
||||||
|
logger.warning('Warning message')
|
||||||
|
logger.error('Error message')
|
||||||
|
logger.critical('Critical message')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
225
map_events.py
225
map_events.py
@ -1,225 +0,0 @@
|
|||||||
import re
|
|
||||||
from typing import NamedTuple
|
|
||||||
import feedparser
|
|
||||||
from erepublik.constants import COUNTRIES
|
|
||||||
|
|
||||||
region = "[\w\(\)\- ']+"
|
|
||||||
country = "(Resistance force of )?[\w\(\)\- ]+"
|
|
||||||
citizen = "[\w\(\)\-\. \d]+"
|
|
||||||
|
|
||||||
|
|
||||||
class EventKind(NamedTuple):
|
|
||||||
name: str
|
|
||||||
regex: re.Pattern
|
|
||||||
format: str
|
|
||||||
|
|
||||||
|
|
||||||
events = [
|
|
||||||
EventKind(
|
|
||||||
"Region attacked",
|
|
||||||
re.compile(rf"(?P<invader>{country}) attacked (?P<region>{region}), (?P<defender>{country})"),
|
|
||||||
"{invader} attacked {defender} ({region})",
|
|
||||||
),
|
|
||||||
EventKind(
|
|
||||||
"Region secured",
|
|
||||||
re.compile(rf"(?P<region>{region}) was secured by (?P<defender>{country}) in the war versus ?(?P<invader>{country})?"),
|
|
||||||
"{defender} defended {invader}'s attack ({region})",
|
|
||||||
),
|
|
||||||
EventKind(
|
|
||||||
"Region conquered",
|
|
||||||
re.compile(rf"(?P<region>{region}) was conquered by (?P<invader>{country}) in the war versus ?(?P<defender>{country})?"),
|
|
||||||
"{invader} conquered {region} from {defender}",
|
|
||||||
),
|
|
||||||
EventKind(
|
|
||||||
"War approved",
|
|
||||||
re.compile(rf"(?P<invader>{country}) declared war on (?P<defender>{country})"),
|
|
||||||
"{invader} declared war against {defender}",
|
|
||||||
),
|
|
||||||
EventKind(
|
|
||||||
"War declared",
|
|
||||||
re.compile(rf"President of (?P<invader>{country}) proposed a war declaration against (?P<defender>{country})"),
|
|
||||||
"{invader} proposed a war declaration on {defender}",
|
|
||||||
),
|
|
||||||
EventKind(
|
|
||||||
"War rejected",
|
|
||||||
re.compile(rf"The proposal for declaring war against (?P<defender>{country}) was rejected."),
|
|
||||||
"{current_country} rejected war declaration on {defender}",
|
|
||||||
),
|
|
||||||
EventKind(
|
|
||||||
"MPP proposed",
|
|
||||||
re.compile(rf"President of (?P<country>{country}) proposed an alliance with (?P<partner>{country})"),
|
|
||||||
"{country} proposed MPP with {partner}",
|
|
||||||
),
|
|
||||||
EventKind(
|
|
||||||
"MPP approved",
|
|
||||||
re.compile(rf"(?P<country>{country}) signed an alliance with (?P<partner>{country})"),
|
|
||||||
"{country} signed a MPP with {partner}",
|
|
||||||
),
|
|
||||||
EventKind(
|
|
||||||
"MPP rejected",
|
|
||||||
re.compile(rf"The alliance between (?P<country>{country}) and (?P<partner>{country}) was rejected"),
|
|
||||||
"MPP between {country} and {partner} was rejected",
|
|
||||||
),
|
|
||||||
EventKind(
|
|
||||||
"Airstrike proposed",
|
|
||||||
re.compile(rf"President of (?P<invader>{country}) proposed an airstrike against (?P<defender>{country})"),
|
|
||||||
"{invader} proposed an airstrike against {defender}",
|
|
||||||
),
|
|
||||||
EventKind(
|
|
||||||
"Airstrike approved",
|
|
||||||
re.compile(rf"(?P<invader>{country}) prepares an airstrike on (?P<defender>{country})"),
|
|
||||||
"{invader} approved an airstrike against {defender}",
|
|
||||||
),
|
|
||||||
EventKind(
|
|
||||||
"Airstrike rejected",
|
|
||||||
re.compile(rf"The airstrike on (?P<defender>{country}) was rejected"),
|
|
||||||
"{current_country} rejected the airstrike against {defender}",
|
|
||||||
),
|
|
||||||
EventKind(
|
|
||||||
"NE proposed",
|
|
||||||
re.compile(rf"(?P<invader>{country}) has declared (?P<defender>{country}) as a Natural Enemy"),
|
|
||||||
"{invader} proposed Natural Enemy declaration against {defender}",
|
|
||||||
),
|
|
||||||
EventKind(
|
|
||||||
"NE approved",
|
|
||||||
re.compile(rf"(?P<defender>{country}) has been proposed as Natural Enemy"),
|
|
||||||
"{current_country} declared {defender} as Natural Enemy",
|
|
||||||
),
|
|
||||||
EventKind(
|
|
||||||
"NE rejected",
|
|
||||||
re.compile(rf"(?P<defender>{country}) as new Natural Enemy proposal has been rejected"),
|
|
||||||
"{current_country} rejected {defender} as Natural Enemy",
|
|
||||||
),
|
|
||||||
EventKind(
|
|
||||||
"NE stopped",
|
|
||||||
re.compile(rf"(?P<defender>{country}) is no longer a Natural Enemy for (?P<invader>{country})"),
|
|
||||||
"{invader} removed Natural Enemy from {defender}",
|
|
||||||
),
|
|
||||||
EventKind(
|
|
||||||
"NE cleared", re.compile(rf"(?P<country>{country}) no longer has a Natural Enemy"), "{country} no longer has a Natural Enemy"
|
|
||||||
),
|
|
||||||
EventKind("NE reset", re.compile("No Natural Enemy law has been proposed."), "{current_country} has proposed to clear Natural Enemy"),
|
|
||||||
EventKind(
|
|
||||||
"Peace proposal",
|
|
||||||
re.compile(rf"President of (?P<defender>{country}) proposed a peace in the war against (?P<invader>{country})"),
|
|
||||||
"{defender} proposed peace against {invader}",
|
|
||||||
),
|
|
||||||
EventKind(
|
|
||||||
"Peace proposal",
|
|
||||||
re.compile(rf"(?P<defender>{country}) proposed peace in the war against (?P<invader>{country})"),
|
|
||||||
"{defender} proposed peace against {invader}",
|
|
||||||
),
|
|
||||||
EventKind(
|
|
||||||
"Peace approved",
|
|
||||||
re.compile(rf"(?P<invader>{country}) signed a peace treaty with (?P<defender>{country})"),
|
|
||||||
"{invader} and {defender} is not in peace",
|
|
||||||
),
|
|
||||||
EventKind(
|
|
||||||
"Peace rejected",
|
|
||||||
re.compile(rf"The proposed peace treaty between (?P<defender>{country}) and (?P<invader>{country}) was rejected"),
|
|
||||||
"{defender} and {invader} did not sign a peace treaty",
|
|
||||||
),
|
|
||||||
EventKind(
|
|
||||||
"Embargo proposed",
|
|
||||||
re.compile(rf"President of (?P<major>{country}) proposed to stop the trade with (?P<minor>{country})"),
|
|
||||||
"{major} proposed trade embargo against {minor}",
|
|
||||||
),
|
|
||||||
EventKind(
|
|
||||||
"Embargo approved",
|
|
||||||
re.compile(rf"(?P<major>{country}) stopped trading with (?P<minor>{country})"),
|
|
||||||
"{major} declared trade ambargo against {minor}",
|
|
||||||
),
|
|
||||||
EventKind(
|
|
||||||
"Donation proposed",
|
|
||||||
re.compile(rf"A congress donation to (?P<org>{citizen}) was proposed"),
|
|
||||||
"{current_country} proposed a donation to {org}",
|
|
||||||
),
|
|
||||||
EventKind(
|
|
||||||
"Donation approved",
|
|
||||||
re.compile(rf"(?P<country>{country}) made a donation to (?P<org>{citizen})"),
|
|
||||||
"{current_country} approved a donation to {org}",
|
|
||||||
),
|
|
||||||
EventKind(
|
|
||||||
"Donation rejected",
|
|
||||||
re.compile(rf"The proposal for a congress donation to (?P<org>{citizen}) was rejected"),
|
|
||||||
"{current_country} rejected a donation to {org}",
|
|
||||||
),
|
|
||||||
EventKind(
|
|
||||||
"RW started",
|
|
||||||
re.compile(rf"A resistance has started in (?P<region>{region})"),
|
|
||||||
"Resistance war was opened in {region} ({current_country})",
|
|
||||||
),
|
|
||||||
EventKind(
|
|
||||||
"Res Concession",
|
|
||||||
re.compile(
|
|
||||||
rf'A Resource Concession law to //www.erepublik.com<b>(?P<target>{country})</b> <a href="https://www.erepublik.com/en/main/law/(?P<source>{country})/\d+">has been proposed</a>'
|
|
||||||
),
|
|
||||||
"{source} proposed resource concession to {target}",
|
|
||||||
),
|
|
||||||
EventKind(
|
|
||||||
"Res Concession",
|
|
||||||
re.compile(
|
|
||||||
rf'A Resource Concession law to //www.erepublik.com<b>(?P<target>{country})</b> <a href="https://www.erepublik.com/en/main/law/(?P<source>{country})/\d+">has been approved'
|
|
||||||
),
|
|
||||||
"{source} approved resource concession to {target}",
|
|
||||||
),
|
|
||||||
EventKind(
|
|
||||||
"CP impeachment",
|
|
||||||
re.compile(rf"A president impeachment against (?P<cp>{citizen}) was proposed"),
|
|
||||||
"Impeachment against {cp} president of {current_country} was proposed",
|
|
||||||
),
|
|
||||||
EventKind(
|
|
||||||
"CP impeachment",
|
|
||||||
re.compile("The president impeachment proposal has been rejected"),
|
|
||||||
"Impeachment against president of {current_country} was rejected",
|
|
||||||
),
|
|
||||||
EventKind("Minimum Wage", re.compile("A new minimum wage was proposed"), "A new minimum wage in {current_country} was proposed"),
|
|
||||||
EventKind(
|
|
||||||
"Minimum Wage",
|
|
||||||
re.compile("The proposal for a minimum wage change was rejected"),
|
|
||||||
"The new minimum wage proposal in {current_country} was rejected",
|
|
||||||
),
|
|
||||||
EventKind("WorkTax", re.compile(rf"(?P<country>{country}) now has a new Work Tax"), "{country} has new Work Tax"),
|
|
||||||
EventKind("Product Tax", re.compile(rf"Taxes for (?P<product>[\w ]+) changed"), "{current_country} changed taxes for {product}"),
|
|
||||||
EventKind(
|
|
||||||
"Product Tax",
|
|
||||||
re.compile(rf"Tax proposal of tax changes for (?P<product>[\w ]+) were rejected"),
|
|
||||||
"{current_country} rejected new taxes for {product}",
|
|
||||||
),
|
|
||||||
EventKind(
|
|
||||||
"Product Tax", re.compile(rf"New taxes for (?P<product>[\w ]+) were proposed"), "{current_country} proposed new taxes for {product}"
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def main(country):
|
|
||||||
page = 1
|
|
||||||
has_unknown = False
|
|
||||||
while True:
|
|
||||||
for entry in feedparser.parse(f"https://www.erepublik.com/en/main/news/military/all/{country}/{page}/rss").entries:
|
|
||||||
msg = entry["summary"]
|
|
||||||
for kind in events:
|
|
||||||
match = kind.regex.search(msg)
|
|
||||||
if match:
|
|
||||||
text = kind.format.format(**dict(match.groupdict(), **{"current_country": country}))
|
|
||||||
print(f"{kind.name:<20} -||- {text:<80} -||- {entry['link']:<64} -||- {entry['published']}")
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
has_unknown = True
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
page += 1
|
|
||||||
if page > 5:
|
|
||||||
break
|
|
||||||
continue
|
|
||||||
break
|
|
||||||
if has_unknown:
|
|
||||||
print(page, entry)
|
|
||||||
raise ValueError(msg)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
for c in sorted(COUNTRIES.values(), key=lambda _c: _c.id):
|
|
||||||
if c.id == 71:
|
|
||||||
main(c.link)
|
|
||||||
print("Finished", c)
|
|
@ -1,4 +1,4 @@
|
|||||||
[tool.black]
|
[tool.black]
|
||||||
line-length = 140
|
line-length = 180
|
||||||
target-version = ['py38', 'py39']
|
target-version = ['py38', 'py39']
|
||||||
|
|
||||||
|
16
run.sh
16
run.sh
@ -1,10 +1,18 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
source venv/bin/activate
|
D=test "$1" = "docker"
|
||||||
|
if test !$D ; then
|
||||||
|
source venv/bin/activate
|
||||||
|
fi
|
||||||
echo "Checking queries..."
|
echo "Checking queries..."
|
||||||
python -m unittest
|
python -m unittest
|
||||||
echo "Starting Discord bot..."
|
echo "Starting Discord bot..."
|
||||||
python discord_bot.py &
|
if test !$D ; then
|
||||||
disown -h %1
|
export $(sed ':a;N;$!ba;s/\n/ /g' .env)
|
||||||
sleep 10
|
python dbot/discord_bot.py
|
||||||
|
disown -h %1
|
||||||
|
sleep 10
|
||||||
|
else
|
||||||
|
/usr/local/bin/python /app/discord_bot.py
|
||||||
|
fi
|
||||||
echo "Done!"
|
echo "Done!"
|
||||||
|
|
||||||
|
15
setup.cfg
Normal file
15
setup.cfg
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
[flake8]
|
||||||
|
exclude = docs,.git,log,debug,venv
|
||||||
|
line_length = 180
|
||||||
|
max-line-length = 180
|
||||||
|
ignore = D100,D101,D102,D103,E203
|
||||||
|
|
||||||
|
[pycodestyle]
|
||||||
|
line_length = 180
|
||||||
|
max-line-length = 180
|
||||||
|
exclude = .git,log,debug,venv, build
|
||||||
|
|
||||||
|
[isort]
|
||||||
|
multi_line_output = 2
|
||||||
|
line_length = 180
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user