diff --git a/.gitignore b/.gitignore index 65c34dc..2c6bc1b 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ build/ # Omit committing pubspec.lock for library packages; see # https://dart.dev/guides/libraries/private-files#pubspeclock. pubspec.lock +.env* diff --git a/README.md b/README.md index 5750a11..60c91cd 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ ## O knihovně Experimentální **neoficiální** webscrape knihovna pro komunikaci se systémem [iCanteen](https://www.z-ware.cz/internetove-objednavky) +[![wakatime](https://wakatime.com/badge/user/17178fab-a33c-430f-a764-7b3f26c7b966/project/82873d93-5b79-4978-a5f6-612e21641817.svg)](https://wakatime.com/badge/user/17178fab-a33c-430f-a764-7b3f26c7b966/project/82873d93-5b79-4978-a5f6-612e21641817) + ## Funkční funkce(*) - získání jídelníčku na aktuální den (s cenami) diff --git a/lib/src/canteen.dart b/lib/src/canteen.dart index d4baafa..a8bb750 100644 --- a/lib/src/canteen.dart +++ b/lib/src/canteen.dart @@ -12,8 +12,28 @@ import 'jidlo.dart'; class Canteen { final String url; Map cookies = {"JSESSIONID": "", "XSRF-TOKEN": ""}; + double _kredit = 0.0; + bool prihlasen = false; Canteen(this.url); + /// Vrátí aktuální kredit ze serveru jako [double]. Jelikož je async, nejdřív [Future] + /// + /// Nastane-li chyba, vrací [Null] + Future ziskejKredit() async { + if (!prihlasen) return 0.0; + var r = await _getRequest("/faces/secured/main.jsp"); + if (r == null) return 0.0; + File("./test.txt").writeAsStringSync(r); + var m = double.tryParse(RegExp(r' +(.+?)(?=&)') + .firstMatch(r)! + .group(1)! + .replaceAll(",", ".") + .replaceAll(RegExp(r"[^\w.]"), "")); + if (m == null) return 0.0; + _kredit = m; + return _kredit; + } + Future getFirstSession() async { var res = await http.get(Uri.parse(url)); _parseCookies(res.headers['set-cookie']!); @@ -36,8 +56,9 @@ class Canteen { /// `user` - uživatelské jméno /// `password` - heslo /// + /// Vrátí `true`, když se uživatel přihlásil, jinak `false` /// TODO: Házet chyby - Future login(String user, String password) async { + Future login(String user, String password) async { if (cookies["JSESSIONID"] == "" || cookies["XSRF-TOKEN"] == "") { await getFirstSession(); } @@ -59,11 +80,17 @@ class Canteen { "targetUrl": "/faces/secured/main.jsp?terminal=false&status=true&printer=&keyboard=" }); + if (res.headers['set-cookie']!.contains("remember-me=;")) { + return false; // špatné heslo + } + _parseCookies(res.headers['set-cookie']!); if (res.statusCode != 302) { print(res.body); print("ERROR"); } + prihlasen = true; + return true; } /// Builder pro GET request @@ -128,9 +155,9 @@ class Canteen { var vydejna = RegExp(r'(?<=).+?(?=<)') .firstMatch(s); // název výdejny / verze 2.18 vydejna ??= RegExp( + // TODO: Lepší systém pro podporu různých verzí iCanteen r'(?<=).+?(?=<)') .firstMatch(s); // název výdejny / verze 2.10 - File("dva.txt").writeAsStringSync(s); var hlavni = RegExp( r' {20}(([a-zA-ZěščřžýáíéÉÍÁÝŽŘČŠĚŤŇťň.,:\/]+ )+[a-zA-ZěščřžýáíéÉÍÁÝŽŘČŠĚŤŇťň.,:\/]+)', dotAll: true) @@ -141,7 +168,8 @@ class Canteen { nazev: hlavni, objednano: false, cislo: vydejna!.group(0).toString(), - lzeObjednat: false)); + lzeObjednat: false, + den: den)); } jidelnicek.add(Jidelnicek(den, jidla)); } @@ -150,11 +178,17 @@ class Canteen { /// Získá jídlo pro daný den /// Vyžaduje přihlášení pomocí [login] - Future jidelnicekDen() async { + /// Aktuálně pouze dnešní den + Future jidelnicekDen({DateTime? den}) async { + den ??= DateTime.now(); var res = await _getRequest( - "/faces/secured/main.jsp?terminal=false&android=false&keyboard=false&printer=false"); - var den = DateTime.parse(RegExp(r'(?<=day-).+?(?=")', dotAll: true) - .firstMatch(res!)! + "/faces/secured/main.jsp?day=${den.year}-${(den.month < 10) ? "0" + den.month.toString() : den.month}-${(den.day < 10) ? "0" + den.day.toString() : den.day}&terminal=false&printer=false&keyboard=false"); + if (res!.contains("iCanteen - přihlášení uživatele")) { + prihlasen = false; + throw Exception("Nepřihlášen"); + } + var obedDen = DateTime.parse(RegExp(r'(?<=day-).+?(?=")', dotAll: true) + .firstMatch(res)! .group(0) .toString()); var jidla = []; @@ -173,6 +207,8 @@ class Canteen { !(o.contains("nelze zrušit") || o.contains("nelze objednat")); var cenaMatch = RegExp(r'(?<=Cena objednaného jídla">).+?(?=&)').firstMatch(o); + cenaMatch ??= + RegExp(r'(?<=Cena při objednání jídla: ).+?(?=&)').firstMatch(o); cenaMatch ??= RegExp(r'(?<=Cena při objednání jídla">).+?(?=&)').firstMatch(o); var cena = @@ -202,20 +238,46 @@ class Canteen { cislo: vydejna, lzeObjednat: lzeObjednat, cena: cena, - orderUrl: orderUrl)); + orderUrl: orderUrl, + den: obedDen)); // KONEC formátování do třídy } - return Jidelnicek(den, jidla); + return Jidelnicek(obedDen, jidla); } - Future objednat(Jidlo j) async { + /// Objedná vybrané jídlo + /// Vrátí upravenou instanci [Jidlo], v případě chyby vrací originální + Future objednat(Jidlo j) async { //TODO if (!j.lzeObjednat || j.orderUrl == null || j.orderUrl!.isEmpty) { - return false; + return j; } - var res = await _getRequest(j.orderUrl!); - print(res); - return true; + var res = + await _getRequest("/faces/secured/" + j.orderUrl!); // provést operaci + if (res == null || res.contains("Chyba")) return j; + + var novy = await _getRequest( + "/faces/secured/db/dbJidelnicekOnDayView.jsp?day=${j.den.year}-${(j.den.month < 10) ? "0" + j.den.month.toString() : j.den.month}-${(j.den.day < 10) ? "0" + j.den.day.toString() : j.den.day}&terminal=false&rating=null&printer=false&keyboard=false"); // získat novou URL pro objednávání + if (novy == null) return j; + var lzeObjednat = + !(novy.contains("nelze zrušit") || novy.contains("nelze objednat")); + String orderUrl = ""; + if (lzeObjednat) { + // pokud lze objednat, nastavíme adresu pro objednání + orderUrl = RegExp(r"(?<=ajaxOrder\(this, ').+?(?=')") + .firstMatch(novy)! + .group(0)! + .replaceAll("amp;", ""); + } + + return Jidlo( + cislo: j.cislo, + nazev: j.nazev, + objednano: !j.objednano, + cena: j.cena, + lzeObjednat: j.lzeObjednat, + orderUrl: orderUrl, + den: j.den); // vrátit upravenou instanci } } diff --git a/lib/src/jidlo.dart b/lib/src/jidlo.dart index 48db3c9..0e680e8 100644 --- a/lib/src/jidlo.dart +++ b/lib/src/jidlo.dart @@ -14,11 +14,14 @@ class Jidlo { ///Lze objednat? bool lzeObjednat; + + DateTime den; final String? orderUrl; Jidlo( {required this.nazev, required this.objednano, required this.cislo, + required this.den, this.cena, required this.lzeObjednat, this.orderUrl}); diff --git a/pubspec.yaml b/pubspec.yaml index f48ddf2..11a0f14 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,6 +11,7 @@ environment: # path: ^1.8.0 dev_dependencies: + dotenv: ^3.0.0 lints: ^1.0.0 test: ^1.16.0 dependencies: diff --git a/test/canteenlib_test.dart b/test/canteenlib_test.dart index 4c73503..c697095 100644 --- a/test/canteenlib_test.dart +++ b/test/canteenlib_test.dart @@ -1,16 +1,20 @@ import 'package:canteenlib/canteenlib.dart'; import 'package:test/test.dart'; +import 'package:dotenv/dotenv.dart' show load, env; void main() { group('A group of tests', () { - Canteen c = Canteen("a"); + load(); + Canteen c = Canteen(env["ADDRESS"]!); setUp(() { - // Additional setup goes here. + c.login(env["USER"]!, env["PASS"]!); }); test('First Test', () { - expect(true, isTrue); + c.jidelnicekDen().then((t) { + expect(DateTime.now().day, t.den.day); + }); }); }); }