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