From cb52be3055b9ef05b5d1ead9431fdd69834ee3ba Mon Sep 17 00:00:00 2001 From: Jinwei Zhao Date: Mon, 2 Jan 2017 22:05:44 +0800 Subject: init --- .gitignore | 6 ++ LICENSE | 21 ++++ bot.py | 289 ++++++++++++++++++++++++++++++++++++++++++++++++++++ config.json.example | 6 ++ ingrex/__init__.py | 4 + ingrex/intel.py | 144 ++++++++++++++++++++++++++ ingrex/praser.py | 25 +++++ ingrex/utils.py | 80 +++++++++++++++ 8 files changed, 575 insertions(+) create mode 100755 .gitignore create mode 100755 LICENSE create mode 100755 bot.py create mode 100755 config.json.example create mode 100755 ingrex/__init__.py create mode 100755 ingrex/intel.py create mode 100755 ingrex/praser.py create mode 100755 ingrex/utils.py diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..5b0c117 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +__pycache__ +*.pyc +*.png +*.log +cookie +config.json diff --git a/LICENSE b/LICENSE new file mode 100755 index 0000000..03ff271 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2015 Daniel + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/bot.py b/bot.py new file mode 100755 index 0000000..fd3c81a --- /dev/null +++ b/bot.py @@ -0,0 +1,289 @@ +# -*- coding: utf-8 -*- + +import os +import re +import time +import json +import logging +import platform +import urllib +from selenium import webdriver + +import telegram +from telegram.error import NetworkError, Unauthorized +from pymongo import MongoClient + +import ingrex + +Debug = True +bot = None +BOT_TOKEN = '' +CHANNEL_NAME = '' +Email = '' +Passwd = '' +PhantomjsPath = '' + +LOG_FILENAME = 'voh.log' +logging.basicConfig(level = logging.DEBUG, + filename = LOG_FILENAME, + filemode='w') +console = logging.StreamHandler() +console.setLevel(logging.INFO) +# set a format which is simpler for console use +formatter = logging.Formatter('%(name)-12s: %(levelname)-8s %(message)s') +# tell the handler to use this format +console.setFormatter(formatter) +# add the handler to the root logger +logging.getLogger('').addHandler(console) + +class CookieException(Exception): + ''' CookieError ''' + +def getTime(): + return time.strftime('%x %X %Z') + +def readConfig(): + global Email + global Passwd + global BOT_TOKEN + global CHANNEL_NAME + global PhantomjsPath + + configfile = open("./config.json") + config = json.load(configfile) + Email = config["Email"] + Passwd = config["Passwd"] + BOT_TOKEN = config["BOT_TOKEN"] + CHANNEL_NAME = config["CHANNEL_NAME"] + + osname = platform.system() + if osname == "Windows": + PhantomjsPath = 'C:\\Users\\LuciaHydrion\\AppData\\Roaming\\npm\\phantomjs.cmd' + elif osname == "Linux": + PhantomjsPath = '/home/clarkzjw/code/phantomjs-2.1.1-linux-i686/bin/phantomjs' + os.environ['TZ'] = 'Asia/Shanghai' + time.tzset() + +def fetchCookie(): + global Debug + global Email + global Passwd + global PhantomjsPath + + logger = logging.getLogger('fetchCookie') + logger.info(getTime() + ': Fetching Cookie...') + + driver = webdriver.PhantomJS(PhantomjsPath) + driver.get('https://www.ingress.com/intel') + + # get login page + link = driver.find_elements_by_tag_name('a')[0].get_attribute('href') + driver.get(link) + if Debug: + driver.get_screenshot_as_file('1.png') + # simulate manual login + driver.set_page_load_timeout(10) + driver.set_script_timeout(20) + driver.find_element_by_id('Email').send_keys(Email) + if Debug: + driver.get_screenshot_as_file('2.png') + driver.find_element_by_css_selector('#next').click() + time.sleep(3) + driver.find_element_by_id('Passwd').send_keys(Passwd) + if Debug: + driver.get_screenshot_as_file('3.png') + driver.find_element_by_css_selector('#signIn').click() + time.sleep(3) + if Debug: + driver.get_screenshot_as_file('3.png') + # get cookies + temp = driver.get_cookies() + + csrftoken = '' + SACSID = '' + for a in temp: + if (a['name'] == 'csrftoken'): + csrftoken = a['value'] + if (a['name'] == 'SACSID'): + SACSID = a['value'] + + if csrftoken == '' or SACSID == '': + logger.error(getTime() + ': Fetch Cookie Failed') + raise CookieException + + with open('cookie', 'w') as file: + cookie = 'SACSID='+SACSID+'; csrftoken='+csrftoken+'; ingress.intelmap.shflt=viz; ingress.intelmap.lat=29.098418372855484; ingress.intelmap.lng=119.81689453125; ingress.intelmap.zoom=17' + file.write(cookie) + + driver.quit() + logger.info(getTime() + ': Fetching Cookie Succeed') + return True + +def sendMessge(bot, msg): + "sendMsg" + + logger = logging.getLogger('sendMessage') + while True: + try: + url = 'https://api.telegram.org/bot' + url += BOT_TOKEN + url += '/sendMessage?chat_id=' + url += CHANNEL_NAME + url += '&text=' + url += urllib.parse.quote(msg) + + req = urllib.request.Request(url, headers={'Content-Type': 'application/x-www-form-urlencoded'}) + resp = urllib.request.urlopen(req) + data = resp.read() + + #bot.sendMessage(chat_id=CHANNEL_NAME, text=msg) + logger.info(getTime() + ": sendMsg " + msg) + break + except NetworkError: + time.sleep(1) + +def sendMonitor(bot, msg): + logger = logging.getLogger('sendMonitor') + while True: + try: + bot.sendMessage(chat_id="@voamonitor", text=msg) + logger.info(getTime() + ": sendMsg " + msg) + break + except NetworkError: + time.sleep(1) + +def formatMessage(raw): + pattern = re.compile('xmps.biz|enl.sh|ingressfarm.com|Polygon') + match = pattern.search(str(raw)) + if match: + return "Blocked" + + msg = '' + plext = raw[2]['plext'] + markup = plext['markup'] + plaintext = plext['text'] + + for mark in markup: + if mark[0] == 'SECURE': + msg += mark[1]['plain'] + elif mark[0] == 'SENDER': + player = mark[1]['plain'] + team = mark[1]['team'] + + pattern = re.compile(':') + match = pattern.search(player) + if match: + if team == 'RESISTANCE': + player = player[:match.span()[0]] + ' 🐳' + player[match.span()[0]:] + elif team == 'ENLIGHTENED': + player = player[:match.span()[0]] + ' 🐸' + player[match.span()[0]:] + msg += player + + elif mark[0] == 'PLAYER' or mark[0] == 'AT_PLAYER': + player = mark[1]['plain'] + team = mark[1]['team'] + + msg += player + if team == 'RESISTANCE': + msg += ' 🐳' + elif team == 'ENLIGHTENED': + msg += ' 🐸' + + elif mark[0] == 'TEXT': + msg += mark[1]['plain'] + + pattern = re.compile('\[secure\]') + match = pattern.search(msg) + if match: + if msg.find(':') != -1: + msg = msg[:9] + '@' + msg[9:] + else: + msg = msg[:10] + '@' + msg[10:] + else: + msg = '@' + msg + + return msg + +def insertDB(time, msg): + logger = logging.getLogger('insertDB') + Conn = MongoClient() + database = Conn['COMM_Hangzhou'] + mycollection = database.entries + post = {"time": time, "msg": msg} + mycollection.insert(post) + + +def main(): + logger = logging.getLogger('main') + + field = { + 'minLngE6':119618783, + 'minLatE6':29912919, + 'maxLngE6':121018722, + 'maxLatE6':30573739, + } + + mints = -1 + maxts=-1 + reverse=False + tab='all' + + while True: + try: + if fetchCookie(): + break + except CookieException: + time.sleep(3) + + count = 0 + while True: + count += 1 + + with open('cookie') as cookies: + cookies = cookies.read().strip() + + logger.info(getTime() + ": {} Fetching from Intel...".format(str(count))) + + while True: + try: + + intel = ingrex.Intel(cookies, field) + result = intel.fetch_msg(mints, maxts, reverse, tab) + + if result: + mints = result[0][1] + 1 + break + except CookieError: + while True: + try: + if fetchCookie(): + break + except CookieException: + time.sleep(3) + + for item in result[::-1]: + message = ingrex.Message(item) + if message.ptype == 'PLAYER_GENERATED': + logger.info(getTime() + str(item)) + + msg = formatMessage(item) + if msg == 'Blocked': + logger.info(getTime() + " " + message.text) + else: + msg = message.time + " " + msg + logger.info(getTime() + " " + msg) + insertDB(message.time, msg) + sendMonitor(bot, msg) + #sendMessge(bot, msg) + + time.sleep(10) + +if __name__ == '__main__': + readConfig() + bot = telegram.Bot(BOT_TOKEN) + + while True: + try: + main() + except Exception: + sendMonitor(bot, 'Main Error') diff --git a/config.json.example b/config.json.example new file mode 100755 index 0000000..c048ee9 --- /dev/null +++ b/config.json.example @@ -0,0 +1,6 @@ +{ + "Email": "Your Ingress Account Email", + "Passwd": "Your Ingress Account Password", + "BOT_TOKEN": "Your Telegram Bot Token", + "CHANNEL_NAME": "Your Channel ID" +} diff --git a/ingrex/__init__.py b/ingrex/__init__.py new file mode 100755 index 0000000..91876bf --- /dev/null +++ b/ingrex/__init__.py @@ -0,0 +1,4 @@ +"Init" +from . intel import Intel +from . praser import Message +from . import utils as Utils diff --git a/ingrex/intel.py b/ingrex/intel.py new file mode 100755 index 0000000..f13b2a2 --- /dev/null +++ b/ingrex/intel.py @@ -0,0 +1,144 @@ +"Ingrex is a python lib for ingress" +import requests +import re +import json +import os + +class Intel(object): + "main class with all Intel functions" + + def __init__(self, cookies, field): + self.DEBUG = True + token = re.findall(r'csrftoken=(\w*);', cookies)[0] + self.headers = { + 'accept-encoding': 'gzip, deflate', + 'content-type': 'application/json; charset=UTF-8', + 'cookie': cookies, + 'origin': 'https://www.ingress.com', + 'referer': 'https://www.ingress.com/intel', + 'user-agent': 'Mozilla/5.0 (MSIE 9.0; Windows NT 6.1; Trident/5.0)', + 'x-csrftoken': token, + } + self.field = { + 'maxLatE6': field['maxLatE6'], + 'minLatE6': field['minLatE6'], + 'maxLngE6': field['maxLngE6'], + 'minLngE6': field['minLngE6'], + } + self.point = { + 'latE6': (field['maxLatE6'] + field['minLatE6']) >> 1, + 'lngE6': (field['maxLngE6'] + field['minLngE6']) >> 1, + } + self.session = requests.session() + self.refresh_version() + + def refresh_version(self): + "refresh api version for request" + request = self.session.get('https://www.ingress.com/intel', headers=self.headers) + self.version = re.findall(r'gen_dashboard_(\w*)\.js', request.text)[0] + + def fetch(self, url, payload): + "raw request with auto-retry and connection check function" + payload['v'] = self.version + count = 0 + while count < 3: + try: + request = self.session.post(url, data=json.dumps(payload), headers=self.headers) + return request.json()['result'] + except requests.ConnectionError: + raise IntelError + except Exception: + count += 1 + continue + raise CookieError + + def fetch_msg(self, mints=-1, maxts=-1, reverse=False, tab='all'): + "fetch message from Ingress COMM, tab can be 'all', 'faction', 'alerts'" + url = 'https://www.ingress.com/r/getPlexts' + payload = { + 'maxLatE6': self.field['maxLatE6'], + 'minLatE6': self.field['minLatE6'], + 'maxLngE6': self.field['maxLngE6'], + 'minLngE6': self.field['minLngE6'], + 'maxTimestampMs': maxts, + 'minTimestampMs': mints, + 'tab': tab + } + if reverse: + payload['ascendingTimestampOrder'] = True + return self.fetch(url, payload) + + def fetch_map(self, tilekeys): + "fetch game entities from Ingress map" + url = 'https://www.ingress.com/r/getEntities' + payload = { + 'tileKeys': tilekeys + } + return self.fetch(url, payload) + + def fetch_portal(self, guid): + "fetch portal details from Ingress" + url = 'https://www.ingress.com/r/getPortalDetails' + payload = { + 'guid': guid + } + return self.fetch(url, payload) + + def fetch_score(self): + "fetch the global score of RESISTANCE and ENLIGHTENED" + url = 'https://www.ingress.com/r/getGameScore' + payload = {} + return self.fetch(url, payload) + + def fetch_region(self): + "fetch the region info of RESISTANCE and ENLIGHTENED" + url = 'https://www.ingress.com/r/getRegionScoreDetails' + payload = { + 'lngE6': self.point['lngE6'], + 'latE6': self.point['latE6'], + } + return self.fetch(url, payload) + + def fetch_artifacts(self): + "fetch the artifacts details" + url = 'https://www.ingress.com/r/getArtifactPortals' + payload = {} + return self.fetch(url, payload) + + def send_msg(self, msg, tab='all'): + "send a message to Ingress COMM, tab can be 'all', 'faction'" + url = 'https://www.ingress.com/r/sendPlext' + payload = { + 'message': msg, + 'latE6': self.point['latE6'], + 'lngE6': self.point['lngE6'], + 'tab': tab + } + return self.fetch(url, payload) + + def send_invite(self, address): + "send a recruit to an email address" + url = 'https://www.ingress.com/r/sendInviteEmail' + payload = { + 'inviteeEmailAddress': address + } + return self.fetch(url, payload) + + def redeem_code(self, passcode): + "redeem a passcode" + url = 'https://www.ingress.com/r/redeemReward' + payload = { + 'passcode': passcode + } + return self.fetch(url, payload) + +class IntelError(BaseException): + """Intel Error""" + pass + +class CookieError(IntelError): + """Intel Error""" + pass + +if __name__ == '__main__': + pass diff --git a/ingrex/praser.py b/ingrex/praser.py new file mode 100755 index 0000000..732ab1b --- /dev/null +++ b/ingrex/praser.py @@ -0,0 +1,25 @@ +"Ingrex praser deal with message" +from datetime import datetime, timedelta +import platform +import os +import time + +osname = platform.system() +if osname == "Linux": + os.environ['TZ'] = 'Asia/Shanghai' + time.tzset() + +class Message(object): + "Message object" + def __init__(self, raw_msg): + self.raw = raw_msg + self.guid = raw_msg[0] + self.timestamp = raw_msg[1] + seconds, millis = divmod(raw_msg[1], 1000) + time = datetime.fromtimestamp(seconds) + timedelta(milliseconds=millis) + self.time = time.strftime('%Y/%m/%d %H:%M:%S:%f')[:-3] + self.text = raw_msg[2]['plext']['text'] + self.ptype = raw_msg[2]['plext']['plextType'] + self.team = raw_msg[2]['plext']['team'] + self.type = raw_msg[2]['plext']['markup'][1][1]['plain'] + diff --git a/ingrex/utils.py b/ingrex/utils.py new file mode 100755 index 0000000..8090f15 --- /dev/null +++ b/ingrex/utils.py @@ -0,0 +1,80 @@ +"Map Utils" +from math import pi, sin, cos, tan, asin, radians, sqrt, log + +def calc_tile(lng, lat, zoomlevel): + tilecounts = [1,1,1,40,40,80,80,320,1E3,2E3,2E3,4E3,8E3,16E3,16E3,32E3] + rlat = radians(lat) + tilecount = tilecounts[zoomlevel] + xtile = int((lng + 180.0) / 360.0 * tilecount) + ytile = int((1.0 - log(tan(rlat) + (1 / cos(rlat))) / pi) / 2.0 * tilecount) + return xtile, ytile + +def calc_dist(lat1, lng1, lat2, lng2): + lat1, lng1, lat2, lng2 = map(radians, [lat1, lng1, lat2, lng2]) + dlat = lat1 - lat2 + dlng = lng1 - lng2 + a = sin(dlat/2)**2 + cos(lat1) * cos(lat2) * sin(dlng/2)**2 + c = 2* asin(sqrt(a)) + m = 6367.0 * c * 1000 + return m + +def point_in_poly(x, y, poly): + n = len(poly) + inside = False + p1x,p1y = poly[0] + for i in range(n+1): + p2x,p2y = poly[i % n] + if y > min(p1y, p2y): + if y <= max(p1y, p2y): + if x <= max(p1x, p2x): + if p1y != p2y: + xints = (y - p1y) * (p2x - p1x) / (p2y - p1y) + p1x + if p1x == p2x or x <= xints: + inside = not inside + p1x,p1y = p2x,p2y + return inside + +def transform(wgLat, wgLon): + """ + transform(latitude,longitude) , WGS84 + return (latitude,longitude) , GCJ02 + """ + a = 6378245.0 + ee = 0.00669342162296594323 + if (outOfChina(wgLat, wgLon)): + mgLat = wgLat + mgLon = wgLon + return mgLat,mgLon + dLat = transformLat(wgLon - 105.0, wgLat - 35.0) + dLon = transformLon(wgLon - 105.0, wgLat - 35.0) + radLat = wgLat / 180.0 * pi + magic = sin(radLat) + magic = 1 - ee * magic * magic + sqrtMagic = sqrt(magic) + dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * pi) + dLon = (dLon * 180.0) / (a / sqrtMagic * cos(radLat) * pi) + mgLat = wgLat + dLat + mgLon = wgLon + dLon + return mgLat,mgLon + +def outOfChina(lat, lon): + if (lon < 72.004 or lon > 137.8347): + return True + if (lat < 0.8293 or lat > 55.8271): + return True + return False + +def transformLat(x, y): + ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * sqrt(abs(x)) + ret += (20.0 * sin(6.0 * x * pi) + 20.0 * sin(2.0 * x * pi)) * 2.0 / 3.0 + ret += (20.0 * sin(y * pi) + 40.0 * sin(y / 3.0 * pi)) * 2.0 / 3.0 + ret += (160.0 * sin(y / 12.0 * pi) + 320 * sin(y * pi / 30.0)) * 2.0 / 3.0 + return ret + +def transformLon(x, y): + ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * sqrt(abs(x)) + ret += (20.0 * sin(6.0 * x * pi) + 20.0 * sin(2.0 * x * pi)) * 2.0 / 3.0 + ret += (20.0 * sin(x * pi) + 40.0 * sin(x / 3.0 * pi)) * 2.0 / 3.0 + ret += (150.0 * sin(x / 12.0 * pi) + 300.0 * sin(x / 30.0 * pi)) * 2.0 / 3.0 + return ret + -- cgit v1.2.3