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 (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