-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathmain.py
132 lines (107 loc) · 4.17 KB
/
main.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
import asyncio
import re
import time
import os
import logging
from os.path import dirname
from watchdog.observers import Observer
from watchdog.events import PatternMatchingEventHandler
import discord
from discord.ext import commands
from mcrcon import MCRcon
logger = logging.getLogger('discord')
logger.setLevel(logging.DEBUG)
class FacLogHandler(PatternMatchingEventHandler):
# Watches a log file for useful events
def __init__(self, fbot, logfile):
self.fbot = fbot
self.log_loc = logfile
self.logfile = None
super().__init__([logfile])
self.spin_up()
self.observer = Observer()
self.observer.schedule(self, dirname(self.log_loc), recursive=False)
self.observer.start()
def spin_up(self):
# When the bridge first starts up, wait for factorio to start and
# create the log file, then read to the end so we don't duplicate
# any messages
self.logfile = None
elapsed = 0
while self.logfile is None:
try:
self.logfile = open(self.log_loc, 'r')
except:
time.sleep(1)
if elapsed > 20:
raise Exception("Timed out opening log file!")
# read up to last line before EOF
for line in self.logfile:
pass
def on_created(self, event):
# Factorio moves the file aside and creates a new one.
# Close the existing file and look for the new one.
self.logfile.close()
self.spin_up()
def on_modified(self, event):
# When a line is written to the log, handle any action needed.
# Right now, there's only chat.
for line in self.logfile:
m = re.match("^[-0-9: ]+\[([A-Z]+)\] (.+)$", line)
if m is None:
continue
dispatch = {
"CHAT": self.got_chat
}
method = dispatch.get(m.group(1), None)
if method is not None:
method(m.group(2))
else:
self.default_handler(m.group(1), m.group(2))
def default_handler(self, kind, text):
logger.debug("Factorio sent unknown '%s': '%s'", kind, text)
channel = self.fbot.get_channel(self.fbot.bridge_id)
coro = channel.send(text)
asyncio.run_coroutine_threadsafe(coro, self.fbot.loop)
def got_chat(self, text):
logger.debug("Factorio sent CHAT '%s'", text)
user, msg = text.split(": ", 1)
if user == "<server>":
return
channel = self.fbot.get_channel(self.fbot.bridge_id)
coro = channel.send(text)
asyncio.run_coroutine_threadsafe(coro, self.fbot.loop)
class FacBot(commands.Bot):
# Interacts with discord
def __init__(self, bridge_id, data_dir, host):
super().__init__(['/', ''])
self.bridge_id = int(bridge_id)
self.data_dir = data_dir
self.host = host
with open(data_dir + "/config/rconpw", "r") as f:
self.pw = f.readline().strip()
self.log_in = FacLogHandler(self, data_dir + "/factorio-current.log")
async def on_message(self, message):
if message.author.bot:
return # don't listen to other bots....
ctx = await self.get_context(message)
if ctx.prefix == '/':
# an actual command
return await self.invoke(ctx)
elif ctx.channel.id == self.bridge_id:
# a chat message
return await self.send_to_factorio(ctx)
async def send_to_factorio(self, ctx):
msg = "{}: {}".format(ctx.author.display_name, ctx.message.content)
logger.debug("Msg from discord: \"{}\"".format(msg))
with MCRcon(self.host, self.pw, 27015) as rcon:
resp = rcon.command(msg)
logger.debug("Response from factorio: '%s'", resp)
async def on_ready(self):
logger.info("Connected to discord")
if __name__ == "__main__":
logger.info("Starting discord/factorio bridge....")
fb = FacBot(os.environ['CHANNEL_ID'],
os.environ["FACTORIO_DATA_DIR_PATH"],
os.environ.get("FACTORIO_HOST", '127.0.0.1'))
fb.run(os.environ["DISCORD_KEY"])