Auto stash before merge of "main" and "origin/main"
This commit is contained in:
parent
55d36d9f33
commit
846e377266
11 changed files with 369 additions and 1 deletions
10
.gitignore
vendored
Normal file
10
.gitignore
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
# Files and directories created by pub.
|
||||||
|
.dart_tool/
|
||||||
|
.packages
|
||||||
|
|
||||||
|
# Conventional directory for build outputs.
|
||||||
|
build/
|
||||||
|
|
||||||
|
# Omit committing pubspec.lock for library packages; see
|
||||||
|
# https://dart.dev/guides/libraries/private-files#pubspeclock.
|
||||||
|
pubspec.lock
|
4
CHANGELOG.md
Normal file
4
CHANGELOG.md
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
## 0.1.0-alpha
|
||||||
|
|
||||||
|
- První verze, funkčnost omezená
|
||||||
|
- Metody pro zobrazení jídelníčku
|
12
COMPATIBILITY.md
Normal file
12
COMPATIBILITY.md
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
# Kompatibilita knihovny s instancemi služby iCanteen
|
||||||
|
V následující tabulce naleznete instance iCanteen, které byly testovány pro jejich funkčnost s touto knihovnou.
|
||||||
|
|
||||||
|
- ❌ - nefunkční nebo netestováno
|
||||||
|
- ✅ - plně funkční nebo pouze s malými chybami
|
||||||
|
- ❓ - částečně funkční
|
||||||
|
|
||||||
|
| Provozovatel | Verze iCanteen | Funkční | Verze knihovny |
|
||||||
|
|:--------------:|------------------|---------|----------------|
|
||||||
|
| SŠTE Brno | iCanteen 2.18.19 | ❓ | 0.1.0-alpha |
|
||||||
|
| SPŠ Třebíč | iCanteen 2.10.25 | ❓ | 0.1.0-alpha |
|
||||||
|
| Výsledky testů | nahlašujte | [zde](https://github.com/hernikplays/canteenlib/issues/new) | |
|
11
README.md
11
README.md
|
@ -1 +1,10 @@
|
||||||
canteenlib
|
## O knihovně
|
||||||
|
Experimentální **neoficiální** webscrape knihovna pro komunikaci se systémem [iCanteen](https://www.z-ware.cz/internetove-objednavky)
|
||||||
|
|
||||||
|
## Funkční funkce(*)
|
||||||
|
- získání jídelníčku na aktuální den (s cenami)
|
||||||
|
|
||||||
|
*\* Knihovna nemusí fungovat na všech instancích systému iCanteen, proto žádám každého, kdo může a je uživatelem iCanteen, aby otestoval funkčnost této knihovny a případné problémy [nahlásil](https://github.com/hernikplays/canteenlib/issues)*
|
||||||
|
|
||||||
|
### Otestované instance iCanteen
|
||||||
|
[zde]()
|
30
analysis_options.yaml
Normal file
30
analysis_options.yaml
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
# This file configures the static analysis results for your project (errors,
|
||||||
|
# warnings, and lints).
|
||||||
|
#
|
||||||
|
# This enables the 'recommended' set of lints from `package:lints`.
|
||||||
|
# This set helps identify many issues that may lead to problems when running
|
||||||
|
# or consuming Dart code, and enforces writing Dart using a single, idiomatic
|
||||||
|
# style and format.
|
||||||
|
#
|
||||||
|
# If you want a smaller set of lints you can change this to specify
|
||||||
|
# 'package:lints/core.yaml'. These are just the most critical lints
|
||||||
|
# (the recommended set includes the core lints).
|
||||||
|
# The core lints are also what is used by pub.dev for scoring packages.
|
||||||
|
|
||||||
|
include: package:lints/recommended.yaml
|
||||||
|
|
||||||
|
# Uncomment the following section to specify additional rules.
|
||||||
|
|
||||||
|
# linter:
|
||||||
|
# rules:
|
||||||
|
# - camel_case_types
|
||||||
|
|
||||||
|
# analyzer:
|
||||||
|
# exclude:
|
||||||
|
# - path/to/excluded/files/**
|
||||||
|
|
||||||
|
# For more information about the core and recommended set of lints, see
|
||||||
|
# https://dart.dev/go/core-lints
|
||||||
|
|
||||||
|
# For additional information about configuring this file, see
|
||||||
|
# https://dart.dev/guides/language/analysis-options
|
10
example/canteenlib_example.dart
Normal file
10
example/canteenlib_example.dart
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import 'package:canteenlib/canteenlib.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
var canteen = Canteen("http://icanteen.vasedomena.neco");
|
||||||
|
canteen.login("user", "password").then((_) {
|
||||||
|
canteen.jidelnicekDen().then((jidelnicek) {
|
||||||
|
print(jidelnicek.jidla[0].hlavni);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
5
lib/canteenlib.dart
Normal file
5
lib/canteenlib.dart
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
/// Hlavní knihovna
|
||||||
|
library canteenlib;
|
||||||
|
|
||||||
|
export 'src/canteen.dart';
|
||||||
|
export 'src/jidlo.dart';
|
220
lib/src/canteen.dart
Normal file
220
lib/src/canteen.dart
Normal file
|
@ -0,0 +1,220 @@
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
|
||||||
|
import 'jidlo.dart';
|
||||||
|
|
||||||
|
/*
|
||||||
|
(C) 2022 Matyáš Caras and contributors
|
||||||
|
This library is released under the GNU LGPLv3 license.
|
||||||
|
If you have not received the license with this library, you can view it here http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||||
|
*/
|
||||||
|
class Canteen {
|
||||||
|
final String url;
|
||||||
|
Map<String, String> cookies = {"JSESSIONID": "", "XSRF-TOKEN": ""};
|
||||||
|
Canteen(this.url);
|
||||||
|
|
||||||
|
Future<void> getFirstSession() async {
|
||||||
|
var res = await http.get(Uri.parse(url));
|
||||||
|
_parseCookies(res.headers['set-cookie']!);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Převede cookie řetězec z požadavku do mapy
|
||||||
|
void _parseCookies(String cookieString) {
|
||||||
|
Map<String, String> cookies = this.cookies;
|
||||||
|
var regCookie = RegExp(r'([A-Z\-]+=.+?(?=;))|(remember-me=.+?)(?=;)')
|
||||||
|
.allMatches(cookieString)
|
||||||
|
.toList();
|
||||||
|
for (var cook in regCookie) {
|
||||||
|
var c = cook.group(0).toString().split("=");
|
||||||
|
cookies[c[0]] = c[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Přihlášení do iCanteen
|
||||||
|
///
|
||||||
|
/// `user` - uživatelské jméno
|
||||||
|
/// `password` - heslo
|
||||||
|
///
|
||||||
|
/// TODO: Házet chyby
|
||||||
|
Future<void> login(String user, String password) async {
|
||||||
|
if (cookies["JSESSIONID"] == "" || cookies["XSRF-TOKEN"] == "") {
|
||||||
|
await getFirstSession();
|
||||||
|
}
|
||||||
|
var res =
|
||||||
|
await http.post(Uri.parse(url + "/j_spring_security_check"), headers: {
|
||||||
|
"Cookie": "JSESSIONID=" +
|
||||||
|
cookies["JSESSIONID"]! +
|
||||||
|
"; " +
|
||||||
|
"XSRF-TOKEN=" +
|
||||||
|
cookies["XSRF-TOKEN"]! +
|
||||||
|
";",
|
||||||
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
|
}, body: {
|
||||||
|
"j_username": user,
|
||||||
|
"j_password": password,
|
||||||
|
"terminal": "false",
|
||||||
|
"_csrf": cookies["XSRF-TOKEN"],
|
||||||
|
"_spring_security_remember_me": "on",
|
||||||
|
"targetUrl":
|
||||||
|
"/faces/secured/main.jsp?terminal=false&status=true&printer=&keyboard="
|
||||||
|
});
|
||||||
|
_parseCookies(res.headers['set-cookie']!);
|
||||||
|
if (res.statusCode != 302) {
|
||||||
|
print(res.body);
|
||||||
|
print("ERROR");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Builder pro GET request
|
||||||
|
Future<String?> _getRequest(String path) async {
|
||||||
|
var r = await http.get(Uri.parse(url + path), headers: {
|
||||||
|
"Cookie": "JSESSIONID=" +
|
||||||
|
cookies["JSESSIONID"]! +
|
||||||
|
"; " +
|
||||||
|
"XSRF-TOKEN=" +
|
||||||
|
cookies["XSRF-TOKEN"]! +
|
||||||
|
(cookies.containsKey("remember-me")
|
||||||
|
? "; " + cookies["remember-me"]! + ";"
|
||||||
|
: ";"),
|
||||||
|
});
|
||||||
|
if (r.headers.containsKey("set-cookie")) {
|
||||||
|
_parseCookies(r.headers["set-cookie"]!);
|
||||||
|
}
|
||||||
|
return r.body;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Získá jídelníček bez cen
|
||||||
|
/// **nevrací** ceny, ale umožňuje získat jídelníček bez přihlášení
|
||||||
|
Future<List<Jidelnicek>> ziskejJidelnicek() async {
|
||||||
|
var res = await _getRequest("/");
|
||||||
|
var reg = RegExp(
|
||||||
|
r'((?=<div class="jidelnicekDen">).+?(?=<div class="jidelnicekDen">))|((?=<div class="jidelnicekDen">).*<\/span>)',
|
||||||
|
dotAll: true)
|
||||||
|
.allMatches(res!)
|
||||||
|
.toList();
|
||||||
|
List<Jidelnicek> jidelnicek = [];
|
||||||
|
for (var t in reg) {
|
||||||
|
// projedeme každý den individuálně
|
||||||
|
var j = t
|
||||||
|
.group(0)
|
||||||
|
.toString() /*.replaceAll(RegExp(r'( )+|([^>a-z]\n)'),
|
||||||
|
'')*/
|
||||||
|
; // převedeme text na něco přehlednějšího
|
||||||
|
var den = DateTime.parse(RegExp(r'(?<=day-).+?(?=")', dotAll: true)
|
||||||
|
.firstMatch(j)!
|
||||||
|
.group(0)
|
||||||
|
.toString());
|
||||||
|
var jidlaDenne = RegExp(
|
||||||
|
r'(?=<div class="container">).+?<\/div>.+?(?=<\/div>)',
|
||||||
|
dotAll: true)
|
||||||
|
.allMatches(j)
|
||||||
|
.toList(); // získáme jednotlivá jídla pro den / VERZE 2.18
|
||||||
|
if (jidlaDenne.isEmpty) {
|
||||||
|
jidlaDenne = RegExp(
|
||||||
|
r'(?=<div style="padding: 2 0 2 20">).+?(?=<\/div>)',
|
||||||
|
dotAll: true)
|
||||||
|
.allMatches(j)
|
||||||
|
.toList(); // získáme jednotlivá jídla pro den / VERZE 2.10
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Jidlo> jidla = [];
|
||||||
|
for (var jidloNaDen in jidlaDenne) {
|
||||||
|
// projedeme vsechna jidla
|
||||||
|
var s = jidloNaDen.group(0)!.replaceAll(
|
||||||
|
RegExp(
|
||||||
|
r'[a-zA-ZěščřžýáíéÉÍÁÝŽŘČŠĚŤŇťň.,:] [a-zA-ZěščřžýáíéÉÍÁÝŽŘČŠĚŤŇťň.,:]'),
|
||||||
|
''); // odstraní dvojté mezery mezi písmeny
|
||||||
|
var cislo = RegExp(r'(?<=<span style="color: #1b75bb;">).+?(?=<)')
|
||||||
|
.firstMatch(s); // název výdejny / verze 2.18
|
||||||
|
cislo ??= RegExp(
|
||||||
|
r'(?<=<span class="smallBoldTitle" style="color: #1b75bb;">).+?(?=<)')
|
||||||
|
.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)
|
||||||
|
.firstMatch(s)!
|
||||||
|
.group(1)
|
||||||
|
.toString(); // Jídlo
|
||||||
|
jidla.add(Jidlo(
|
||||||
|
nazev: hlavni,
|
||||||
|
objednano: false,
|
||||||
|
cislo: cislo!.group(0).toString(),
|
||||||
|
lzeObjednat: false));
|
||||||
|
}
|
||||||
|
jidelnicek.add(Jidelnicek(den, jidla));
|
||||||
|
}
|
||||||
|
return jidelnicek;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Získá jídlo pro daný den
|
||||||
|
/// Vyžaduje přihlášení pomocí [login]
|
||||||
|
Future<Jidelnicek> jidelnicekDen() async {
|
||||||
|
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!)!
|
||||||
|
.group(0)
|
||||||
|
.toString());
|
||||||
|
var jidla = <Jidlo>[];
|
||||||
|
var jidelnicek =
|
||||||
|
RegExp(r'(?<=<div class="jidWrapLeft">).+?(?=<br>)', dotAll: true)
|
||||||
|
.allMatches(res)
|
||||||
|
.toList();
|
||||||
|
for (var obed in jidelnicek) {
|
||||||
|
// formátování do třídy
|
||||||
|
var o = obed
|
||||||
|
.group(0)
|
||||||
|
.toString()
|
||||||
|
.replaceAll(RegExp(r'( )+|([^>a-z]\n)'), '');
|
||||||
|
var objednano = o.contains("Máte objednáno");
|
||||||
|
var lzeObjednat =
|
||||||
|
!(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);
|
||||||
|
var cena =
|
||||||
|
double.parse(cenaMatch!.group(0).toString().replaceAll(",", "."));
|
||||||
|
var jidlaProDen = RegExp(r'(?<=Polévka: ).+')
|
||||||
|
.firstMatch(o)!
|
||||||
|
.group(0)
|
||||||
|
.toString()
|
||||||
|
.split(" / ");
|
||||||
|
var vydejna = RegExp(
|
||||||
|
r'(?<=<span class="smallBoldTitle" style="color: #1b75bb;">).+?(?=<)')
|
||||||
|
.firstMatch(o)!
|
||||||
|
.group(0)
|
||||||
|
.toString();
|
||||||
|
String? orderUrl;
|
||||||
|
if (lzeObjednat) {
|
||||||
|
// pokud lze objednat, nastavíme adresu pro objednání
|
||||||
|
orderUrl = RegExp(r"(?<=ajaxOrder\(this, ').+?(?=')")
|
||||||
|
.firstMatch(o)!
|
||||||
|
.group(0)!
|
||||||
|
.replaceAll("amp;", "");
|
||||||
|
}
|
||||||
|
jidla.add(Jidlo(
|
||||||
|
nazev: jidlaProDen[1]
|
||||||
|
.replaceAll(r' (?=[^a-zA-ZěščřžýáíéĚŠČŘŽÝÁÍÉŤŇťň])', ''),
|
||||||
|
objednano: objednano,
|
||||||
|
cislo: vydejna,
|
||||||
|
lzeObjednat: lzeObjednat,
|
||||||
|
cena: cena,
|
||||||
|
orderUrl: orderUrl));
|
||||||
|
// KONEC formátování do třídy
|
||||||
|
}
|
||||||
|
|
||||||
|
return Jidelnicek(den, jidla);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> objednat(Jidlo j) async {
|
||||||
|
if (!j.lzeObjednat || j.orderUrl == null || j.orderUrl!.isEmpty) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var res = await _getRequest(j.orderUrl!);
|
||||||
|
print(res);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
35
lib/src/jidlo.dart
Normal file
35
lib/src/jidlo.dart
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
/// Reprezentuje jedno jídlo z jídelníčku
|
||||||
|
class Jidlo {
|
||||||
|
/// Název jídla
|
||||||
|
String nazev;
|
||||||
|
|
||||||
|
/// Objednal si uživatel toto jídlo?
|
||||||
|
bool objednano;
|
||||||
|
|
||||||
|
/// Název výdejny
|
||||||
|
String cislo;
|
||||||
|
|
||||||
|
/// Cena
|
||||||
|
double? cena;
|
||||||
|
|
||||||
|
///Lze objednat?
|
||||||
|
bool lzeObjednat;
|
||||||
|
final String? orderUrl;
|
||||||
|
Jidlo(
|
||||||
|
{required this.nazev,
|
||||||
|
required this.objednano,
|
||||||
|
required this.cislo,
|
||||||
|
this.cena,
|
||||||
|
required this.lzeObjednat,
|
||||||
|
this.orderUrl});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reprezentuje jídelníček pro jeden dan
|
||||||
|
class Jidelnicek {
|
||||||
|
/// Den, pro který je jídelníček zveřejněn
|
||||||
|
DateTime den;
|
||||||
|
|
||||||
|
/// Seznam jídel
|
||||||
|
List<Jidlo> jidla;
|
||||||
|
Jidelnicek(this.den, this.jidla);
|
||||||
|
}
|
17
pubspec.yaml
Normal file
17
pubspec.yaml
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
name: canteenlib
|
||||||
|
description: Knihovna pro komunikaci se stravovacím systémem iCanteen
|
||||||
|
version: 0.1.0-alpha
|
||||||
|
# homepage: https://www.example.com
|
||||||
|
|
||||||
|
environment:
|
||||||
|
sdk: '>=2.16.1 <3.0.0'
|
||||||
|
|
||||||
|
|
||||||
|
# dependencies:
|
||||||
|
# path: ^1.8.0
|
||||||
|
|
||||||
|
dev_dependencies:
|
||||||
|
lints: ^1.0.0
|
||||||
|
test: ^1.16.0
|
||||||
|
dependencies:
|
||||||
|
http: ^0.13.4
|
16
test/canteenlib_test.dart
Normal file
16
test/canteenlib_test.dart
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import 'package:canteenlib/canteenlib.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('A group of tests', () {
|
||||||
|
final awesome = Awesome();
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
// Additional setup goes here.
|
||||||
|
});
|
||||||
|
|
||||||
|
test('First Test', () {
|
||||||
|
expect(awesome.isAwesome, isTrue);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
Reference in a new issue