-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathbls-mod-updating-bot.py
150 lines (122 loc) · 4.62 KB
/
bls-mod-updating-bot.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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
#!/usr/bin/env python3
#
# A very quick script to update BlS mods when requested
#
import collections, datetime, io, itertools, miniirc_discord, os, re, \
subprocess, threading, time
# I am too lazy to do config files
CHANNELS = ('#<channel-id-here>',)
DISALLOWED = ('lurklite#1029',)
TOKEN = '(Discord token)'
CRASH_LOGS_DIR = '/path/to/logs/directory'
irc = miniirc_discord.Discord(TOKEN, auto_connect=False, stateless_mode=True)
lock = threading.Lock()
_logs_re = re.compile(
r'^(?:show|give) me (?:(?:crash|error) logs?|what you got)(?: ([0-9]+))?$'
)
@irc.Handler('PRIVMSG', colon=False)
def handler(irc, hostmask, args):
lmsg = args[1].lower().strip()
match = _logs_re.fullmatch(lmsg)
if match:
if hostmask[1] in DISALLOWED:
irc.msg(args[0], 'No')
return
try:
logs = find_crash_logs(int(match.group(1) or 1))
except Exception as exc:
irc.msg(args[0], f'{exc.__class__.__name__}: {exc}')
raise
irc.msg(args[0], f'```\n{logs}\n```')
return
elif lmsg != 'do the thing':
return
if hostmask[1] in DISALLOWED:
irc.msg(args[0], 'Do what? How dare you tell me what to do?')
return
if lock.locked():
irc.msg(args[0], 'Please be patient! You have to wait at least 30 '
'seconds between mod updates.')
return
with lock:
irc.msg(args[0], 'Updating mods, just a moment...')
print('Updating mods...')
try:
env = os.environ.copy()
env['GIT_TERMINAL_PROMPT'] = '0'
subprocess.check_call(('bash', 'update-bls-mods.sh'), env=env)
except (FileNotFoundError, subprocess.CalledProcessError) as exc:
irc.msg(args[0], f'Uh-oh, there was a problem while updating mods!'
f'\n> {exc}')
print(f'Got error while updating mods: {exc!r}')
else:
irc.msg(args[0], 'Alright, mods updated.')
print('Mods updated!')
time.sleep(30)
def read_crash_logs(fn):
# This uses grep because I'm not sure how fast this would be in Python
try:
return subprocess.check_output((
'grep', '-aP', r'^[0-9\-]+ [0-9:]+ ERROR\[Main\]: '
r'(?!The following mods could not be found)', '--', fn,
)).decode('utf-8', 'replace')
except subprocess.CalledProcessError:
return ''
_minute_re = re.compile(r'^[0-9\-]+ [0-9]{2}:[0-9]{2}:')
def _lines_to_incident(incident_lines):
logs = '\n'.join(incident_lines)
if len(logs) > 1990:
logs = logs[-1990:].split('\n', 1)[-1]
return logs
def find_crash_logs_iter():
now = datetime.datetime.now()
not_before = now.strftime('%m%d.txt')
files = os.listdir(CRASH_LOGS_DIR)
files.sort(reverse=True)
long_ago_dt = now - datetime.timedelta(days=300)
long_ago_ts = long_ago_dt.timestamp()
for fn in files:
# Ignore files that have wrapped around
if fn > not_before:
continue
# Ignore files more than a year old because the logs are going to wrap
# around which will get messy
path = os.path.join(CRASH_LOGS_DIR, fn)
if os.path.getmtime(path) < long_ago_ts:
continue
# Try and read crash logs (and if there are none skip to the next file)
raw_logs = read_crash_logs(path).strip()
if not raw_logs:
continue
# Only return logs from the same incident
all_lines = raw_logs.split('\n')
incident_lines = collections.deque()
prefix = _minute_re.match(all_lines[-1]).group(0)
for line in reversed(all_lines):
if not line.startswith(prefix):
# Stop processing this logfile if we encounter a crash from
# long ago.
dt = datetime.datetime.fromisoformat(line.split(' ', 1)[0])
if dt < long_ago_dt:
break
# Yield the current incident
yield _lines_to_incident(incident_lines)
# New incident
incident_lines.clear()
prefix = _minute_re.match(line).group(0)
incident_lines.appendleft(line)
# Yield the last incident
yield _lines_to_incident(incident_lines)
def find_crash_logs(num=1):
it = find_crash_logs_iter()
if num > 1:
it = itertools.islice(it, num - 1, None)
elif num != 1:
return 'Crash log incident numbers start at 1'
try:
return next(it)
except StopIteration:
return 'No crash logs found!'
if __name__ == '__main__':
irc.connect()
irc.wait_until_disconnected()