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 (17020B)
       ---
            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], [], [], 60)
          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             randint = random.randint(1, 65535)
          317             if randint > 65500 and len(hackers) > 1:
          318                 (attacker, defender) = random.sample(list(hackers.keys()), 2)
          319                 attack(hackers, attacker, defender)
          320             elif randint < 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 randint < 5 and len(hackers) > 1:
          324                 hand_of_rms(hackers, random.choice(list(hackers.keys())))
          325             elif randint < 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