feat: better cli
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -8,3 +8,5 @@ wheels/
|
|||||||
|
|
||||||
# Virtual environments
|
# Virtual environments
|
||||||
.venv
|
.venv
|
||||||
|
#----------------Custom
|
||||||
|
.env
|
||||||
|
|||||||
15
README.md
15
README.md
@@ -17,13 +17,24 @@ This will provide the `uulm` command.
|
|||||||
```
|
```
|
||||||
Usage: uulm [OPTIONS] COMMAND [ARGS]...
|
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:
|
Options:
|
||||||
-u, --username TEXT
|
-u, --username TEXT
|
||||||
-p, --password TEXT
|
-p, --password TEXT
|
||||||
--headful Show the browser window
|
--headful Show the browser window
|
||||||
|
-d, --debug Set the log level to DEBUG
|
||||||
--help Show this message and exit.
|
--help Show this message and exit.
|
||||||
|
|
||||||
Commands:
|
Commands:
|
||||||
campusonline Interact with the module tree in Campusonline
|
campusonline Interact with the module tree in Campusonline.
|
||||||
coronang Automatically register for courses on CoronaNG
|
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",
|
"click>=8.1.8",
|
||||||
"pandas>=2.2.3",
|
"pandas>=2.2.3",
|
||||||
"playwright>=1.51.0",
|
"playwright>=1.51.0",
|
||||||
|
"python-dotenv>=1.1.0",
|
||||||
"questionary>=2.1.0",
|
"questionary>=2.1.0",
|
||||||
]
|
]
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import asyncclick as click
|
|||||||
import questionary
|
import questionary
|
||||||
from playwright.async_api import async_playwright, Playwright
|
from playwright.async_api import async_playwright, Playwright
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
import asyncio
|
import asyncio
|
||||||
@@ -10,9 +12,9 @@ import logging
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
|
load_dotenv() # take environment variables
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
CORONANG_VERSION='v1.8.00'
|
|
||||||
Selection = Enum('Selection', ['TREE_WALK', 'TREE_LEAF', 'ITEM_SELECTED'])
|
Selection = Enum('Selection', ['TREE_WALK', 'TREE_LEAF', 'ITEM_SELECTED'])
|
||||||
|
|
||||||
async def selection_or_walk(options):
|
async def selection_or_walk(options):
|
||||||
@@ -33,19 +35,25 @@ async def run_playwright(headless: bool):
|
|||||||
await browser.close()
|
await browser.close()
|
||||||
|
|
||||||
@click.group()
|
@click.group()
|
||||||
@click.option('--username','-u')
|
@click.option('--username','-u', envvar='UULM_USERNAME', prompt='Enter your kiz username:')
|
||||||
@click.option('--password','-p')
|
@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('--headful', is_flag=True, help='Show the browser window')
|
||||||
@click.option('--debug', '-d', is_flag=True, help='Set the log level to DEBUG')
|
@click.option('--debug', '-d', is_flag=True, help='Set the log level to DEBUG')
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
async def cli(ctx, username, password, headful, debug):
|
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')
|
logging.basicConfig(level=logging.WARNING,format='%(asctime)s - %(levelname)s - %(message)s')
|
||||||
if(debug): logger.setLevel(logging.DEBUG)
|
if(debug): logger.setLevel(logging.DEBUG)
|
||||||
ctx.ensure_object(dict)
|
ctx.ensure_object(dict)
|
||||||
if ctx.invoked_subcommand != 'grades':
|
ctx.obj['HEADLESS'] = not headful
|
||||||
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
|
|
||||||
|
|
||||||
@cli.command()
|
@cli.command()
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
@@ -65,17 +73,18 @@ async def campusonline(ctx):
|
|||||||
selection = await selection_or_walk(options)
|
selection = await selection_or_walk(options)
|
||||||
print(selection)
|
print(selection)
|
||||||
sleep(2)
|
sleep(2)
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
@cli.command()
|
@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.option('--before', '-b', type=int, default=10, help='How many seconds before the target time to start')
|
||||||
@click.pass_context
|
@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".
|
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.
|
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)
|
logger.info('Parsed input times as %s', target_times)
|
||||||
async for page, browser, context in run_playwright(ctx.obj['HEADLESS']):
|
async for page, browser, context in run_playwright(ctx.obj['HEADLESS']):
|
||||||
await page.goto("https://campusonline.uni-ulm.de/CoronaNG/user/mycorona.html")
|
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")
|
server_time = datetime.strptime(server_str.split().pop(), "%H:%M:%S")
|
||||||
break
|
break
|
||||||
|
|
||||||
exit()
|
raise NotImplementedError
|
||||||
await page.locator("input[name=\"uid\"]").click()
|
await page.locator("input[name=\"uid\"]").click()
|
||||||
await page.locator("input[name=\"uid\"]").fill(ctx.obj['USERNAME'])
|
await page.locator("input[name=\"uid\"]").fill(ctx.obj['USERNAME'])
|
||||||
await page.locator("input[name=\"password\"]").click()
|
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.get_by_role("cell", name="An Markierten teilnehmen Ausf").get_by_role("button").click()
|
||||||
await page.reload()
|
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()
|
@cli.command()
|
||||||
@click.argument('filename', type=click.Path(exists=True))
|
@click.argument('filename', type=click.Path(exists=True))
|
||||||
@click.option('--target_lp', '-t', type=int, default=74, help='Target number of n credits needed')
|
@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 },
|
{ 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]]
|
[[package]]
|
||||||
name = "pytz"
|
name = "pytz"
|
||||||
version = "2025.2"
|
version = "2025.2"
|
||||||
@@ -285,6 +294,7 @@ dependencies = [
|
|||||||
{ name = "click" },
|
{ name = "click" },
|
||||||
{ name = "pandas" },
|
{ name = "pandas" },
|
||||||
{ name = "playwright" },
|
{ name = "playwright" },
|
||||||
|
{ name = "python-dotenv" },
|
||||||
{ name = "questionary" },
|
{ name = "questionary" },
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -294,6 +304,7 @@ requires-dist = [
|
|||||||
{ name = "click", specifier = ">=8.1.8" },
|
{ name = "click", specifier = ">=8.1.8" },
|
||||||
{ name = "pandas", specifier = ">=2.2.3" },
|
{ name = "pandas", specifier = ">=2.2.3" },
|
||||||
{ name = "playwright", specifier = ">=1.51.0" },
|
{ name = "playwright", specifier = ">=1.51.0" },
|
||||||
|
{ name = "python-dotenv", specifier = ">=1.1.0" },
|
||||||
{ name = "questionary", specifier = ">=2.1.0" },
|
{ name = "questionary", specifier = ">=2.1.0" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user