├── .gitignore ├── LICENCE ├── README.rst ├── ircclive.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | 3 | 4 | # Created by https://www.gitignore.io/api/python 5 | 6 | ### Python ### 7 | # Byte-compiled / optimized / DLL files 8 | __pycache__/ 9 | *.py[cod] 10 | *$py.class 11 | 12 | # C extensions 13 | *.so 14 | 15 | # Distribution / packaging 16 | .Python 17 | build/ 18 | develop-eggs/ 19 | dist/ 20 | downloads/ 21 | eggs/ 22 | .eggs/ 23 | lib/ 24 | lib64/ 25 | parts/ 26 | sdist/ 27 | var/ 28 | wheels/ 29 | *.egg-info/ 30 | .installed.cfg 31 | *.egg 32 | 33 | # PyInstaller 34 | # Usually these files are written by a python script from a template 35 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 36 | *.manifest 37 | *.spec 38 | 39 | # Installer logs 40 | pip-log.txt 41 | pip-delete-this-directory.txt 42 | 43 | # Unit test / coverage reports 44 | htmlcov/ 45 | .tox/ 46 | .coverage 47 | .coverage.* 48 | .cache 49 | .pytest_cache/ 50 | nosetests.xml 51 | coverage.xml 52 | *.cover 53 | .hypothesis/ 54 | 55 | # Translations 56 | *.mo 57 | *.pot 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule.* 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # myp 104 | .mypy_cache/ 105 | 106 | 107 | # End of https://www.gitignore.io/api/python 108 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-2018 Minacle 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | IRCCLive 2 | ======== 3 | 4 | Simple IRCCloud session keeper; written in python3. 5 | 6 | 7 | Installation 8 | ------------ 9 | 10 | - ``pip install ircclive`` 11 | 12 | 13 | Usage 14 | ----- 15 | 16 | - ``ircclive`` 17 | - ``ircclive "email@example.com"`` 18 | - ``ircclive "email@example.com" "p4ssw0rd"`` 19 | -------------------------------------------------------------------------------- /ircclive.py: -------------------------------------------------------------------------------- 1 | import getpass 2 | import gzip 3 | import io 4 | import json 5 | import sys 6 | import time 7 | import traceback 8 | import urllib.error 9 | import urllib.parse 10 | import urllib.request 11 | 12 | __version__ = "1.0.1" 13 | 14 | baseurl = "https://www.irccloud.com/chat/" 15 | email, password, stat_user = None, None, None 16 | 17 | def rpc(method, path, session=None, token=None, keepalive=False, data=None): 18 | try: # python 3.4 or later 19 | r = urllib.request.Request(urllib.parse.urljoin(baseurl, path), method=method) 20 | r.data = data 21 | except TypeError: 22 | r = urllib.request.Request(urllib.parse.urljoin(baseurl, path)) 23 | r.add_data(data) 24 | r.add_header("User-Agent", "IRCCLive") 25 | if method == "POST": 26 | r.add_header("Content-Type","application/x-www-form-urlencoded") 27 | if session: 28 | r.add_header("Cookie", "session=" + session) 29 | if keepalive: 30 | r.add_header("Connection", "keep-alive") 31 | if token: 32 | r.add_header("x-auth-formtoken", token) 33 | return urllib.request.urlopen(r) 34 | 35 | def rpc_get(session, path, keepalive=False): 36 | return rpc("GET", path, session, keepalive=keepalive) 37 | 38 | def rpc_post(session, path, keepalive=False, data=None): 39 | return rpc("POST", path, session, keepalive=keepalive, data=data) 40 | 41 | def getresponse(response): 42 | b = io.BytesIO() 43 | b.write(response.read()) 44 | b.seek(0, 0) 45 | if response.info().get("Content-Encoding") == "gzip": 46 | f = gzip.GzipFile(fileobj=b) 47 | return f.read().decode("utf-8") 48 | else: 49 | return b.read().decode("utf-8") 50 | 51 | def auth_formtoken(): 52 | f = rpc("POST", "auth-formtoken", data=b"") 53 | d = json.loads(getresponse(f)) 54 | if d.get("success", False): 55 | return d["token"] 56 | return None 57 | 58 | def login(email, password, token): 59 | f = rpc("POST", "login", token=token, data=urllib.parse.urlencode({"email": email, "password": password, "token": token}).encode("ascii")) 60 | d = json.loads(getresponse(f)) 61 | if d.get("success", False): 62 | return d["session"] 63 | return None 64 | 65 | def stream(session): 66 | f = rpc_get(session, "stream", True) 67 | global stat_user 68 | interval = 0 69 | while True: 70 | d = f.readline() 71 | if not d: 72 | _print("disconnected.") 73 | break 74 | else: 75 | d = json.loads(d.decode("utf-8")) 76 | if d["type"] == "oob_include": 77 | if oob_include(session, d["url"]): 78 | _print("connected successfully.") 79 | else: 80 | _print("connection failed.") 81 | break 82 | elif d["type"] == "stat_user": 83 | stat_user = d 84 | try: 85 | f.close() 86 | except: 87 | pass 88 | 89 | def oob_include(session, url): 90 | f = rpc_get(session, url) 91 | d = json.loads(getresponse(f)) 92 | for i in d: 93 | if i["type"] == "makeserver": 94 | if i["disconnected"]: 95 | reconnect(session, i["cid"]) 96 | return True if d else False 97 | 98 | def reconnect(session, cid): 99 | f = rpc_post(session, "reconnect", data=urllib.parse.urlencode({"session": session, "cid": cid}).encode("ascii")) 100 | d = json.loads(getresponse(f)) 101 | 102 | def _print(*objects, reporter=None, sep=" ", begin="", end="\n", file=sys.stdout, flush=False): 103 | try: # python 3.3 or later 104 | print("%s[%s]" % (begin, (reporter or email or "(unknown)")), *objects, sep=sep, end=end, file=file, flush=flush) 105 | except TypeError: 106 | print("%s[%s]" % (begin, (reporter or email or "(unknown)")), *objects, sep=sep, end=end, file=file) 107 | if flush: 108 | file.flush() 109 | 110 | def _identify(clear=False): 111 | global email 112 | global password 113 | oe = email 114 | try: 115 | if clear: 116 | email = None 117 | password = None 118 | while not email: 119 | email = input("email: ") 120 | while not password: 121 | password = getpass.getpass("password: ") 122 | except KeyboardInterrupt: 123 | _print("terminating...", reporter=oe, begin="\n") 124 | sys.exit(0) 125 | 126 | def _run(): 127 | global stat_user 128 | while True: 129 | err = 0 130 | stat_user = None 131 | try: 132 | _print("connecting...") 133 | stream(login(email, password, auth_formtoken())) 134 | except KeyboardInterrupt: 135 | _print("terminating...") 136 | sys.exit(0) 137 | except urllib.error.HTTPError as e: 138 | err = e.code 139 | except: 140 | _print(begin="\n") 141 | traceback.print_exc() 142 | print() 143 | try: 144 | if stat_user: 145 | zombie = stat_user["limits"]["zombiehours"] * 60 - 10 146 | if zombie < 0: 147 | zombie = 1430 148 | _print("waiting for %d minutes..." % zombie) 149 | time.sleep(zombie * 60) 150 | elif err == 400: 151 | _print("bad request.") 152 | _identify(True) 153 | elif err == 401: 154 | _print("unauthorised.") 155 | _identify(True) 156 | else: 157 | _print("waiting for 90 seconds...") 158 | time.sleep(90) 159 | except KeyboardInterrupt: 160 | _print("terminating...", begin="\n") 161 | sys.exit(0) 162 | 163 | def _main(): 164 | global email, password 165 | if len(sys.argv) > 2: 166 | password = sys.argv[2] 167 | if len(sys.argv) > 1: 168 | email = sys.argv[1] 169 | _identify() 170 | _run() 171 | 172 | if __name__ == "__main__": 173 | _main() 174 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from distutils.core import setup 2 | from os.path import abspath, dirname, join 3 | import re 4 | 5 | with open(join(abspath(dirname(__file__)), "ircclive.py")) as f: 6 | version = re.search(r'\n__version__ = \"(.*?)\"\n', f.read()).group(1) 7 | 8 | setup( 9 | name="ircclive", 10 | version=version, 11 | author="Mayu Laierlence", 12 | author_email="minacle@live.com", 13 | url="https://github.com/minacle/ircclive", 14 | description="Simple IRCCloud session keeper; written in python3.", 15 | classifiers=[ 16 | "Development Status :: 5 - Production/Stable", 17 | "Environment :: Console", 18 | "Intended Audience :: End Users/Desktop", 19 | "License :: OSI Approved :: MIT License", 20 | "Natural Language :: English", 21 | "Operating System :: OS Independent", 22 | "Programming Language :: Python", 23 | "Programming Language :: Python :: 3", 24 | "Topic :: Communications :: Chat :: Internet Relay Chat", 25 | "Topic :: Utilities", 26 | ], 27 | py_modules=["ircclive"], 28 | keywords=["irccloud"], 29 | entry_points={ 30 | "console_scripts": ["ircclive = ircclive:_main"], 31 | "setuptools.installation": ["eggsecutable = ircclive:_main"], 32 | }, 33 | zip_safe=True, 34 | ) 35 | --------------------------------------------------------------------------------