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 (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" % (app), file=sys.stderr) 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, hackers[hacker][1]) 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 %d." % (hacker, hackers[hacker][0], hackers[hacker][5]) 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_processor) 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!" % (hacker, newlevel)) 313 elif newlevel < hackers[hacker][5]: 314 say(chaninpath, "%s levelled down to level %s." % (hacker, newlevel)) 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()), 2) 319 attack(hackers, attacker, defender) 320 elif random.randint(1, 65535) < 30 and len(hackers) > 1: 321 questhackers = random.sample(list(hackers.keys()), random.randint(1, len(hackers))) 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(hackers.keys()), 2) 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.keys()), random.randint(1, len(hackers))) 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(hackers.keys()))) 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.keys()))) 386 elif cmd == "!stats": 387 say(chaninpath, "%s, try gophers://bitreich.org/1/irc/idlerpg" \ 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 %d to %d due to the %s penalty." \ 437 % (hacker, penaltytime, hackers[hacker][0], penalty)) 438 writeout_dictfile("%s/hackers.txt" % (basepath), hackers) 439 440 return 0 441 442 if __name__ == "__main__": 443 sys.exit(main(sys.argv)) 444