intel-screenbot

An Ingress Intel Map Bot for Hangoutsbot
git clone git://archive.git.mtrnord.blog/MTRNord/intel-screenbot.git
Log | Files | Refs | README | LICENSE

__init__.py (22403B)


      1 from bs4 import BeautifulSoup
      2 import requests
      3 import json
      4 import asyncio, io, logging, os, tempfile
      5 import plugins
      6 import re
      7 
      8 logger = logging.getLogger(__name__)
      9 
     10 
     11 def _initialise(bot):
     12     plugins.register_user_command(["intel", "iitc", "portalpic", "portalinfo", "active_iitcplugins"])
     13     plugins.register_admin_command(["setintel", "show_iitcplugins", "set_iitcplugins"])
     14     _get_iitc_plugins(bot)
     15 
     16 
     17 @asyncio.coroutine
     18 def _open_file(name, otype):
     19     return open(name, otype)
     20 
     21 @asyncio.coroutine
     22 def readline(f):
     23     while True:
     24         data = f.readline()
     25         if data:
     26             return data
     27 
     28 def _parse_onlineRepos(url, ext=''):
     29     page = requests.get(url).text
     30     if 'gitlab.com' in url:
     31         files = []
     32         for json_page in json.loads(page):
     33             for attribute, value in json_page.items():
     34                 if attribute == "name":
     35                     if value.endswith(ext):
     36                         files.append(url.replace("/tree/", "/blobs/master") + "&filepath="  + value)
     37         return files
     38     elif 'github.com' in url:
     39         files = []
     40         for attribute, value in json.loads(page).items():
     41             if attribute == "tree":
     42                 for tree in value:
     43                     for attribute, value in tree.items():
     44                         if attribute == "path":
     45                             if value.endswith(ext) and not 'total-conversion-build.user.js' in value and not 'user-location.user.js' in value and not 'test' in value:
     46                                 files.append(url.replace("https://api.github.com/repos/", "https://raw.githubusercontent.com/").replace("git/trees/",'').replace("master?recursive=1","master/") + value)
     47         return files
     48     else:
     49         soup = BeautifulSoup(page, 'html.parser')
     50         return [url + '/' + node.get('href') for node in soup.find_all('a') if node.get('href').endswith(ext)]
     51 
     52 def _get_iitc_plugins(bot):
     53     logger.debug("getting availible plugins")
     54     if bot.config.exists(["intel_screenbot", "gitlab_token"]):
     55         token = bot.config.get_by_path(["intel_screenbot", "gitlab_token"])
     56     url_config = bot.config.get_by_path(["intel_screenbot", "plugin_dirs"])
     57     ext = '.user.js'
     58     data=[]
     59     for url in url_config:
     60         if ext in url:
     61             item = {"name": url.split('/', url.count('/'))[-1].replace(ext, ''), "url": url}
     62             data.append(item)
     63         else:
     64             if "gitlab.com" in url:
     65                 url = url + '?private_token=' + token
     66             for file in _parse_onlineRepos(url, ext):
     67                 if "gitlab.com" in url:
     68                     item = {"name": file.split('=', file.count('='))[-1].replace(ext, ''), "url": file}
     69                 elif 'github.com' in url:
     70                     item = {"name": file.split('/', file.count('/'))[-1].replace(ext, ''), "url": file}
     71                 else:
     72                     item = {"name": file.split('/', file.count('/'))[-1].replace(ext, ''), "url": file}
     73                 data.append(item)
     74 
     75     iitc_plugins = data
     76     if bot.memory.exists(["iitc_plugins"]):
     77         bot.memory.pop_by_path(["iitc_plugins"])
     78         bot.memory.set_by_path(["iitc_plugins"], iitc_plugins)
     79     else:
     80         bot.memory.set_by_path(["iitc_plugins"], iitc_plugins)
     81 
     82 def _get_lines(shell_command):
     83     p = yield from asyncio.create_subprocess_shell(shell_command)
     84     output = yield from asyncio.wait_for(p.wait(), 240.0)
     85     return p.returncode, output, True
     86 
     87 @asyncio.coroutine
     88 def _screencap(url, args_filepath, filepath, filename, bot, event, arguments):
     89     os.chmod(filepath, 0o666)
     90     loop = asyncio.get_event_loop()
     91     command = 'phantomjs hangupsbot/plugins/intel_screenbot/screencap.js "' + args_filepath + '"'
     92     task = _get_lines(command)
     93     task = asyncio.wait_for(task, 420.0)
     94     exitcode, output, status = yield from task
     95 
     96     # read the resulting file into a byte array
     97     # yield from asyncio.sleep(10)
     98     if (arguments['screenshotfunction'] != 'portalinfoText'):
     99         file_resource = yield from _open_file(filepath, 'rb')
    100         file_data = yield from loop.run_in_executor(None, file_resource.read)
    101         image_data = yield from loop.run_in_executor(None, io.BytesIO, file_data)
    102         if status:
    103             try:
    104                 image_id = yield from bot._client.upload_image(image_data, filename=filename)
    105                 yield from bot.coro_send_message(event.conv.id_, None, image_id=image_id)
    106                 os.unlink(filepath)
    107                 os.unlink(args_filepath)
    108             except Exception as e:
    109                 os.unlink(filepath)
    110                 os.unlink(args_filepath)
    111                 logger.exception("upload failed: {}".format(url))
    112                 logger.exception("exception: {}".format(e))
    113                 yield from bot.coro_send_message(event.conv_id, "<i>error uploading screenshot</i>")
    114     else:
    115         if status:
    116             response_file = yield from _open_file(arguments['portalinfoResponse'], 'r')
    117             response = yield from readline(response_file)
    118             yield from bot.coro_send_message(event.conv.id_, response.replace("'", ""))
    119             os.unlink(filepath)
    120             os.unlink(args_filepath)
    121             os.unlink(arguments['portalinfoResponse'])
    122 
    123 def setintel(bot, event, *args):
    124     """set url for current converation for the intel or iitc command.
    125     use /bot clearintel to clear the previous url before setting a new one."""
    126     
    127     url = bot.conversation_memory_get(event.conv_id, 'IntelURL')
    128     if url is None:
    129         bot.conversation_memory_set(event.conv_id, 'IntelURL', ''.join(args))
    130         html = "<i><b>{}</b> updated screenshot URL".format(event.user.full_name)
    131         yield from bot.coro_send_message(event.conv, html)
    132 
    133     else:
    134         bot.conversation_memory_set(event.conv_id, 'IntelURL', None)
    135         bot.conversation_memory_set(event.conv_id, 'IntelURL', ''.join(args))
    136         html = "<i><b>{}</b> updated screenshot URL".format(event.user.full_name)
    137         yield from bot.coro_send_message(event.conv, html)
    138 
    139 def portalpic(bot, event, *args):
    140     """get a screenshot of the portalInfo by URL."""
    141 
    142     arguments = {}
    143 
    144     if args:
    145         if len(args) > 1:
    146             url = ' '.join(str(i) for i in args)
    147         else:
    148             url = args[0]
    149         if '"' in url:
    150             url = url.replace('"', '')
    151     else:
    152         html = "<i><b>{}</b> No Arguments provided".format(event.user.full_name)
    153         yield from bot.coro_send_message(event.conv, html)
    154 
    155     if bot.config.exists(["intel_screenbot", "email"]):
    156         if bot.config.exists(["intel_screenbot", "password"]):
    157             email = bot.config.get_by_path(["intel_screenbot", "email"])
    158             password = bot.config.get_by_path(["intel_screenbot", "password"])
    159             arguments['email'] = email
    160             arguments['password'] = password
    161         else:
    162             html = "<i><b>{}</b> No Intel password has been added to config. Unable to authenticate".format(event.user.full_name)
    163             yield from bot.coro_send_message(event.conv, html)
    164     else:
    165         html = "<i><b>{}</b> No Intel Email/password has been added to config. Unable to authenticate".format(event.user.full_name)
    166         yield from bot.coro_send_message(event.conv, html)
    167 
    168     if url is None:
    169         html = "<i><b>{}</b> No Portal URL has been set for screenshots.".format(event.user.full_name)
    170         yield from bot.coro_send_message(event.conv, html)
    171     else:
    172         if re.match(r'(http(s)?:\/\/)', url):
    173             yield from bot.coro_send_message(event.conv_id, "<i>Portal Picture requested, please wait...</i>")
    174             filepath = tempfile.NamedTemporaryFile(suffix=".png", delete=False).name
    175             filename = filepath.split('/', filepath.count('/'))[-1]
    176             args_filepath = tempfile.NamedTemporaryFile(prefix="args_{}".format(event.conv_id), suffix=".json", delete=False).name
    177 
    178             arguments['url'] = url
    179             arguments['filepath'] = filepath
    180             arguments['screenshotfunction'] = "portalinfoScreen"
    181 
    182             with open(args_filepath, 'w') as out:
    183                 out.write(json.dumps(arguments))
    184 
    185             try:
    186                 loop = asyncio.get_event_loop()
    187                 image_data = yield from _screencap(url, args_filepath, filepath, filename, bot, event, arguments)
    188             except Exception as e:
    189                 yield from bot.coro_send_message(event.conv_id, "<i>error getting screenshot</i>")
    190                 logger.exception("screencap failed".format(url))
    191                 return
    192         else:
    193             html = "<i><b>{}</b> No URL found in Arguments".format(event.user.full_name)
    194             yield from bot.coro_send_message(event.conv, html)
    195 
    196 def portalinfo(bot, event, *args):
    197     """get a of the portalInfo by URL."""
    198 
    199     arguments = {}
    200 
    201     if args:
    202         if len(args) > 1:
    203             url = ' '.join(str(i) for i in args)
    204         else:
    205             url = args[0]
    206         if '"' in url:
    207             url = url.replace('"', '')
    208     else:
    209         html = "<i><b>{}</b> No Arguments provided".format(event.user.full_name)
    210         yield from bot.coro_send_message(event.conv, html)
    211 
    212     if bot.config.exists(["intel_screenbot", "email"]):
    213         if bot.config.exists(["intel_screenbot", "password"]):
    214             email = bot.config.get_by_path(["intel_screenbot", "email"])
    215             password = bot.config.get_by_path(["intel_screenbot", "password"])
    216             arguments['email'] = email
    217             arguments['password'] = password
    218         else:
    219             html = "<i><b>{}</b> No Intel password has been added to config. Unable to authenticate".format(event.user.full_name)
    220             yield from bot.coro_send_message(event.conv, html)
    221     else:
    222         html = "<i><b>{}</b> No Intel Email/password has been added to config. Unable to authenticate".format(event.user.full_name)
    223         yield from bot.coro_send_message(event.conv, html)
    224 
    225     if url is None:
    226         html = "<i><b>{}</b> No Portal URL has been set for portal Info.".format(event.user.full_name)
    227         yield from bot.coro_send_message(event.conv, html)
    228     else:
    229         if re.match(r'(http(s)?:\/\/)', url):
    230             yield from bot.coro_send_message(event.conv_id, "<i>Portal Info requested, please wait...</i>")
    231             filepath = tempfile.NamedTemporaryFile(suffix=".png", delete=False).name
    232             portalinfoResponse = tempfile.NamedTemporaryFile(prefix="response_{}".format(event.conv_id), delete=False).name
    233             filename = filepath.split('/', filepath.count('/'))[-1]
    234             args_filepath = tempfile.NamedTemporaryFile(prefix="args_{}".format(event.conv_id), suffix=".json", delete=False).name
    235 
    236             arguments['url'] = url
    237             arguments['filepath'] = filepath
    238             arguments['screenshotfunction'] = "portalinfoText"
    239             arguments['portalinfoResponse'] = portalinfoResponse
    240 
    241             with open(args_filepath, 'w') as out:
    242                 out.write(json.dumps(arguments))
    243 
    244             try:
    245                 loop = asyncio.get_event_loop()
    246                 image_data = yield from _screencap(url, args_filepath, filepath, filename, bot, event, arguments)
    247             except Exception as e:
    248                 yield from bot.coro_send_message(event.conv_id, "<i>error getting infos</i>")
    249                 logger.exception("getting infos failed".format(url))
    250                 return
    251         else:
    252             html = "<i><b>{}</b> No URL found in Arguments".format(event.user.full_name)
    253             yield from bot.coro_send_message(event.conv, html)
    254             
    255 def intel(bot, event, *args):
    256     """get a screenshot of a search term or intel URL or the default intel URL of the hangout."""
    257 
    258     arguments = {}
    259 
    260     if args:
    261         if len(args) > 1:
    262             url = ' '.join(str(i) for i in args)
    263         else:
    264             url = args[0]
    265         if '"' in url:
    266             url = url.replace('"', '')
    267     else:
    268         try:
    269             url = bot.conversation_memory_get(event.conv_id, 'IntelURL')
    270         except:
    271             url = 'https://www.ingress.com/intel'
    272 
    273     if bot.config.exists(["intel_screenbot", "email"]):
    274         if bot.config.exists(["intel_screenbot", "password"]):
    275             email = bot.config.get_by_path(["intel_screenbot", "email"])
    276             password = bot.config.get_by_path(["intel_screenbot", "password"])
    277             arguments['email'] = email
    278             arguments['password'] = password
    279         else:
    280             html = "<i><b>{}</b> No Intel password has been added to config. Unable to authenticate".format(event.user.full_name)
    281             yield from bot.coro_send_message(event.conv, html)
    282     else:
    283         html = "<i><b>{}</b> No Intel Email/password has been added to config. Unable to authenticate".format(event.user.full_name)
    284         yield from bot.coro_send_message(event.conv, html)
    285 
    286     if url is None:
    287         html = "<i><b>{}</b> No Intel URL or search term has been set for screenshots.".format(event.user.full_name)
    288         yield from bot.coro_send_message(event.conv, html)
    289 
    290     else:
    291         if re.match(r'(http(s)?:\/\/)', url):
    292             search = 'nix'
    293             zoomParameter = re.search(r"(?:&z=)", url, re.IGNORECASE)
    294             if zoomParameter:
    295                 ZoomSearch = re.finditer(r"(?:&z=).*", url, flags=re.I)
    296                 for matchNum, zoomlevel_raw in enumerate(ZoomSearch):
    297                     matchNum = matchNum + 1
    298                 zoomlevel_clean = zoomlevel_raw.group()
    299                 zoomlevel = zoomlevel_clean[3:][:2]
    300                 if zoomlevel.isdigit():
    301                     yield from bot.coro_send_message(event.conv_id, "<i>intel map at zoom level "+ zoomlevel + " requested, please wait...</i>")
    302             else:
    303                 yield from bot.coro_send_message(event.conv_id, "<i>intel map requested, please wait...</i>")
    304         else:
    305             search = url
    306             zoomParameter = re.search(r"(?<=z=)", url, re.IGNORECASE)
    307             if zoomParameter:
    308                 ZoomSearch = re.finditer(r"(?<=z=)[^\s]+", url, flags=re.I)
    309                 for matchNum, zoomlevel_raw in enumerate(ZoomSearch):
    310                     matchNum = matchNum + 1
    311                 zoomlevel = zoomlevel_raw.group()
    312                 search = search.replace("z={}".format(zoomlevel),"")
    313                 if zoomlevel.isdigit():
    314                     yield from bot.coro_send_message(event.conv_id, "<i>searching " + search + " on intel map and screenshooting at zoom level "+ zoomlevel + " as requested, please wait...</i>")
    315                     arguments['zoomlevel'] = str(zoomlevel)
    316             else:
    317                 yield from bot.coro_send_message(event.conv_id, "<i>searching " + search + " on intel map and screenshooting as requested, please wait...</i>")
    318             url = 'https://www.ingress.com/intel'
    319 
    320         filepath = tempfile.NamedTemporaryFile(suffix=".png", delete=False).name
    321         filename = filepath.split('/', filepath.count('/'))[-1]
    322         args_filepath = tempfile.NamedTemporaryFile(prefix="args_{}".format(event.conv_id), suffix=".json", delete=False).name
    323 
    324         arguments['search'] = search
    325         arguments['url'] = url
    326         arguments['filepath'] = filepath
    327         arguments['maptype'] = "intel"
    328         arguments['screenshotfunction'] = "map"
    329 
    330         with open(args_filepath, 'w') as out:
    331             out.write(json.dumps(arguments))
    332             
    333         try:
    334             loop = asyncio.get_event_loop()
    335             image_data = yield from _screencap(url, args_filepath, filepath, filename, bot, event, arguments)
    336         except Exception as e:
    337             yield from bot.coro_send_message(event.conv_id, "<i>error getting screenshot</i>")
    338             logger.exception("screencap failed".format(url))
    339             return
    340 
    341 
    342 def iitc(bot, event, *args):
    343     """get a screenshot of a search term or intel URL or the default intel URL of the hangout."""
    344 
    345     arguments = {}
    346 
    347     if args:
    348         if len(args) > 1:
    349             url = ' '.join(str(i) for i in args)
    350         else:
    351             url = args[0]
    352         if '"' in url:
    353             url = url.replace('"', '')
    354     else:
    355         url = bot.conversation_memory_get(event.conv_id, 'IntelURL')
    356 
    357     if bot.config.exists(["intel_screenbot", "email"]):
    358         if bot.config.exists(["intel_screenbot", "password"]):
    359             email = bot.config.get_by_path(["intel_screenbot", "email"])
    360             password = bot.config.get_by_path(["intel_screenbot", "password"])
    361             arguments['email'] = email
    362             arguments['password'] = password
    363         else:
    364             html = "<i><b>{}</b> No Intel password has been added to config. Unable to authenticate".format(event.user.full_name)
    365             yield from bot.coro_send_message(event.conv, html)
    366     else:
    367         html = "<i><b>{}</b> No Intel Email/password has been added to config. Unable to authenticate".format(event.user.full_name)
    368         yield from bot.coro_send_message(event.conv, html)
    369 
    370     if url is None:
    371         html = "<i><b>{}</b> No Intel URL has been set for screenshots.".format(event.user.full_name)
    372         yield from bot.coro_send_message(event.conv, html)
    373 
    374     else:
    375         if re.match(r'(http(s)?:\/\/)', url):
    376             search = 'nix'
    377             zoomParameter = re.search(r"(?:&z=)", url, re.IGNORECASE)
    378             if zoomParameter:
    379                 ZoomSearch = re.finditer(r"(?:&z=).*", url, flags=re.I)
    380                 for matchNum, zoomlevel_raw in enumerate(ZoomSearch):
    381                     matchNum = matchNum + 1
    382                 zoomlevel_clean = zoomlevel_raw.group()
    383                 zoomlevel = zoomlevel_clean[3:][:2]
    384                 if zoomlevel.isdigit():
    385                     yield from bot.coro_send_message(event.conv_id, "<i>iitc map at zoom level "+ zoomlevel + " requested, please wait...</i>")
    386             else:
    387                 yield from bot.coro_send_message(event.conv_id, "<i>iitc map requested, please wait...</i>")
    388         else:
    389             search = url
    390             zoomParameter = re.search(r"(?<=z=)", url, re.IGNORECASE)
    391             if zoomParameter:
    392                 ZoomSearch = re.finditer(r"(?<=z=)[^\s]+", url, flags=re.I)
    393                 for matchNum, zoomlevel_raw in enumerate(ZoomSearch):
    394                     matchNum = matchNum + 1
    395                 zoomlevel = zoomlevel_raw.group()
    396                 search = search.replace("z={}".format(zoomlevel),"")
    397                 if zoomlevel.isdigit():
    398                     yield from bot.coro_send_message(event.conv_id, "<i>searching " + search + " on iitc map and screenshooting it at zoom level "+ zoomlevel + " as requested, please wait...</i>")
    399                     arguments['zoomlevel'] = str(zoomlevel)
    400             else:
    401                 yield from bot.coro_send_message(event.conv_id, "<i>searching " + search + " on iitc map and and screenshooting it as requested, please wait...</i>")
    402             url = 'https://www.ingress.com/intel'
    403 
    404         filepath = tempfile.NamedTemporaryFile(suffix=".png", delete=False).name
    405         filename = filepath.split('/', filepath.count('/'))[-1]
    406         args_filepath = tempfile.NamedTemporaryFile(prefix="args_{}".format(event.conv_id), suffix=".json", delete=False).name
    407         logger.debug("temporary screenshot file: {}".format(filepath))
    408         logger.debug("temporary args file: {}".format(args_filepath))
    409         if bot.conversation_memory_get(event.conv_id, 'iitc_plugins'):
    410             plugins = []
    411             plugin_names = bot.conversation_memory_get(event.conv_id, 'iitc_plugins').split(", ")
    412             if bot.memory.exists(["iitc_plugins"]):
    413                 for plugin_objects in bot.memory.get_by_path(["iitc_plugins"]):
    414                     for plugin_name in plugin_names:
    415                         if plugin_objects["name"]  == plugin_name:
    416                             plugins.append(plugin_objects["url"])
    417         else:
    418              plugins = ''
    419 
    420         arguments['plugins'] = plugins
    421         arguments['search'] = search
    422         arguments['url'] = url
    423         arguments['filepath'] = filepath
    424         arguments['maptype'] = "iitc"
    425         arguments['screenshotfunction'] = "map"
    426 
    427         with open(args_filepath, 'w') as out:
    428             out.write(json.dumps(arguments))
    429 
    430         try:
    431             loop = asyncio.get_event_loop()
    432             image_data = yield from _screencap(url, args_filepath, filepath, filename, bot, event, arguments)
    433         except Exception as e:
    434             yield from bot.coro_send_message(event.conv_id, "<i>error getting screenshot</i>")
    435             logger.exception("screencap failed".format(url))
    436             return
    437 
    438 def show_iitcplugins(bot, event, *args):
    439     """Shows all available iitc Plugins."""
    440     if bot.memory.exists(["iitc_plugins"]):
    441         plugin_names = []
    442         for plugin_objects in bot.memory.get_by_path(["iitc_plugins"]):
    443             for attribute, value in plugin_objects.items():
    444                 if attribute == "name":
    445                     plugin_names.append(value)
    446         yield from bot.coro_send_to_user_and_conversation(event.user.id_.chat_id, event.conv_id, "<i><b>IITC Plugins:</b><br> {}</i>".format(', <br>'.join(str(i) for i in plugin_names)), _("<i><b>{}</b>, I've sent you the plugins ;)</i>").format(event.user.full_name))
    447 
    448 def set_iitcplugins(bot, event, *args):
    449     """sets the activated iitc plugins for a hangout."""
    450     if not bot.conversation_memory_get(event.conv_id, 'iitc_plugins') is None:
    451         bot.conversation_memory_set(event.conv_id, 'iitc_plugins', None)
    452         bot.conversation_memory_set(event.conv_id, 'iitc_plugins', ', '.join(args))
    453         html = "<i><b>{}</b> updated plugins".format(event.user.full_name)
    454         yield from bot.coro_send_message(event.conv, html)
    455     else:
    456         bot.conversation_memory_set(event.conv_id, 'iitc_plugins', ', '.join(args))
    457         html = "<i><b>{}</b> updated plugins".format(event.user.full_name)
    458         yield from bot.coro_send_message(event.conv, html)
    459 
    460 def active_iitcplugins(bot, event, *args):
    461     """shows the activated iitc plugins for a hangout."""
    462     if bot.conversation_memory_get(event.conv_id, 'iitc_plugins') is None:
    463         html = "<i><b>{}</b> no active IITC Plugins for this conversation".format(event.user.full_name)
    464         yield from bot.coro_send_message(event.conv, html)
    465     else:
    466         plugins = bot.conversation_memory_get(event.conv_id, 'iitc_plugins')
    467         html = "<i><b>{}</b> plugins for this conversation: {}<br />".format(event.user.full_name, plugins)
    468         yield from bot.coro_send_message(event.conv, html)