diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9ddb7ab --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +**/node_modules +**/build +.vscode +__pycache__ diff --git a/README.md b/README.md new file mode 100644 index 0000000..014558f --- /dev/null +++ b/README.md @@ -0,0 +1,58 @@ +# cookie-sync + +## Description + +## About + +This is a simple extension that allows you to sync cookies between Chrome and Python using the [websocket](https://en.wikipedia.org/wiki/WebSocket) protocol. + +## Requirements + +- Python >= 3.7 +- [aiohttp](https://docs.aiohttp.org/en/stable) + +## Usage + +1. `pip install cookie-sync` + +2. Download the and unarchive the extension from [releases](https://github.com/sunney-x/cookie-sync/releases) + +3. Set `host_permissions` for the target website in `manifest.json` + +4. Load the extension in Chrome + - Go to `chrome://extensions` + - Click `Load unpacked` and locate the extension folder + +5. Quickstart example +```python +import aiohttp +from cookie_sync import run_server + +session = aiohttp.ClientSession() + +run_server( + session=session, + domain='https://google.com' +) +``` + +## Documentation + +### **run_server** configuration options + +```py +session: aiohttp.ClientSession + - 'The session to keep cookies synced on' + +domain: str + - 'The domain to sync cookies for' + +cookies: Optional[list] # Default [] + - 'List of cookies to sync, if empty, all cookies will be synced' + +wait_for_cookies: Optional[bool] # Default True + - 'If `False`, will not wait for the cookies to be synced' + +timeout: Optional[int] # Default 30 + - 'Timeout in seconds to wait for `wait_for_cookies`' +``` diff --git a/cookie_sync/__init__.py b/cookie_sync/__init__.py new file mode 100644 index 0000000..52c6408 --- /dev/null +++ b/cookie_sync/__init__.py @@ -0,0 +1 @@ +from .server import run_server diff --git a/cookie_sync/server.py b/cookie_sync/server.py new file mode 100644 index 0000000..9d7000b --- /dev/null +++ b/cookie_sync/server.py @@ -0,0 +1,67 @@ +import time +from aiohttp import web +from typing import Dict, List +import aiohttp +import json +import threading +import asyncio + + +def _run_server( + session: aiohttp.ClientSession, + cookie_names: List[str], + domain: str, +): + async def websocket_handler(request): + ws = web.WebSocketResponse() + await ws.prepare(request) + + await ws.send_json({ + 'action': 'get', + 'cookies': cookie_names, + 'domain': domain, + }) + + async for msg in ws: + if msg.type != aiohttp.WSMsgType.TEXT: + continue + + try: + cookies: List[Dict[str, str]] = msg.json() + except json.decoder.JSONDecodeError: + continue + + for c in cookies: + session.cookie_jar.update_cookies(c) + + return ws + + app = web.Application() + app.add_routes([web.get('/ws', websocket_handler)]) + + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + web.run_app(app, host='localhost', port=3001) + + +def run_server( + session: aiohttp.ClientSession, + domain: str, + cookies: List[str] = [], + wait_for_cookies: bool = True, + timeout: int = 30, +): + threading.Thread( + target=_run_server, + args=(session, cookies, domain,), + daemon=True + ).start() + + if wait_for_cookies: + t = time.time() + + while not len(session.cookie_jar): + if (time.time() - t) > timeout: + break + + pass diff --git a/extension/manifest.json b/extension/manifest.json new file mode 100644 index 0000000..537598f --- /dev/null +++ b/extension/manifest.json @@ -0,0 +1,11 @@ +{ + "name": "Cookie Sync", + "description": "Synchronize cookies between chrome and Python", + "version": "0.1", + "manifest_version": 3, + "host_permissions": ["*://*.google.com/*"], + "permissions": ["cookies"], + "background": { + "service_worker": "build/sw.js" + } +} diff --git a/extension/package.json b/extension/package.json new file mode 100644 index 0000000..04af240 --- /dev/null +++ b/extension/package.json @@ -0,0 +1,15 @@ +{ + "name": "cookie_sync", + "version": "0.1", + "description": "Synchronize cookies between chrome and Python", + "scripts": { + "dev": "tsc -p . --watch", + "build": "tsc -p ." + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "@types/chrome": "0.0.179" + } +} diff --git a/extension/src/sw.ts b/extension/src/sw.ts new file mode 100644 index 0000000..30055d0 --- /dev/null +++ b/extension/src/sw.ts @@ -0,0 +1,62 @@ +interface CookieRequest { + action: "get"; + cookies: string[]; + domain: string; +} + +const neededCookies: string[] = []; +let ws: WebSocket; + +const connect = () => { + const _ws = new WebSocket("ws://localhost:3001/ws"); + ws = _ws; + ws.onopen = () => { + console.log("Connected! 👌"); + }; + + ws.onmessage = async (event): Promise => { + const cookieReq: CookieRequest = JSON.parse(event.data); + + switch (cookieReq.action) { + case "get": + let retrievedCookies: { [key: string]: string }[] = []; + + if (cookieReq.cookies.length) { + const promises = cookieReq.cookies.map((c) => + chrome.cookies + .get({ name: c, url: cookieReq.domain }) + .then((c) => c && retrievedCookies.push({ [c.name]: c.value })) + ); + + await Promise.all(promises); + } else { + retrievedCookies = (await chrome.cookies.getAll({})).map((c) => ({ + [c.name]: c.value, + })); + } + + ws.send(JSON.stringify(retrievedCookies)); + } + }; + + ws.onclose = () => { + console.log("Disconnected! 👎"); + setTimeout(connect, 10 * 1000); + }; + + ws.onerror = () => { + console.log("Error! 💩"); + ws.close(); + }; +}; + +connect(); + +chrome.cookies.onChanged.addListener((c) => { + if ( + c.cause === "overwrite" && + (neededCookies.includes(c.cookie.name) || !neededCookies.length) + ) { + ws.send(JSON.stringify({ [c.cookie.name]: c.cookie.value })); + } +}); diff --git a/extension/tsconfig.json b/extension/tsconfig.json new file mode 100644 index 0000000..8183708 --- /dev/null +++ b/extension/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": ["ES2016", "DOM"], + "module": "commonjs", + "outDir": "./build", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..8f9530d --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +aiohttp == 3.8.1 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..664368e --- /dev/null +++ b/setup.py @@ -0,0 +1,17 @@ +from setuptools import setup + + +setup( + name='cookie-sync', + version='0.0.1', + description='Sync cookies between Chrome', + author='Sunney-X', + author_email='sunneyxdev@gmail.com', + url='https://github.com/Sunney-X/cookie-sync', + packages=['cookie_sync'], + long_description=open('README.md').read(), + long_description_content_type="text/markdown", + install_requires=[ + "aiohttp" + ], +)