first commit

This commit is contained in:
iou1name 2018-06-11 09:33:52 -04:00
commit 560b016485
6 changed files with 18259 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*.cfg
*.swp

24
README.md Normal file
View File

@ -0,0 +1,24 @@
# WarBot
Because I can't name projects to save my life.
WarBot (working title) is an IRC bot which joins channel and periodically checks Warframe's public API for new alerts and announces them into the channel. More features coming soon.
## Dependencies
Python 3.6+
`twisted requests`
## Config
`server` - Server address.
`port` - Server port. SSL probably won't work.
`nickname` - The bot's nickname.
`username` - The user ident.
`channel` - The channel to join and spam alerts to.
### Example config.cfg
```
[default]
server = irc.steelbea.me
port = 6667
nickname = Cephalon
ident = Cephalon
channel = #warframe
```

16188
static/languages.json Normal file

File diff suppressed because it is too large Load Diff

59
static/missionTypes.json Normal file
View File

@ -0,0 +1,59 @@
{
"MT_EXCAVATE": {
"value": "Excavation"
},
"MT_SABOTAGE": {
"value": "Sabotage"
},
"MT_MOBILE_DEFENSE": {
"value": "Mobile Defense"
},
"MT_ASSASSINATION": {
"value": "Assassination"
},
"MT_EXTERMINATION": {
"value": "Extermination"
},
"MT_HIVE": {
"value": "Hive"
},
"MT_DEFENSE": {
"value": "Defense"
},
"MT_TERRITORY": {
"value": "Interception"
},
"MT_ARENA": {
"value": "Rathuum"
},
"MT_PVP": {
"value": "Conclave"
},
"MT_RESCUE": {
"value": "Rescue"
},
"MT_INTEL": {
"value": "Spy"
},
"MT_SURVIVAL": {
"value": "Survival"
},
"MT_CAPTURE": {
"value": "Capture"
},
"MT_SECTOR": {
"value": "Dark Sector"
},
"MT_RETRIEVAL": {
"value": "Hijack"
},
"MT_ASSAULT": {
"value": "Assault"
},
"MT_EVACUATION": {
"value": "Defection"
},
"MT_LANDSCAPE": {
"value": "Free Roam"
}
}

1792
static/solNodes.json Normal file

File diff suppressed because it is too large Load Diff

194
warbot.py Executable file
View File

@ -0,0 +1,194 @@
#!/usr/bin/env python3
"""
An IRC bot which tracks new alerts and invasions for Warframe and reports
them to channel. Only intended to be used in one channel at a time.
"""
import os
import json
import time
import functools
import threading
import configparser
import requests
from twisted.internet import protocol, reactor
from twisted.words.protocols import irc
URI = "http://content.warframe.com/dynamic/worldState.php"
class ClockThread(threading.Thread):
"""
A thread class which acts like a clock for signaling the bot to check
the world state every minute.
"""
def __init__(self, bot):
threading.Thread.__init__(self)
self.bot = bot
def run(self):
while not stop.is_set():
self.bot.checkNewAlerts()
stop.wait(60)
class WarBot(irc.IRCClient):
def __init__(self, config):
self.config = config
self.nickname = self.config["nickname"]
self.username = self.config["ident"]
self.channel = self.config["channel"]
with open(os.path.join("static", "languages.json"), "r") as file:
self.languages = json.loads(file.read())
with open(os.path.join("static", "missionTypes.json"), "r") as file:
self.missionTypes = json.loads(file.read())
with open(os.path.join("static", "solNodes.json"), "r") as file:
self.solNodes = json.loads(file.read())
self.clockThread = ClockThread(self)
self.clockThread.start()
self.alert_ids = []
def stillConnected(self):
"""Returns true if the bot is still connected to the server."""
if self._heartbeat:
return True
else:
return False
def clock(self):
"""
A continous loop which calls the processing functions once per
minute. Should only be ran in a separate thread.
"""
while not stop.is_set():
self.checkNewAlerts()
stop.wait(60)
def checkNewAlerts(self):
"""
Checks the world state for new alerts or invasions.
"""
while not self.stillConnected(): # startup reasons
time.sleep(1)
try:
res = requests.get(URI)
res.raise_for_status()
except requests.exceptions.ConnectionError:
return
data = res.json()
alert_ids = [alert["_id"]["$oid"] for alert in data["Alerts"]]
new_alerts = [a for a in alert_ids if a not in self.alert_ids]
self.alert_ids += new_alerts
for alert in data["Alerts"]:
if alert["_id"]["$oid"] in new_alerts:
self.process_alert(alert)
expired_alerts = [a for a in self.alert_ids if a not in alert_ids]
for alert_id in expired_alerts:
self.alert_ids.remove(alert_id)
def process_alert(self, alert):
"""
Processes the provided alert and sends a message to channel.
"""
info = alert["MissionInfo"]
node = self.solNodes[info["location"]]
mission_type = self.missionTypes[info["missionType"]]["value"]
credits = info["missionReward"].get("credits")
items = info["missionReward"].get("items", [])
if items:
items = [self.languages[item.lower()]["value"] for item in items]
cItems = info["missionReward"].get("countedItems")
if cItems:
for item in cItems:
itemStr = f"({item['ItemCount']}) "
itemStr += self.languages[item["ItemType"].lower()]["value"]
items.append(itemStr)
expire = int(alert["Expiry"]["$date"]["$numberLong"])
expire_diff = int(expire/1000 - time.time())
expire_t = time.localtime(expire)
message = "\x0300[\x0305ALERT\x0300] " \
+ f"\x0310Location\x03: \x0312{node['value']}\x03 | " \
+ f"\x0310Mission Type\x03: \x0312{mission_type}\x03 | " \
+ f"\x0310Expiry\x03: \x0308{expire_diff // 3600}h" \
+ f"{expire_diff % 3600 // 60}m\x03 " \
+ f"(\x0308{time.strftime('%H:%M', expire_t)}\x03)" \
+ f" | \x0310Credits\x03: \x0312{credits}\x03"
if items:
message += f" | \x0310Items: \x0312{', '.join(items)}\x03"
self.msg(self.channel, message)
def privmsg(self, user, channel, message):
"""
Called when the bot receives a PRIVMSG, which can come from channels
or users alike.
"""
pass
def joined(self, channel):
"""Called when the bot joins a new channel."""
print("Joined", channel)
def signedOn(self):
"""Called when the bot successfully connects to the server."""
print("Signed on as", self.nickname)
self.mode(self.nickname, True, "B") # set +B on self
self.join(self.channel)
def nickChanged(self, nick):
"""Called when my nick has been changed."""
print("Nick changed to", nick)
class WarBotFactory(protocol.ReconnectingClientFactory):
# black magic going on here
protocol = property(lambda s: functools.partial(WarBot, s.config))
def __init__(self, config):
self.config = config
stop = threading.Event()
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(
description="Downloads new torrents from a torrent tracker's IRC " \
+ "announce channel.")
parser.add_argument(
"-c",
"--config",
default="config.cfg",
help="Specify an alternate config file to use.")
args = parser.parse_args()
config = configparser.ConfigParser()
config.read(args.config)
config = config[config.sections()[0]] #only use the first section
server = config["server"]
port = config.getint("port")
print("Connecting to:", server)
reactor.connectTCP(server, port, WarBotFactory(config))
reactor.addSystemEventTrigger('before','shutdown', stop.set)
reactor.run()