diff --git a/jimm.py b/jimm.py index 9ca7c51..5bda185 100644 --- a/jimm.py +++ b/jimm.py @@ -1,62 +1,68 @@ -globals().update({color: lambda text, ansi=91+i: f'\x1b[{ansi}m{text}\x1b[0m' - for i, color in enumerate('red green yellow blue magenta cyan'.split())}) -print(red('hello'), green('world')) +def sync(coro): + import asyncio, functools + if not asyncio.iscoroutinefunction(coro): return coro + @functools.wraps(coro) + def wrapper(*args, **kwargs): + loop, future = asyncio.get_event_loop(), asyncio.ensure_future(coro(*args, **kwargs)) + while not future.done(): + loop._process_events(loop._selector.select(0)) + if (ready := loop._ready) and (handle := ready.popleft())._cancelled is False: + task = (tasks := asyncio.tasks._current_tasks).pop(loop, None) + handle._run(); tasks[loop] = task + return future.result() + return wrapper + +for i, c in enumerate('RGYBMC'): globals()[c] = lambda s, i=i: f'\x1b[{91+i}m{s}\x1b[0m' +unsafe = __import__('contextlib').suppress(Exception) +Soup = lambda html: __import__('bs4').BeautifulSoup(html, 'lxml') -'sync' -def Sync(): - import os, sys, asyncio, functools - 'nest_asyncio' in sys.modules or os.system('pip install -q nest_asyncio') - __import__('nest_asyncio').apply(); return lambda func: functools.wraps(func)( - lambda *args, **kwargs: asyncio.run(func(*args, **kwargs))) -sync = Sync() -'' +def SQL(): + import sqlite3, os, hashlib + (con := sqlite3.connect('.db', isolation_level=None)).row_factory = sqlite3.Row + con.execute('PRAGMA journal_mode=wal') + con.execute('PRAGMA busy_timeout='f'{1e9}') + con.execute('CREATE TABLE IF NOT EXISTS kv(k, v, t DEFAULT CURRENT_TIMESTAMP)') + os.makedirs('.blob', exist_ok=True) + def put(sql, filename, blob): + sha1 = hashlib.sha1(blob).hexdigest() + if not sql('SELECT 1 FROM kv WHERE v=?', sha1): + try: + with open(f'.blob/{sha1}', 'xb') as f: f.write(blob) + print(f'{G(len(blob)):>16} {filename}') + except FileExistsError: pass + sql[filename] = sha1 + def get(sql, filename): + return open(f'.blob/{sql[filename]}', 'rb').read() -import os, asyncio, contextlib, multiprocessing, threading -from inspect import iscoroutinefunction + return type('', (), dict(put=put, get=get, + __call__=lambda _, q, *p: list(map(dict, con.execute(q, p))), + __setitem__=lambda sql, k, v: sql('INSERT INTO kv(k,v) VALUES(?,?)', k, v), + __getitem__ = lambda sql, k: sql( + 'SELECT v FROM kv WHERE k=? ORDER BY t DESC LIMIT 1', k)[0]['v'], + __iter__=lambda sql: (kv.values() for kv in sql( + 'SELECT k, v FROM kv GROUP BY k HAVING t = MAX(t)'))))() +sql = SQL() -def go(func, *args, **kwargs): - if iscoroutinefunction(func): - target = lambda: asyncio.runners.run(func(*args, **kwargs)) - else: - target = lambda: func(*args, **kwargs) - multiprocessing.Process(target=target).start() +@sync +async def Page(headless=True): + from playwright.async_api import async_playwright + browser = await (await async_playwright().start()).firefox.launch(headless=headless) + (page := await browser.new_page()).set_default_timeout(0) + for attr in dir(page): + if callable(method := getattr(page, attr)): setattr(page, attr, sync(method)) + async def handle(route): + if route.request.method == 'GET' and (response := await route.fetch()).ok: + sql.put(route.request.url, await response.body()) + await route.continue_() + page.route('**/*', handle) -unsafe = contextlib.suppress(Exception) + def goto(url, goto=page.goto): + goto(url, wait_until='networkidle') + sql.put(url.split('://')[1], page.content().encode()) + from IPython.display import Image + return Image(page.screenshot()) + page.goto = goto + return page +page = Page() -with unsafe: - asyncio.get_running_loop() - asyncio.run = lambda main: threading.Thread( - target=lambda: asyncio.runners.run(main)).start() - -os.environ['DISPLAY'] = ':0' - -import rich, rich.theme, rich.traceback -console = rich.console.Console(width=75, - theme=rich.theme.Theme({ - 'inspect.callable': 'bold color(114)', # inspect - 'inspect.attr': 'white', - 'inspect.help': 'white', - 'inspect.def': 'color(69)', - 'inspect.class': 'color(69)', - 'repr.tag_name': 'color(43)', # repr - 'repr.tag_contents': 'white', - 'repr.bool_true': 'color(77)', # True, False, None - 'repr.bool_false': 'color(161)', - 'repr.none': 'white', - 'repr.attrib_name': 'color(216)', # Name - 'repr.call': 'color(111)', - 'repr.tag_name': 'color(38)', - 'repr.number': 'bold color(113)', - 'repr.str': 'color(183)', - 'traceback.title': 'color(160)', # Traceback - 'traceback.exc_type': 'color(160)', - 'traceback.border': 'color(203)', - 'traceback.error': 'white', - 'traceback.border.syntax_error': 'white', - 'traceback.text': 'white', - 'traceback.exc_value': 'white', - 'traceback.offset': 'white', - 'scope.border': 'color(111)'})) -rich.traceback.install(console=console) -def inspect(obj): - rich.inspect(obj, methods=True, console=console) \ No newline at end of file +page.goto('https://naver.com/') \ No newline at end of file