initial commit

This commit is contained in:
2023-02-18 07:39:08 +01:00
commit e63ab7b77e
63 changed files with 7122 additions and 0 deletions
+139
View File
@@ -0,0 +1,139 @@
import argparse
import asyncio
import contextlib
import datetime
import logging
import random
import re
import subprocess
import time
import urllib.parse
from pathlib import Path
from playwright.async_api import async_playwright, Browser, BrowserContext, Page
@contextlib.asynccontextmanager
async def launch_browser(headless: bool = False) -> Page:
async with async_playwright() as playwright:
browser = await playwright.chromium.launch(headless=headless)
browser: Browser
async with browser:
ctx = await browser.new_context()
ctx: BrowserContext
async with ctx:
page = await ctx.new_page()
async with page:
yield page
class Error(Exception):
pass
def alert(text: str):
subprocess.run(['open', f'alfred://runtrigger/dev.abdus.integrations/remind/?argument={urllib.parse.quote(text)}'])
dump_dir = Path('~/Desktop').expanduser() / 'immigration'
dump_dir.mkdir(parents=True, exist_ok=True)
async def check_slots(page: Page, retry_count: int = 1) -> list[datetime.datetime]:
await page.add_init_script('''Object.defineProperty(navigator, 'webdriver', { get: () => false })''')
window_id = random.randint(1000, 9999)
req_id = random.randint(0, 999)
await page.goto(
f'https://otv.verwalt-berlin.de/ams/TerminBuchen/wizardng?dswid={window_id}&dsrid={req_id}',
wait_until='networkidle',
)
await page.check('[name="gelesen"]')
async with page.expect_navigation(url=re.compile('st=2')):
await page.click('[name="applicationForm:managedForm:proceed"]')
await page.get_by_role("combobox", name="Staatsangehörigkeit *").select_option(label="Türkei")
await asyncio.sleep(0.3)
await page.get_by_role(
"combobox",
name="Anzahl der Personen, die einen Aufenthaltstitel beantragen (auch ausländische Ehepartner und Kinder) *",
).select_option(label="eine Person")
await asyncio.sleep(0.3)
await page.get_by_role(
"combobox", name="Leben Sie in Berlin zusammen mit einem Familienangehörigen (z.B. Ehepartner, Kind) *"
).select_option(label="nein")
await asyncio.sleep(0.3)
await page.get_by_text("Aufenthaltstitel - beantragen").click()
await page.locator("label").filter(has_text="Erwerbstätigkeit").click()
await page.get_by_text("Blaue Karte EU (§ 18b Abs. 2)").click()
stop_at = datetime.datetime.now() + datetime.timedelta(minutes=28)
async def go_forward():
async with page.expect_navigation(url=re.compile(r'st='), wait_until='networkidle', timeout=60_000):
await page.get_by_role("button", name="Weiter").click()
if 'st=2' in page.url:
error_message = await page.inner_text('.errorMessage')
if 'keine Termine frei' in error_message:
return False
raise Error(error_message)
return 'st=3' in page.url
while 'st=2' in page.url:
retry_count -= 1
async with page.expect_navigation(url=re.compile(r'st='), wait_until='networkidle', timeout=60_000):
await page.get_by_role("button", name="Weiter").click()
if datetime.datetime.now() > stop_at:
raise TimeoutError('could not find a slot')
if not retry_count:
return []
await asyncio.sleep(random.randint(4, 10))
continue
if 'st=3' in page.url:
alert('empty slot found')
now = int(datetime.datetime.now().timestamp())
html_path = dump_dir / f'immigration_{now}.html'
html_path.write_text(await page.content())
await page.screenshot(path=dump_dir/f'immigration_{now}.png')
await page.pause()
input()
def parse_args():
arger = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
arger.add_argument(
'--headless', '--silent', '--quiet', action='store_true', dest='headless', help='Run in headless mode'
)
arger.add_argument(
'--retry-count', '--retry', type=int, default=1, help='Run in headless mode'
)
args, _ = arger.parse_known_args()
return args
async def main():
args = parse_args()
async with launch_browser(headless=args.headless) as page:
try:
slots = await check_slots(page, retry_count=args.retry_count)
if not slots:
print('no slots yet')
return
except Exception as e:
logging.exception('got an error')
await page.pause()
if __name__ == '__main__':
asyncio.run(main())
logging.basicConfig(level=logging.INFO)
main()