import 'dart:developer'; import 'dart:io'; //import 'package:dio/adapter.dart'; import 'package:dio/dio.dart'; import 'package:dio/io.dart'; import 'package:dio_cookie_manager/dio_cookie_manager.dart'; import 'package:cookie_jar/cookie_jar.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:hive_flutter/hive_flutter.dart'; // import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../api/dio_exceptions.dart'; import 'package:html/dom.dart' as dom; import 'package:intl/intl.dart'; import 'package:html/parser.dart'; // import 'package:path_provider/path_provider.dart'; import 'package:syncfusion_flutter_pdf/pdf.dart'; // import 'dio_logger.dart'; final crewlinkapiProvider = Provider((ref) { return CrewlinkApi(); }); class CrewlinkApi { static int maxDaysRoster = 59; static int maxDaysCrewlist = 30; // static final CrewlinkApi instance = CrewlinkApi._(); late Dio _dio; String realUrl = "https://crewlink.flytunisair.net/crewlink"; String get baseUrl => realUrl; Map get _headers => { "Host": Uri.parse(realUrl).host, "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:103.0) Gecko/20100101 Firefox/103.0", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8", "Accept-Language": "en-US,en;q=0.5", "Accept-Encoding": "gzip, deflate, br", "Origin": Uri.parse(realUrl).host, "Connection": "keep-alive", }; // CrewlinkApi._() { CrewlinkApi() { { _dio = Dio(); //ignore bad certificate _dio.httpClientAdapter = IOHttpClientAdapter( createHttpClient: () { // Don't trust any certificate just because their root cert is trusted. final HttpClient client = HttpClient(context: SecurityContext(withTrustedRoots: false)); // You can test the intermediate / root cert here. We just ignore it. client.badCertificateCallback = (cert, host, port) => true; return client; }, ); //default param _dio = _dio ..options.baseUrl = baseUrl ..options.followRedirects = false ..options.connectTimeout = const Duration(seconds: 5000) ..options.receiveTimeout = const Duration(seconds: 10000) ..options.responseType = ResponseType.plain ..options.validateStatus = (status) { return (status ?? 999) < 500; }; //addcookie manager _dio.interceptors.add(CookieManager(CookieJar())); //add logger // _dio.interceptors.add(DioLogger( // baseurl: false, // path: true, // statuscode: true, // reqdata: true, // resdata: true)); //error interceptor //add header interceptor _dio.interceptors.add(InterceptorsWrapper(onRequest: (options, handler) { options.headers.addAll(_headers); if (options.method == "POST") { options.headers .addAll({"Content-Type": "application/x-www-form-urlencoded"}); } return handler.next(options); //continue }, onResponse: (response, handler) { return handler.next(response); // continue }, onError: (DioException e, handler) { // Do something with response error log("crewlinkapi: onerror: ${e.type} ${e.message}"); return handler.resolve(Response( statusCode: e.response?.statusCode, statusMessage: DioExceptions.fromDioException(e).message, data: e.response?.data ?? "", headers: e.response?.headers, requestOptions: e.requestOptions)); })); } } Future login({String? username, String? password}) async { storedusername = username ?? storedusername; storedpassword = password ?? storedpassword; var res = await _dio.get( "/crewlink.jsp?crewlinkOperation=crewlinkForCrew&resetSession=Y", ); res = await _dio.post( '/clApp', data: { "crewlinkService": "crewlinkForCrew", "crewlinkOperation": "loadMainFrameSet", "crewlinkSourcePage": "spStartup", "crewlinkUserName": storedusername, "crewlinkPassword": storedpassword }, ); String? error; if (res.statusCode == 302) { error = "invalid username or password"; } else if (res.statusCode == 200) { error = null; } else { error = res.statusMessage; } return { 'error': error, 'data': {"logged": (res.statusCode == 200)}, 'meta': { "user": username, "pass": password, "logged": (res.statusCode == 200), "statuscode": res.statusCode } }; } Future rosterMinMax({bool loginfirst = false}) async { if (loginfirst) { var x = await login(); if ((x["data"]["logged"] ?? false) == false) return x; } var res = await _dio.get( "/clApp?crewlinkService=individualDutyPlan&crewlinkOperation=default&crewlinkSourcePage=spCrew", ); var mindate = (RegExp( r"(?<=minDateMinus1 = getDateFromFormat\(').+?(?=', 'yyyy-MM-dd'\);)", dotAll: true) .stringMatch(res.data ?? "")); var maxdate = (RegExp( r"(?<=maxDatePlus1 = getDateFromFormat\(').+?(?=', 'yyyy-MM-dd'\);)", dotAll: true) .stringMatch(res.data ?? "")); rostermin = mindate ?? ""; rostermax = maxdate ?? ""; String? error; if (res.statusCode == 302) { if (!loginfirst) return await rosterMinMax(loginfirst: true); } else if (res.statusCode == 200) { error = null; } else { error = res.statusMessage; } return { 'error': error, 'data': {"mindate": mindate, "maxdate": maxdate}, 'meta': { "user": storedusername, "pass": storedpassword, "statuscode": res.statusCode } }; } Future roster( {required String start, required String end, required String fileid, CancelToken? cancelToken, ProgressCallback? progressCallback, bool loginfirst = false}) async { if (loginfirst) { var x = await login(); if ((x["data"]["logged"] ?? false) == false) return x; } var res = await _dio.post("/clApp", data: { "crewlinkService": "individualDutyPlan", "crewlinkOperation": "makeReport", "buddyName": "", "beginDate": start, "endDate": end }); String? pdflink = (RegExp(r'(?<=file=\/crewlink)(.+\.pdf?)(?=")', dotAll: true) .stringMatch(res.data ?? "")); String? msg = parse(res.data).querySelector(".errMsg")?.text.trim(); //String fileid ="${(await getApplicationDocumentsDirectory()).path}/${DateFormat("yyyyMMddHHmm").format(DateTime.now())}_${storedusername}_${start}_$end.pdf"; // String fileid = // "${(await getApplicationDocumentsDirectory()).path}/ROSTER_${storedusername}_${start}_$end.pdf"; String? error; if (res.statusCode == 302) { if (!loginfirst) { return await roster( start: start, end: end, loginfirst: true, fileid: fileid, cancelToken: cancelToken, progressCallback: progressCallback); } else { return "invalid username or password"; } } else if (res.statusCode == 200 && pdflink != null) { error = null; await _dio.download(pdflink, fileid, cancelToken: cancelToken, onReceiveProgress: progressCallback); } else if (res.statusMessage != "") { error = res.statusMessage; } else { error = null; } return { 'error': error, 'data': { "pdflink": pdflink, "msg": msg, ...pdflink != null ? {"id": fileid} : {}, ...pdflink != null ? {"decoded": _decodeRoster(fileid)} : {} }, 'meta': { "user": storedusername, "pass": storedpassword, "statuscode": res.statusCode } }; } Future showCrewMinMax({bool loginfirst = false}) async { if (loginfirst) { var x = await login(); if ((x["data"]["logged"] ?? false) == false) return x; } var res = await _dio.get( "/clApp?crewlinkService=showCrew&crewlinkOperation=default&crewlinkSourcePage=spCrew"); var mindate = (RegExp( r"(?<=minDateMinus1 = getDateFromFormat\(').+?(?=', 'yyyy-MM-dd'\);)", dotAll: true) .stringMatch(res.data)); var maxdate = (RegExp( r"(?<=maxDatePlus1 = getDateFromFormat\(').+?(?=', 'yyyy-MM-dd'\);)", dotAll: true) .stringMatch(res.data)); showcrewmin = mindate ?? ""; showcrewmax = maxdate ?? ""; String? error; if (res.statusCode == 302) { if (!loginfirst) return await showCrewMinMax(loginfirst: true); } else if (res.statusCode == 200) { error = null; } else { error = res.statusMessage; } return { 'error': error, 'data': {"mindate": mindate, "maxdate": maxdate}, 'meta': { "user": storedusername, "pass": storedpassword, "statuscode": res.statusCode } }; } Future showCrew( {required String start, required String end, bool crewlist = false, String al = "", String fnum = "", String dep = "", String des = "", bool loginfirst = false}) async { //String cachekey = "|$start,$end,$al,$fnum,$dep,$des,$crewlist"; //start format ddMMMyy if (loginfirst) { var x = await login(); if ((x["data"]["logged"] ?? false) == false) return x; } var res = await showCrewMinMax(); if (res["error"] != null) return res; res = await _dio.post('/clApp', data: { "buddyName": "", "depDatePeriodBegin": start, "depDatePeriodEnd": end, "fnAirline": al, "fnNumber": fnum, "depAp": dep, "arrAp": des, "crewlinkService": "showCrew", "crewlinkOperation": "showCrew", "crewlinkSourcePage": "spCrew" }); var msg = parse(res.data).querySelector(".errMsg")?.text; List leglist = []; if (res.statusCode == 200 && (msg ?? "") == "") { res = await _dio .get("/clApp?crewlinkService=showCrew&crewlinkOperation=legList"); leglist = parse(res.data) .querySelectorAll("table tbody tr") .map((dom.Element tr) => (tr.children .map((td) => ((parse(td.innerHtml) .querySelector('[name="indexOfLeg"]') ?.attributes["value"] .toString() .trim()) ?? td.text.trim())) .toList())) .toList(); // treat cabin: cockpit: DH Flight: leglist = leglist.map((x) { x[7] = "${(RegExp(r"Cockpit: (.*?)(DH Flight: |Cabin: |$)").firstMatch(x[7])?.group(1) ?? "").trim()}|${(RegExp(r"Cabin: (.*?)(DH Flight: |$)").firstMatch(x[7])?.group(1) ?? "").trim()}|${(RegExp(r"DH Flight: (.*?)$").firstMatch(x[7])?.group(1) ?? "").trim()}"; return x; }).toList(); if (crewlist) { var out = []; for (var leg in leglist) { await _dio.post("/clApp", data: { "crewlinkService": "showCrew", "crewlinkOperation": "clearCrewList", "crewlinkSourcePage": "" }); var res = await _dio.post("/clApp", data: { "crewlinkService": "showCrew", "crewlinkOperation": "exploreFlight", "crewlinkSourcePage": "", "indexOfLeg": leg[0].toString() }); // log("flt: ${res.data}"); var flt = ((parse(res.data).querySelector("table tr th[colspan]")?.text) ?? "\n\n\n\n\n\n\n") .split("\n") .skip(1) .map((e) => e.trim()) .toList(); // ..removeAt(6); var crew = []; if ((parse(res.data).querySelector(".errMsg")?.text != null)) { crew = []; } else { crew = (parse(res.data) .querySelectorAll("table tr") .map((e) => e.text.split("\n").map((e) => e.trim()).skip(1).toList() ..removeAt(6)) .skip(2)) .toList(); } // log("crew: $crew"); out.add([ leg[0], flt[0], flt[1], flt[2], flt[3], flt[4], flt[5], leg[7], crew ]); //log((flt).toString()); } leglist = out; } } String? error; if (res.statusCode == 302) { if (!loginfirst) { return await showCrew( start: start, end: end, crewlist: crewlist, al: al, fnum: fnum, dep: dep, des: dep, loginfirst: true); } } else if (res.statusCode == 200) { error = null; } else { error = res.statusMessage; } return { 'error': error, 'data': { "leglist": leglist, "msg": msg, }, 'meta': { "user": storedusername, "pass": storedpassword, "statuscode": res.statusCode } }; } Future userInfo({bool loginfirst = false}) async { if (loginfirst) { var x = await login(); if ((x["data"]["logged"] ?? false) == false) return x; } var res = await _dio.get("/HeaderPage.jsp"); var data = (RegExp( r'(?<=\)\s*(.*?)<\/b>\s*(.+?)\s*(.*?)\s(.{3})\s(.{3}).*?(?=\<\/td\>)', dotAll: true) .firstMatch(res.data)); bool novalidsession = (res.data).indexOf("unknown user") > -1; String? error; if (novalidsession) { if (!loginfirst) return await userInfo(loginfirst: true); } else if (res.statusCode == 200) { error = null; } else { error = res.statusMessage; } return { 'error': error, 'data': { "tlc": data?.group(1), "name": data?.group(2), "qualif": (data?.group(3) ?? "").split(","), "base": data?.group(4), "sit": data?.group(5), }, 'meta': {"user": storedusername, "pass": storedpassword} }; } Future changePass({required String newpass, bool loginfirst = false}) async { if (loginfirst) { var x = await login(); if ((x["data"]["logged"] ?? false) == false) return x; } var res = await _dio.get( "/clApp?crewlinkService=changePassword&crewlinkOperation=default&crewlinkSourcePage=spCrew"); res = await _dio.post("/clApp", data: { "oldPassword": storedpassword, "newPassword1": newpass, "newPassword2": newpass, "crewlinkService": "changePassword", "crewlinkOperation": "changePassword", "change": "Change" }); var infomsg = parse(res.data).querySelector(".infoMsg")?.text; var msg = parse(res.data).querySelector(".errMsg")?.text; var errormsg = parse(res.data).querySelector("h1.error")?.text; String? error; if (res.statusCode == 302) { if (!loginfirst) { return await changePass(newpass: newpass, loginfirst: true); } } else if (res.statusCode == 200) { error = null; } else { error = res.statusMessage; } return { 'error': error, 'data': {"info": infomsg, "msg": msg, "error": errormsg}, 'meta': { "user": storedusername, "pass": storedpassword, "statuscode": res.statusCode } }; } Future logout() async { var res = await _dio.get("/closeSession.jsp"); storedusername = ""; storedpassword = ""; String? error; if (res.statusCode == 200) { error = null; } else { error = res.statusMessage; } log(res.toString()); return { 'error': error, 'data': {}, 'meta': { "user": storedusername, "pass": storedpassword, "statuscode": res.statusCode } }; } Future notif( {bool download = false, required String fileid, CancelToken? cancelToken, ProgressCallback? progressCallback, bool loginfirst = false}) async { if (loginfirst) { var x = await login(); if ((x["data"]["logged"] ?? false) == false) return x; } var res = await _dio.get( "/clApp?crewlinkService=notification&crewlinkOperation=default&crewlinkSourcePage=spCrew"); String? pdflink = (RegExp( r"(?<='js\/pdfjs\/web\/viewer\.html\?file=).+.pdf?(?=')", dotAll: true) .stringMatch(res.data)); String? msg = parse(res.data).querySelector(".msgDetails")?.text; // String fileid = // "${(await getApplicationDocumentsDirectory()).path}/${DateFormat("yyyyMMddHHmm").format(DateTime.now())}_${storedusername}_notif.pdf"; String? error; if (res.statusCode == 302) { if (!loginfirst) { return await notif( download: download, fileid: fileid, cancelToken: cancelToken, progressCallback: progressCallback, loginfirst: true); } } else if (res.statusCode == 200) { error = null; if (download && pdflink != null) { await _dio.download( "${Uri.parse(realUrl).scheme}://${Uri.parse(realUrl).host}$pdflink", fileid, ); } } else { error = res.statusMessage; } return { 'error': error, 'data': { "pdflink": pdflink, "msg": msg, ...pdflink != null ? {"id": fileid} : {} }, 'meta': { "user": storedusername, "pass": storedpassword, "statuscode": res.statusCode } }; } Future confirmNotif({bool loginfirst = false}) async { if (loginfirst) { var x = await login(); if ((x["data"]["logged"] ?? false) == false) return x; } var res = await _dio.post("/clApp", data: { 'crewlinkService': 'notification', 'crewlinkOperation': 'confirmNotif', }); var msg = parse(res.data).querySelector(".errMsg")?.text; var errormsg = parse(res.data).querySelector("h1.error")?.text; String? error; if (res.statusCode == 302) { if (!loginfirst) return await confirmNotif(loginfirst: true); } else if (res.statusCode == 200) { error = null; } else { error = res.statusMessage; } // print("=============="); // print({ // 'error': error, // 'data': {"msg": msg, "error": errormsg}, // 'meta': { // "user": storedusername, // "pass": storedpassword, // "statuscode": res.statusCode // } // }); // print("=============="); return { 'error': error, 'data': {"msg": msg, "error": errormsg}, 'meta': { "user": storedusername, "pass": storedpassword, "statuscode": res.statusCode } }; } bool credSaved() { return (Hive.box("profile").get("crewlink_user", defaultValue: "") != "") && (Hive.box("profile").get("crewlink_pass", defaultValue: "") != ""); } String storedusername = ""; String storedpassword = ""; bool get logged => storedusername != "" && storedpassword != ""; String rostermin = ""; String rostermax = ""; String showcrewmin = ""; String showcrewmax = ""; Map decodeRoster(String pdffile) { return _decodeRoster(pdffile); } Map _decodeRoster(String pdffile) { // try { const weekdays = "Mon|Tue|Wed|Thu|Fri|Sat|Sun"; const monthday = '[0-3]\\d'; const month = "Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec"; const date = "$monthday($month)[0-5]\\d"; const station = "[A-Z]{3}"; const al = "[A-Z]{2}"; const fnum = "\\d{1,4}[A-Z^R]?"; const actype = "\\d{2}[A-Z0-9]"; const label = "(OFF|[A-Z]{2,}|[A-Z]+([0-9?]))"; const time = "[0-2]\\d[0-5]\\d"; PdfDocument document = PdfDocument(inputBytes: File(pdffile).readAsBytesSync()); var text = PdfTextExtractor(document).extractText(layoutText: true); document.dispose(); text = text.replaceAll("\r\n", "#"); // var text = ros.replaceAll("\n", "#"); Map info = {}; var d = RegExp(r'for(\d+)(.+?),(.+?)#').firstMatch(text); info["tlc"] = d?.group(1); info["lname"] = d?.group(2); info["fname"] = d?.group(3); d = RegExp( r'NetLine/Crew\((.+?)\)printedbyCREWLINK(.{7})(\d\d:\d\d)Page\d+#') .firstMatch(text); info["date"] = d?.group(2); info["time"] = d?.group(3); d = RegExp(r'Period:(.{7})-(.{7})#').firstMatch(text); info["startdate"] = d?.group(1); info["enddate"] = d?.group(2); String header = RegExp("(?<=Period:$date-$date#)(.*?)(?=dateHdutyRdeparrACinfo)") .stringMatch(text) ?? ""; String body = text.replaceAll( RegExp( "Individualdutyplan(.+?)${RegExp.escape(header)}(dateHdutyRdeparrACinfo#dateHdutyRdeparrACinfo#dateHdutyRdeparrACinfo#)?"), ""); // String footer = RegExp("(Flighttime.*)\$").stringMatch(body) ?? ""; String onlyprog = RegExp("^(.*?)(?=Flighttime)").stringMatch(body) ?? ""; onlyprog = onlyprog.replaceAllMapped( RegExp("(?:$weekdays)$monthday.+?(?=(($weekdays)$monthday)|\$)"), (m) => "${m.group(0)}\n"); const ground = "(?