9 Commits

Author SHA1 Message Date
9117ff1354 Merge branch 'fix/xdg-path-fix-again' of github.com:szymon-jozef/zutui into fix/xdg-path-fix-again 2026-02-26 18:53:12 +01:00
f25306380f comma 2026-02-26 18:52:08 +01:00
483270349e pkgbuild version bump 2026-02-26 18:30:44 +01:00
86d792e24d make xdg dirs more correct! 2026-02-26 18:25:13 +01:00
Szymon
acc19d9539 Add nix flake (#7) 2026-02-22 18:19:16 +01:00
Szymon
ceefa475cb Add PKGBUILD for nice Arch Linux install (#6)
* add PGKBUILD

* update README with AUR info
2026-02-16 23:59:29 +01:00
shv187
be89c511f9 Fix deps on non-Windows platforms. 2026-02-15 13:56:28 +01:00
Szymon
e48ed5370a Add secure password storing (#4)
* Added xdg-compliant path for Linux and macOS

Created config.py

Moved system path logic to config.py

* Added secure password storing with keyring

* fixed not showing login screen if no password is saved

* Update readme with new config info

* add pywin32 lib
2026-02-15 13:21:23 +01:00
876463d7c5 Added xdg-compliant path for Linux and macOS 2026-02-15 13:06:41 +01:00
8 changed files with 219 additions and 30 deletions

3
.gitignore vendored
View File

@@ -214,3 +214,6 @@ __marimo__/
# Streamlit # Streamlit
.streamlit/secrets.toml .streamlit/secrets.toml
# nix
result

31
PKGBUILD Normal file
View File

@@ -0,0 +1,31 @@
# Maintainer: <szymon_jozef@proton.me>
_pkgname="zutui"
pkgname="$_pkgname-git"
pkgver="1.0.0.r8.ceefa47"
pkgrel=4
pkgdesc="TUI for accessing edziekanat of ZUT university"
arch=("any")
url="https://github.com/shv187/zutui"
license=("MIT")
depends=("python" "python-keyring" "python-textual" "python-appdirs" "python-beautifulsoup4" "python-requests")
makedepends=("git" "python-build" "python-installer" "python-wheel" "python-setuptools")
provides=("zutui")
conflicts=("zutui")
source=("$_pkgname::git+${url}.git")
sha256sums=("SKIP")
pkgver() {
cd "$_pkgname"
printf "1.0.0.r%s.%s" "$(git rev-list --count HEAD)" "$(git rev-parse --short HEAD)"
}
build() {
cd "$_pkgname"
python -m build --wheel --no-isolation
}
package() {
cd "$_pkgname"
python -m installer --destdir="$pkgdir" dist/*.whl
install -Dm644 LICENSE "$pkgdir/usr/share/licenses/$pkgname/LICENSE"
}

View File

@@ -10,15 +10,36 @@
<img width="100%" height="auto" alt="Grades Page" src="https://github.com/user-attachments/assets/6536c74d-13a5-436e-af3d-5522216ce8c1" /> <img width="100%" height="auto" alt="Grades Page" src="https://github.com/user-attachments/assets/6536c74d-13a5-436e-af3d-5522216ce8c1" />
# Usage # Usage
### 1. Download: ## 1. Download:
### On Windows:
``` ```
pip install git+https://github.com/shv187/zutui.git pip install git+https://github.com/shv187/zutui.git
``` ```
### 2. Start: ### Arch Linux:
You can use PKGBUILD. Just run:
```bash
makepkg -si
```
or if you want to, you can use AUR helper of your choice. For example:
```bash
yay -S zutui-git
```
### Nix:
```bash
nix profile install github:shv187/zutui
```
### MacOs and other Linux distros:
Most distros environments are externally managed by the system package manager, so the most straightforward way would be using something like `pipx`.
```bash
pipx install git+https://github.com/shv187/zutui.git
```
## 2. Start:
``` ```
zutui zutui
``` ```
### 3. Enjoy :) ## 3. Enjoy :)
# Bindings # Bindings
`j` - Down `j` - Down
@@ -30,17 +51,12 @@ zutui
`ctrl`+`q` - Quit `ctrl`+`q` - Quit
# Sidenotes # Sidenotes
- config is stored @ `~/zutui/config.json` ## Config
- On Unix like is stored @ `~/.config/zutui/config.json`
- On Windows is stored @ `~/zutui/config.json`
- so are `grades_cache.json` - so are `grades_cache.json`
- in plaintext.....
##### As of right now I don't see any MUCH better way. ##### Feel free to pull request if you're smarter :)
##### I mean we could simply encrypt and decrypt it during runtime, but... does it really make it any more secure?
##### And we can't really store it in any form of a _hash_ as we have to log in everytime with plain text anyways...
##### However feel free to pull request if you're smarter :)
# TODO: # TODO:
- [x] **Nothing** - [x] **Nothing**

61
flake.lock generated Normal file
View File

@@ -0,0 +1,61 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1771369470,
"narHash": "sha256-0NBlEBKkN3lufyvFegY4TYv5mCNHbi5OmBDrzihbBMQ=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "0182a361324364ae3f436a63005877674cf45efb",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

60
flake.nix Normal file
View File

@@ -0,0 +1,60 @@
{
description = "TUI for accessing edziekanat of ZUT university";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
};
outputs =
{
self,
nixpkgs,
flake-utils,
}:
flake-utils.lib.eachDefaultSystem (
system:
let
pkgs = nixpkgs.legacyPackages.${system};
in
{
packages = {
default = pkgs.python3Packages.buildPythonApplication {
pname = "zutui";
version = "1.0.0";
src = ./.;
pyproject = true;
build-system = with pkgs.python3Packages; [
setuptools
wheel
];
dependencies = with pkgs.python3Packages; [
keyring
textual
requests
beautifulsoup4
appdirs
];
meta = with pkgs.lib; {
description = "TUI for accessing edziekanat of ZUT university";
homepage = "https://github.com/shv187/zutui";
license = licenses.mit;
mainProgram = "zutui";
};
};
};
devShells = {
default = pkgs.mkShell {
inputsFrom = [ self.packages.${system}.default ];
};
};
}
);
}

View File

@@ -8,6 +8,9 @@ setup(
"requests", "requests",
"beautifulsoup4", "beautifulsoup4",
"textual", "textual",
"keyring",
"pywin32; sys_platform == 'win32'",
"appdirs",
], ],
entry_points={ entry_points={
'console_scripts': [ 'console_scripts': [

View File

@@ -1,15 +1,18 @@
import requests import requests
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
import json import json
from appdirs import user_config_dir, user_cache_dir
import os import os
USER_HOME = os.path.expanduser("~") app_name = "zutui"
APP_DIR = os.path.join(USER_HOME, "zutui") app_author = "shv187"
if not os.path.exists(APP_DIR):
os.makedirs(APP_DIR)
CACHE_FILE = os.path.join(APP_DIR, "grades_cache.json") CONFIG_DIR = user_config_dir(app_name, app_author)
CACHE_DIR = user_cache_dir(app_name, app_author)
os.makedirs(CACHE_DIR, exist_ok=True)
os.makedirs(CONFIG_DIR, exist_ok=True)
CACHE_FILE = os.path.join(CACHE_DIR, "grades_cache.json")
TIMEOUT = 10 TIMEOUT = 10
class ZUT: class ZUT:

View File

@@ -1,8 +1,9 @@
import json import json
import os import os
import time import keyring
from keyring.errors import KeyringError
from datetime import datetime from datetime import datetime
from appdirs import user_config_dir
from textual.app import App, ComposeResult from textual.app import App, ComposeResult
from textual.containers import Container from textual.containers import Container
from textual.widgets import Header, Footer, DataTable, Label, Button, Input from textual.widgets import Header, Footer, DataTable, Label, Button, Input
@@ -11,10 +12,7 @@ from textual.binding import Binding
from .zut_client import ZUT from .zut_client import ZUT
USER_HOME = os.path.expanduser("~") APP_DIR = user_config_dir("zutui", "shv187")
APP_DIR = os.path.join(USER_HOME, "zutui")
if not os.path.exists(APP_DIR):
os.makedirs(APP_DIR)
CONFIG_FILE = os.path.join(APP_DIR, "config.json") CONFIG_FILE = os.path.join(APP_DIR, "config.json")
REFRESH_INTERVAL = 1800 REFRESH_INTERVAL = 1800
@@ -58,9 +56,20 @@ class LoginScreen(Screen):
if success: if success:
try: try:
with open(CONFIG_FILE, "w") as f: with open(CONFIG_FILE, "w") as f:
json.dump({"username": user, "password": password}, f) json.dump({"username": user}, f)
keyring.set_password("zutui", user, password)
except KeyringError as e:
error = e
self.app.call_from_thread(
lambda:
self.app.notify(
f"Błąd: {str(error)}",
title="Błąd zapisu hasła do systemowego menedżera haseł",
severity="warning",
timeout=5
)
)
except: pass except: pass
def do_switch(): def do_switch():
self.app.switch_screen(DashboardScreen()) self.app.switch_screen(DashboardScreen())
self.app.call_from_thread(do_switch) self.app.call_from_thread(do_switch)
@@ -410,7 +419,10 @@ class ZutApp(App):
try: try:
with open(CONFIG_FILE, "r") as f: with open(CONFIG_FILE, "r") as f:
creds = json.load(f) creds = json.load(f)
self.zut_client = ZUT(creds["username"], creds["password"]) password = keyring.get_password("zutui", creds["username"])
if password is None:
raise ValueError("Hasło nie zostało znalezione")
self.zut_client = ZUT(creds["username"], password)
self.push_screen(DashboardScreen()) self.push_screen(DashboardScreen())
except: except:
self.push_screen(LoginScreen()) self.push_screen(LoginScreen())