URI: 
       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