feat: better cli
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -8,3 +8,5 @@ wheels/
|
||||
|
||||
# Virtual environments
|
||||
.venv
|
||||
#----------------Custom
|
||||
.env
|
||||
|
||||
15
README.md
15
README.md
@@ -17,13 +17,24 @@ This will provide the `uulm` command.
|
||||
```
|
||||
Usage: uulm [OPTIONS] COMMAND [ARGS]...
|
||||
|
||||
Passing username and password is supported through multiple ways as entering
|
||||
your password visibly into your shell history is discouraged for security
|
||||
reasons.
|
||||
|
||||
- using environment variables `UULM_USERNAME`, `UULM_PASSWORD`
|
||||
- using a `.env` file in the current working directory with the same variables
|
||||
- interactive mode, if none of the above was specified
|
||||
|
||||
Options:
|
||||
-u, --username TEXT
|
||||
-p, --password TEXT
|
||||
--headful Show the browser window
|
||||
-d, --debug Set the log level to DEBUG
|
||||
--help Show this message and exit.
|
||||
|
||||
Commands:
|
||||
campusonline Interact with the module tree in Campusonline
|
||||
coronang Automatically register for courses on CoronaNG
|
||||
campusonline Interact with the module tree in Campusonline.
|
||||
coronang Automatically register for courses on CoronaNG by...
|
||||
grades Calculate your weighted grade using the best n credits.
|
||||
sport Automatically register for courses on the AktivKonzepte...
|
||||
```
|
||||
|
||||
@@ -9,6 +9,7 @@ dependencies = [
|
||||
"click>=8.1.8",
|
||||
"pandas>=2.2.3",
|
||||
"playwright>=1.51.0",
|
||||
"python-dotenv>=1.1.0",
|
||||
"questionary>=2.1.0",
|
||||
]
|
||||
[project.scripts]
|
||||
|
||||
@@ -2,6 +2,8 @@ import asyncclick as click
|
||||
import questionary
|
||||
from playwright.async_api import async_playwright, Playwright
|
||||
import pandas as pd
|
||||
from dotenv import load_dotenv
|
||||
|
||||
|
||||
from enum import Enum
|
||||
import asyncio
|
||||
@@ -10,9 +12,9 @@ import logging
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
load_dotenv() # take environment variables
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
CORONANG_VERSION='v1.8.00'
|
||||
Selection = Enum('Selection', ['TREE_WALK', 'TREE_LEAF', 'ITEM_SELECTED'])
|
||||
|
||||
async def selection_or_walk(options):
|
||||
@@ -33,19 +35,25 @@ async def run_playwright(headless: bool):
|
||||
await browser.close()
|
||||
|
||||
@click.group()
|
||||
@click.option('--username','-u')
|
||||
@click.option('--password','-p')
|
||||
@click.option('--username','-u', envvar='UULM_USERNAME', prompt='Enter your kiz username:')
|
||||
@click.option('--password','-p', envvar='UULM_PASSWORD', prompt='Enter your kiz password:', hide_input=True)
|
||||
@click.option('--headful', is_flag=True, help='Show the browser window')
|
||||
@click.option('--debug', '-d', is_flag=True, help='Set the log level to DEBUG')
|
||||
@click.pass_context
|
||||
async def cli(ctx, username, password, headful, debug):
|
||||
'''
|
||||
Passing username and password is supported through multiple ways
|
||||
as entering your password visibly into your shell history is discouraged for security reasons.
|
||||
|
||||
\b
|
||||
- using environment variables `UULM_USERNAME`, `UULM_PASSWORD`
|
||||
- using a `.env` file in the current working directory with the same variables
|
||||
- interactive mode, if none of the above was specified
|
||||
'''
|
||||
logging.basicConfig(level=logging.WARNING,format='%(asctime)s - %(levelname)s - %(message)s')
|
||||
if(debug): logger.setLevel(logging.DEBUG)
|
||||
ctx.ensure_object(dict)
|
||||
if ctx.invoked_subcommand != 'grades':
|
||||
ctx.obj['USERNAME'] = username or await questionary.text('Enter your kiz username:').ask_async()
|
||||
ctx.obj['PASSWORD'] = password or await questionary.password('Enter your kiz password:').ask_async()
|
||||
ctx.obj['HEADLESS'] = not headful
|
||||
ctx.obj['HEADLESS'] = not headful
|
||||
|
||||
@cli.command()
|
||||
@click.pass_context
|
||||
@@ -65,17 +73,18 @@ async def campusonline(ctx):
|
||||
selection = await selection_or_walk(options)
|
||||
print(selection)
|
||||
sleep(2)
|
||||
raise NotImplementedError
|
||||
|
||||
@cli.command()
|
||||
@click.argument('times', nargs=-1, required=True)
|
||||
@click.argument('target_times', nargs=-1, type=click.DateTime( ['%H:%M:%S']), required=True)
|
||||
@click.option('--before', '-b', type=int, default=10, help='How many seconds before the target time to start')
|
||||
@click.pass_context
|
||||
async def coronang(ctx, times, before):
|
||||
async def coronang(ctx, target_times, before):
|
||||
'''
|
||||
Automatically register for courses on CoronaNG by specifying one or more timestamps of the format "HH:MM:SS".
|
||||
Please beware that CoronaNG only allows one active session at all times.
|
||||
'''
|
||||
target_times = sorted([datetime.strptime(t, "%H:%M:%S") for t in times])
|
||||
CORONANG_VERSION='v1.8.00'
|
||||
logger.info('Parsed input times as %s', target_times)
|
||||
async for page, browser, context in run_playwright(ctx.obj['HEADLESS']):
|
||||
await page.goto("https://campusonline.uni-ulm.de/CoronaNG/user/mycorona.html")
|
||||
@@ -90,7 +99,7 @@ async def coronang(ctx, times, before):
|
||||
server_time = datetime.strptime(server_str.split().pop(), "%H:%M:%S")
|
||||
break
|
||||
|
||||
exit()
|
||||
raise NotImplementedError
|
||||
await page.locator("input[name=\"uid\"]").click()
|
||||
await page.locator("input[name=\"uid\"]").fill(ctx.obj['USERNAME'])
|
||||
await page.locator("input[name=\"password\"]").click()
|
||||
@@ -102,6 +111,23 @@ async def coronang(ctx, times, before):
|
||||
await page.get_by_role("cell", name="An Markierten teilnehmen Ausf").get_by_role("button").click()
|
||||
await page.reload()
|
||||
|
||||
@cli.command()
|
||||
@click.argument('target_times', nargs=-1, type=click.DateTime( ["%H:%M:%S"]), required=True)
|
||||
@click.option('--target_course', '-t', multiple=True, required=True, help='Unique course name to register for. Can be passed multiple times')
|
||||
@click.option('--before', '-b', type=int, default=10, help='How many seconds before the target time to start')
|
||||
@click.pass_context
|
||||
async def sport(ctx, target_times, target_course, before):
|
||||
'''
|
||||
Automatically register for courses on the AktivKonzepte Hochschulsport Platform
|
||||
by specifying one or more timestamps of the format "HH:MM:SS".
|
||||
'''
|
||||
print(target_course)
|
||||
# TODO Check Version in HTML Head
|
||||
logger.info('Parsed input times as %s', target_times)
|
||||
async for page, browser, context in run_playwright(ctx.obj['HEADLESS']):
|
||||
pass
|
||||
raise NotImplementedError
|
||||
|
||||
@cli.command()
|
||||
@click.argument('filename', type=click.Path(exists=True))
|
||||
@click.option('--target_lp', '-t', type=int, default=74, help='Target number of n credits needed')
|
||||
|
||||
11
uv.lock
generated
11
uv.lock
generated
@@ -219,6 +219,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-dotenv"
|
||||
version = "1.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/88/2c/7bb1416c5620485aa793f2de31d3df393d3686aa8a8506d11e10e13c5baf/python_dotenv-1.1.0.tar.gz", hash = "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5", size = 39920 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/18/98a99ad95133c6a6e2005fe89faedf294a748bd5dc803008059409ac9b1e/python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d", size = 20256 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pytz"
|
||||
version = "2025.2"
|
||||
@@ -285,6 +294,7 @@ dependencies = [
|
||||
{ name = "click" },
|
||||
{ name = "pandas" },
|
||||
{ name = "playwright" },
|
||||
{ name = "python-dotenv" },
|
||||
{ name = "questionary" },
|
||||
]
|
||||
|
||||
@@ -294,6 +304,7 @@ requires-dist = [
|
||||
{ name = "click", specifier = ">=8.1.8" },
|
||||
{ name = "pandas", specifier = ">=2.2.3" },
|
||||
{ name = "playwright", specifier = ">=1.51.0" },
|
||||
{ name = "python-dotenv", specifier = ">=1.1.0" },
|
||||
{ name = "questionary", specifier = ">=2.1.0" },
|
||||
]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user