idlerpg-channel-service.py - annna - Annna the nice friendly bot.
HTML git clone git://bitreich.org/annna/ git://enlrupgkhuxnvlhsf6lc3fziv5h2hhfrinws65d7roiv6bfj7d652fid.onion/annna/
DIR Log
DIR Files
DIR Refs
DIR Tags
DIR README
---
idlerpg-channel-service.py (17040B)
---
1 #!/usr/bin/env python
2 # coding=UTF-8
3 #
4 # Copy me, if you can.
5 # by 20h
6 #
7
8 import os
9 import sys
10 import getopt
11 import time
12 import random
13 import select
14 import pyinotify
15 import errno
16 import fcntl
17 import functools
18
19 def annna_alive(base):
20 if os.path.exists("%s/running" % (base)):
21 return 1
22 return 0
23
24 def readin_file(f):
25 lines = []
26 try:
27 fd = open(f)
28 except:
29 sys.exit(1)
30 lines = [e.strip() for e in fd.readlines()]
31 fd.close()
32 return lines
33
34 def readin_dictfile(f):
35 lines = []
36 rdict = {}
37 try:
38 fd = open(f)
39 except:
40 sys.exit(1)
41 lines = [e.strip().split("\t") for e in fd.readlines()]
42 fd.close()
43 for line in lines:
44 rdict[line[0]] = line[1:]
45 return rdict
46
47 def writeout_dictfile(f, d):
48 try:
49 fd = open(f, "w")
50 except:
51 sys.exit(1)
52 for key in d.keys():
53 fd.write("%s\t%s\n" % (key, "\t".join([str(s) for s in d[key]])))
54 fd.flush()
55 fd.close()
56
57 def say(fpath, text):
58 try:
59 fd = open(fpath, "w")
60 fd.write("%s\n" % (text))
61 fd.flush()
62 fd.close()
63 except:
64 sys.exit(1)
65
66 def usage(app):
67 app = os.path.basename(app)
68 print("usage: %s [-h]" % (app), file=sys.stderr)
69 sys.exit(1)
70
71 def main(args):
72 try:
73 opts, largs = getopt.getopt(args[1:], "h")
74 except getopt.GetoptError as err:
75 print(str(err))
76 usage(args[0])
77
78 for o, a in opts:
79 if opts == "-h":
80 usage(args[0])
81 else:
82 assert False, "unhandled option"
83
84 annnabase = os.environ["ANNNA_BASE"]
85 ircuser = os.environ["IRC_USER"]
86 basepath = os.environ["SERVICE_BASE"]
87 ircpath = os.environ["ANNNA_IRCBASE"]
88 server = os.environ["IRC_SERVER"]
89 channel = os.environ["IRC_CHANNEL"]
90 serverpath = "%s/%s" % (ircpath, server)
91 chanpath = "%s/%s" % (serverpath, channel)
92 chaninpath = "%s/in" % (chanpath)
93
94 def get_channel_users():
95 say(chaninpath, "/names %s\n" % (channel))
96 serveroutlines = readin_file("%s/out" % (serverpath))
97 namesstring = " 353 %s = %s :" % (ircuser, channel)
98 users = []
99 for line in serveroutlines[::-1]:
100 if namesstring in line:
101 for user in line.strip().split(namesstring)[1].split(" "):
102 if user.startswith("@"):
103 user = user[1:]
104 if user not in users:
105 users.append(user)
106 return users
107
108 users = get_channel_users()
109 if len(users) == 0:
110 return 1
111
112 penalties = readin_dictfile("%s/penalties.txt" % (basepath))
113 classes = readin_dictfile("%s/classes.txt" % (basepath))
114 hardware = readin_dictfile("%s/hardware.txt" % (basepath))
115 shields = readin_dictfile("%s/shields.txt" % (basepath))
116 weapons = readin_dictfile("%s/weapons.txt" % (basepath))
117 quests = readin_dictfile("%s/quests.txt" % (basepath))
118 events = readin_dictfile("%s/events.txt" % (basepath))
119 hackers = readin_dictfile("%s/hackers.txt" % (basepath))
120 for hacker in hackers.keys():
121 hackers[hacker][0] = int(hackers[hacker][0])
122 hackers[hacker][5] = int(hackers[hacker][5])
123 # All are offline by default.
124 try:
125 hackers[hacker][6] = 0
126 except IndexError:
127 hackers[hacker].append(0)
128
129 admins = readin_dictfile("%s/admins.txt" % (basepath))
130
131 def random_hacker():
132 hacker = []
133 # Idletime
134 hacker.append(0)
135 # Class
136 hacker.append(random.choice(list(classes.keys())))
137 # Hardware
138 hacker.append(random.choice(list(hardware.keys())))
139 # Shield
140 hacker.append(random.choice(list(shields.keys())))
141 # Weapon
142 hacker.append(random.choice(list(weapons.keys())))
143 # Level
144 hacker.append(0)
145 # Online
146 hacker.append(1)
147 return hacker
148
149 def calamity(hackers, hacker):
150 calamity_type = random.randint(1, 10)
151 if calamity_type == 1:
152 change_type = random.randint(1, 4)
153 if change_type == 1:
154 new_class = None
155 while new_class != hackers[hacker][1]:
156 new_class = random.choice(list(classes.keys()))
157 hackers[hacker][1] = new_class
158 say(chaninpath, "Due to a bit flip during a solar flare, " \
159 "%s's hacker class changed to %s." \
160 % (hacker, hackers[hacker][1]))
161 elif change_type == 2:
162 new_hardware = None
163 while new_hardware != hackers[hacker][2]:
164 new_hardware = random.choice(list(hardware.keys()))
165 hackers[hacker][2] = new_hardware
166 say(chaninpath, "It is %s's birthday. " \
167 "%s's hardware changed to %s." \
168 % (hacker, hacker, hackers[hacker][2]))
169 elif change_type == 3:
170 new_shield = None
171 while new_shield != hackers[hacker][3]:
172 new_shield = random.choice(list(shields.keys()))
173 hackers[hacker][3] = new_shield
174 say(chaninpath, "%s fell over a Windows 95 CD. " \
175 "%s's shield changed to %s." \
176 % (hacker, hacker, hackers[hacker][3]))
177 elif change_type == 4:
178 new_weapon = None
179 while new_weapon != hackers[hacker][4]:
180 new_weapon = random.choice(list(weapons.keys()))
181 hackers[hacker][4] = new_weapon
182 say(chaninpath, "%s had to reinstall the OS. " \
183 "%s's weapon changed to %s." \
184 % (hacker, hacker, hackers[hacker][4]))
185 else:
186 event = random.choice(list(events.keys()))
187 boost = random.randint(-10, 10) * 100
188 hackers[hacker][0] += boost
189 say(chaninpath, "%s! This caused the idle time to " \
190 "change by %d to %d." \
191 % (event % (hacker), boost, hackers[hacker][0]))
192
193 def hand_of_rms(hackers, hacker):
194 win = random.randint(0, 5)
195 rmstime = random.randint(1, 10) * 100
196 if win:
197 hackers[hacker][0] += rmstime
198 say(chaninpath, "The holy hand of RMS moved %s %d seconds" \
199 " forward in idle time." % (hacker, rmstime))
200 else:
201 hackers[hacker][0] -= rmstime
202 say(chaninpath, "RMS had a bad day and moved %s %d seconds" \
203 " back in idle time." % (hacker, rmstime))
204
205 def go_on_quest(hackers, questhackers):
206 quest = random.choice(list(quests.keys()))
207 success = random.randint(1, 16)
208 damage = (success - 5) * 100
209 if damage >= 0:
210 say(chaninpath, quest \
211 % (", ".join(questhackers), "succeeded", damage))
212 else:
213 say(chaninpath, quest \
214 % (", ".join(questhackers), "failed", damage))
215 for hacker in questhackers:
216 hackers[hacker][0] += damage
217
218 def attack(hackers, attacker, defender):
219 attackweapon = hackers[attacker][4]
220 defendweapon = hackers[defender][4]
221 attackshield = hackers[attacker][3]
222 defendshield = hackers[defender][3]
223
224 attackweapon_roll = random.randint(1,12)
225 defendshield_roll = random.randint(1,12)
226 damage = (attackweapon_roll - defendshield_roll) * 100
227
228 attackinfo = "The hacker "
229 if damage > 0:
230 # Attack success
231 hackers[defender][0] -= damage
232 attackinfo = "%s attacked %s with a %s" \
233 ", causing %s seconds of activity. %s, your " \
234 "idle time has been increased by %d to %s." \
235 % (attacker, defender, attackweapon, damage, defender, \
236 damage, hackers[defender][0])
237 elif damage < 0:
238 # Defence success
239 damage = abs(damage)
240 hackers[attacker][0] -= damage
241 attackinfo = "%s defended an attack from %s " \
242 "with their %s, causing %s seconds of activity. %s, " \
243 "your idle time has been reduced by %d to %s." \
244 % (defender, attacker, defendshield, damage, attacker, \
245 damage, hackers[attacker][0])
246 else:
247 attackinfo = "%s attacked %s with a %s" \
248 ", but %s defended so well with %s, " \
249 "that no damage occured." \
250 % (attacker, defender, attackweapon, defender, \
251 defendshield)
252 say(chaninpath, attackinfo)
253
254 def hacker_info(hackers, hacker):
255 hackerinfo = "The hacker %s of the class %s " % (hacker, hackers[hacker][1])
256 hackerinfo += "is using %s hardware " % (hackers[hacker][2])
257 hackerinfo += "which is protected by %s. " % (hackers[hacker][3])
258 hackerinfo += "%s's weapon is %s. " % (hacker, hackers[hacker][4])
259 hackerinfo += "%s has idled for %d seconds and has reached level %d." % (hacker, hackers[hacker][0], hackers[hacker][5])
260 say(chaninpath, hackerinfo)
261
262 def update_hackers_from_users(hackers, users):
263 for user in users:
264 # Build a hacker for newly appeared irc user
265 if user not in list(hackers.keys()) and user != ircuser:
266 hackers[user] = random_hacker()
267 elif user in list(hackers.keys()):
268 hackers[user][6] = 1
269
270 def sync_hackers_with_channel(hackers):
271 users = get_channel_users()
272 update_hackers_from_users(hackers, users)
273
274 update_hackers_from_users(hackers, users)
275
276 try:
277 inotifywm = pyinotify.WatchManager()
278 inotifywm.add_watch("%s/out" % (chanpath), pyinotify.IN_MODIFY)
279 except:
280 sys.exit(1)
281 inotifyfd = inotifywm.get_fd()
282
283 def event_processor(notifier):
284 pass
285 notifier = pyinotify.Notifier(inotifywm, default_proc_fun=event_processor)
286
287 try:
288 chanoutfd = open("%s/out" % (chanpath), "r+")
289 except:
290 sys.exit(1)
291
292 chanoutfd.readlines()
293 while annna_alive(annnabase):
294 # Game ticks every 5 seconds.
295 try:
296 (rfds, wfds, sfds) = select.select([inotifyfd], [], [], 5)
297 except select.error as err:
298 if err.args[0] == errno.EINTR:
299 continue
300 break
301 if rfds == [] and wfds == [] and sfds == []:
302 for hacker in hackers.keys():
303 # Is offline.
304 if hackers[hacker][6] == 0:
305 continue
306
307 hackers[hacker][0] += 5
308 # Level up every 5 days.
309 newlevel = int(hackers[hacker][0]/(86400*5))
310 if newlevel > hackers[hacker][5]:
311 say(chaninpath, "%s levelled up to level %s!" % (hacker, newlevel))
312 elif newlevel < hackers[hacker][5]:
313 say(chaninpath, "%s levelled down to level %s." % (hacker, newlevel))
314 hackers[hacker][5] = newlevel
315
316 if random.randint(1, 65535) > 65500 and len(hackers) > 1:
317 (attacker, defender) = random.sample(list(hackers.keys()), 2)
318 attack(hackers, attacker, defender)
319 elif random.randint(1, 65535) < 30 and len(hackers) > 1:
320 questhackers = random.sample(list(hackers.keys()), random.randint(1, len(hackers)))
321 go_on_quest(hackers, questhackers)
322 elif random.randint(1, 65535) < 5 and len(hackers) > 1:
323 hand_of_rms(hackers, random.choice(list(hackers.keys())))
324 elif random.randint(1, 65535) < 10 and len(hackers) > 1:
325 calamity(hackers, random.choice(list(hackers.keys())))
326
327 writeout_dictfile("%s/hackers.txt" % (basepath), hackers)
328 continue
329
330 notifier.read_events()
331 notifier.process_events()
332
333 lines = chanoutfd.readlines()
334 lines = [line.strip() for line in lines]
335 for line in lines:
336 if line == None or line == "":
337 continue
338
339 penalty = None
340 try:
341 (timestamp, user, remain) = line.split(" ", 2)
342 except ValueError:
343 continue
344
345 if user.startswith("<") and user.endswith(">"):
346 hacker = user.split("<", 1)[1].split(">", 1)[0]
347 is_admin = False
348 if hacker in admins.keys():
349 is_admin = True
350 else:
351 penalty = "text"
352 if remain.startswith("!"):
353 (cmd, *cmdargs) = remain.split(" ")
354 if cmd == "!info" and is_admin and len(cmdargs) > 0:
355 if cmdargs[0] in hackers:
356 hacker_info(hackers, cmdargs[0])
357 else:
358 hacker_info(hackers, hacker)
359 elif cmd == "!attack":
360 if len(cmdargs) > 0 and cmdargs[0] in hackers:
361 attack(hackers, hacker, cmdargs[0])
362 else:
363 (attacker, defender) = random.sample(list(hackers.keys()), 2)
364 attack(hackers, attacker, defender)
365 elif cmd == "!quest":
366 if len (cmdargs) > 0 and cmdargs[0] in hackers:
367 argsinhackers = [hacker]
368 for cmdarg in cmdargs:
369 if cmdarg in hackers:
370 argsinhackers.append(cmdarg)
371 go_on_quest(hackers, argsinhackers)
372 else:
373 questhackers = random.sample(list(hackers.keys()), random.randint(1, len(hackers)))
374 go_on_quest(hackers, questhackers)
375 elif cmd == "!hor":
376 if len(cmdargs) > 0 and cmdargs[0] in hackers:
377 hand_of_rms(hackers, cmdargs[0])
378 else:
379 hand_of_rms(hackers, random.choice(list(hackers.keys())))
380 elif cmd == "!calamity":
381 if len(cmdargs) > 0 and cmdargs[0] in hackers:
382 calamity(hackers, cmdargs[0])
383 else:
384 calamity(hackers, random.choice(list(hackers.keys())))
385 elif cmd == "!stats":
386 say(chaninpath, "%s, try gophers://bitreich.org/1/irc/idlerpg" \
387 % (hacker))
388
389 elif user == "-!-":
390 (hacker, text) = remain.split(" ", 1)
391 if "has joined " in text:
392 penalty = "join"
393 hacker = hacker.split("(", 1)[0]
394 if hacker not in hackers:
395 hackers[hacker] = random_hacker()
396 hacker_info(hackers, hacker)
397 else:
398 hackers[hacker][6] = 1
399 sync_hackers_with_channel(hackers)
400 elif "has left " in text:
401 penalty = "part"
402 hacker = hacker.split("(", 1)[0]
403 if hacker in hackers:
404 hackers[hacker][6] = 0
405 sync_hackers_with_channel(hackers)
406 elif "has quit " in text:
407 penalty = "quit"
408 hacker = hacker.split("(", 1)[0]
409 if hacker in hackers:
410 hackers[hacker][6] = 0
411 sync_hackers_with_channel(hackers)
412 elif "changed nick to " in text:
413 # TODO: Fix. It is now in channelmaster.
414 # Instead we sync on part and quit.
415 penalty = "nick"
416 newhacker = text.split("to ", 1)[1].split("\"")[1]
417 if newhacker not in hackers:
418 hackers[newhacker] = random_hacker()
419 hacker_info(hackers, newhacker)
420 elif "kicked " in text:
421 penalty = "kick"
422 hacker = text.split(" ", 3)[2]
423 if hacker in hackers:
424 hackers[hacker][6] = 0
425 sync_hackers_with_channel(hackers)
426
427 if hacker == ircuser:
428 continue
429 if hacker not in hackers:
430 continue
431
432 if penalty != None and penalty in penalties:
433 penaltytime = int(penalties[penalty][0])
434 hackers[hacker][0] -= penaltytime
435 say(chaninpath, "%s, your idletime has been reduced by %d to %d due to the %s penalty." \
436 % (hacker, penaltytime, hackers[hacker][0], penalty))
437 writeout_dictfile("%s/hackers.txt" % (basepath), hackers)
438
439 return 0
440
441 if __name__ == "__main__":
442 sys.exit(main(sys.argv))
443