crewlink_api.dart 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825
  1. import 'dart:developer';
  2. import 'dart:io';
  3. //import 'package:dio/adapter.dart';
  4. import 'package:dio/dio.dart';
  5. import 'package:dio/io.dart';
  6. import 'package:dio_cookie_manager/dio_cookie_manager.dart';
  7. import 'package:cookie_jar/cookie_jar.dart';
  8. import 'package:flutter_riverpod/flutter_riverpod.dart';
  9. import 'package:hive_flutter/hive_flutter.dart';
  10. // import 'package:flutter_riverpod/flutter_riverpod.dart';
  11. import '../api/dio_exceptions.dart';
  12. import 'package:html/dom.dart' as dom;
  13. import 'package:intl/intl.dart';
  14. import 'package:html/parser.dart';
  15. // import 'package:path_provider/path_provider.dart';
  16. import 'package:syncfusion_flutter_pdf/pdf.dart';
  17. // import 'dio_logger.dart';
  18. final crewlinkapiProvider = Provider<CrewlinkApi>((ref) {
  19. return CrewlinkApi();
  20. });
  21. class CrewlinkApi {
  22. static int maxDaysRoster = 59;
  23. static int maxDaysCrewlist = 30;
  24. // static final CrewlinkApi instance = CrewlinkApi._();
  25. late Dio _dio;
  26. String realUrl = "https://crewlink.flytunisair.net/crewlink";
  27. String get baseUrl => realUrl;
  28. Map<String, dynamic> get _headers => {
  29. "Host": Uri.parse(realUrl).host,
  30. "User-Agent":
  31. "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:103.0) Gecko/20100101 Firefox/103.0",
  32. "Accept":
  33. "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
  34. "Accept-Language": "en-US,en;q=0.5",
  35. "Accept-Encoding": "gzip, deflate, br",
  36. "Origin": Uri.parse(realUrl).host,
  37. "Connection": "keep-alive",
  38. };
  39. // CrewlinkApi._() {
  40. CrewlinkApi() {
  41. {
  42. _dio = Dio();
  43. //ignore bad certificate
  44. _dio.httpClientAdapter = IOHttpClientAdapter(
  45. createHttpClient: () {
  46. // Don't trust any certificate just because their root cert is trusted.
  47. final HttpClient client =
  48. HttpClient(context: SecurityContext(withTrustedRoots: false));
  49. // You can test the intermediate / root cert here. We just ignore it.
  50. client.badCertificateCallback = (cert, host, port) => true;
  51. return client;
  52. },
  53. );
  54. //default param
  55. _dio = _dio
  56. ..options.baseUrl = baseUrl
  57. ..options.followRedirects = false
  58. ..options.connectTimeout = const Duration(seconds: 5000)
  59. ..options.receiveTimeout = const Duration(seconds: 10000)
  60. ..options.responseType = ResponseType.plain
  61. ..options.validateStatus = (status) {
  62. return (status ?? 999) < 500;
  63. };
  64. //addcookie manager
  65. _dio.interceptors.add(CookieManager(CookieJar()));
  66. //add logger
  67. // _dio.interceptors.add(DioLogger(
  68. // baseurl: false,
  69. // path: true,
  70. // statuscode: true,
  71. // reqdata: true,
  72. // resdata: true));
  73. //error interceptor
  74. //add header interceptor
  75. _dio.interceptors.add(InterceptorsWrapper(onRequest: (options, handler) {
  76. options.headers.addAll(_headers);
  77. if (options.method == "POST") {
  78. options.headers
  79. .addAll({"Content-Type": "application/x-www-form-urlencoded"});
  80. }
  81. return handler.next(options); //continue
  82. }, onResponse: (response, handler) {
  83. return handler.next(response); // continue
  84. }, onError: (DioException e, handler) {
  85. // Do something with response error
  86. log("crewlinkapi: onerror: ${e.type} ${e.message}");
  87. return handler.resolve(Response(
  88. statusCode: e.response?.statusCode,
  89. statusMessage: DioExceptions.fromDioException(e).message,
  90. data: e.response?.data ?? "",
  91. headers: e.response?.headers,
  92. requestOptions: e.requestOptions));
  93. }));
  94. }
  95. }
  96. Future login({String? username, String? password}) async {
  97. storedusername = username ?? storedusername;
  98. storedpassword = password ?? storedpassword;
  99. var res = await _dio.get(
  100. "/crewlink.jsp?crewlinkOperation=crewlinkForCrew&resetSession=Y",
  101. );
  102. res = await _dio.post(
  103. '/clApp',
  104. data: {
  105. "crewlinkService": "crewlinkForCrew",
  106. "crewlinkOperation": "loadMainFrameSet",
  107. "crewlinkSourcePage": "spStartup",
  108. "crewlinkUserName": storedusername,
  109. "crewlinkPassword": storedpassword
  110. },
  111. );
  112. String? error;
  113. if (res.statusCode == 302) {
  114. error = "invalid username or password";
  115. } else if (res.statusCode == 200) {
  116. error = null;
  117. } else {
  118. error = res.statusMessage;
  119. }
  120. return {
  121. 'error': error,
  122. 'data': {"logged": (res.statusCode == 200)},
  123. 'meta': {
  124. "user": username,
  125. "pass": password,
  126. "logged": (res.statusCode == 200),
  127. "statuscode": res.statusCode
  128. }
  129. };
  130. }
  131. Future rosterMinMax({bool loginfirst = false}) async {
  132. if (loginfirst) {
  133. var x = await login();
  134. if ((x["data"]["logged"] ?? false) == false) return x;
  135. }
  136. var res = await _dio.get(
  137. "/clApp?crewlinkService=individualDutyPlan&crewlinkOperation=default&crewlinkSourcePage=spCrew",
  138. );
  139. var mindate = (RegExp(
  140. r"(?<=minDateMinus1 = getDateFromFormat\(').+?(?=', 'yyyy-MM-dd'\);)",
  141. dotAll: true)
  142. .stringMatch(res.data ?? ""));
  143. var maxdate = (RegExp(
  144. r"(?<=maxDatePlus1 = getDateFromFormat\(').+?(?=', 'yyyy-MM-dd'\);)",
  145. dotAll: true)
  146. .stringMatch(res.data ?? ""));
  147. rostermin = mindate ?? "";
  148. rostermax = maxdate ?? "";
  149. String? error;
  150. if (res.statusCode == 302) {
  151. if (!loginfirst) return await rosterMinMax(loginfirst: true);
  152. } else if (res.statusCode == 200) {
  153. error = null;
  154. } else {
  155. error = res.statusMessage;
  156. }
  157. return {
  158. 'error': error,
  159. 'data': {"mindate": mindate, "maxdate": maxdate},
  160. 'meta': {
  161. "user": storedusername,
  162. "pass": storedpassword,
  163. "statuscode": res.statusCode
  164. }
  165. };
  166. }
  167. Future roster(
  168. {required String start,
  169. required String end,
  170. required String fileid,
  171. CancelToken? cancelToken,
  172. ProgressCallback? progressCallback,
  173. bool loginfirst = false}) async {
  174. if (loginfirst) {
  175. var x = await login();
  176. if ((x["data"]["logged"] ?? false) == false) return x;
  177. }
  178. var res = await _dio.post("/clApp", data: {
  179. "crewlinkService": "individualDutyPlan",
  180. "crewlinkOperation": "makeReport",
  181. "buddyName": "",
  182. "beginDate": start,
  183. "endDate": end
  184. });
  185. String? pdflink =
  186. (RegExp(r'(?<=file=\/crewlink)(.+\.pdf?)(?=")', dotAll: true)
  187. .stringMatch(res.data ?? ""));
  188. String? msg = parse(res.data).querySelector(".errMsg")?.text.trim();
  189. //String fileid ="${(await getApplicationDocumentsDirectory()).path}/${DateFormat("yyyyMMddHHmm").format(DateTime.now())}_${storedusername}_${start}_$end.pdf";
  190. // String fileid =
  191. // "${(await getApplicationDocumentsDirectory()).path}/ROSTER_${storedusername}_${start}_$end.pdf";
  192. String? error;
  193. if (res.statusCode == 302) {
  194. if (!loginfirst) {
  195. return await roster(
  196. start: start,
  197. end: end,
  198. loginfirst: true,
  199. fileid: fileid,
  200. cancelToken: cancelToken,
  201. progressCallback: progressCallback);
  202. } else {
  203. return "invalid username or password";
  204. }
  205. } else if (res.statusCode == 200 && pdflink != null) {
  206. error = null;
  207. await _dio.download(pdflink, fileid,
  208. cancelToken: cancelToken, onReceiveProgress: progressCallback);
  209. } else if (res.statusMessage != "") {
  210. error = res.statusMessage;
  211. } else {
  212. error = null;
  213. }
  214. return {
  215. 'error': error,
  216. 'data': {
  217. "pdflink": pdflink,
  218. "msg": msg,
  219. ...pdflink != null ? {"id": fileid} : {},
  220. ...pdflink != null ? {"decoded": _decodeRoster(fileid)} : {}
  221. },
  222. 'meta': {
  223. "user": storedusername,
  224. "pass": storedpassword,
  225. "statuscode": res.statusCode
  226. }
  227. };
  228. }
  229. Future showCrewMinMax({bool loginfirst = false}) async {
  230. if (loginfirst) {
  231. var x = await login();
  232. if ((x["data"]["logged"] ?? false) == false) return x;
  233. }
  234. var res = await _dio.get(
  235. "/clApp?crewlinkService=showCrew&crewlinkOperation=default&crewlinkSourcePage=spCrew");
  236. var mindate = (RegExp(
  237. r"(?<=minDateMinus1 = getDateFromFormat\(').+?(?=', 'yyyy-MM-dd'\);)",
  238. dotAll: true)
  239. .stringMatch(res.data));
  240. var maxdate = (RegExp(
  241. r"(?<=maxDatePlus1 = getDateFromFormat\(').+?(?=', 'yyyy-MM-dd'\);)",
  242. dotAll: true)
  243. .stringMatch(res.data));
  244. showcrewmin = mindate ?? "";
  245. showcrewmax = maxdate ?? "";
  246. String? error;
  247. if (res.statusCode == 302) {
  248. if (!loginfirst) return await showCrewMinMax(loginfirst: true);
  249. } else if (res.statusCode == 200) {
  250. error = null;
  251. } else {
  252. error = res.statusMessage;
  253. }
  254. return {
  255. 'error': error,
  256. 'data': {"mindate": mindate, "maxdate": maxdate},
  257. 'meta': {
  258. "user": storedusername,
  259. "pass": storedpassword,
  260. "statuscode": res.statusCode
  261. }
  262. };
  263. }
  264. Future showCrew(
  265. {required String start,
  266. required String end,
  267. bool crewlist = false,
  268. String al = "",
  269. String fnum = "",
  270. String dep = "",
  271. String des = "",
  272. bool loginfirst = false}) async {
  273. //String cachekey = "|$start,$end,$al,$fnum,$dep,$des,$crewlist";
  274. //start format ddMMMyy
  275. if (loginfirst) {
  276. var x = await login();
  277. if ((x["data"]["logged"] ?? false) == false) return x;
  278. }
  279. var res = await showCrewMinMax();
  280. if (res["error"] != null) return res;
  281. res = await _dio.post('/clApp', data: {
  282. "buddyName": "",
  283. "depDatePeriodBegin": start,
  284. "depDatePeriodEnd": end,
  285. "fnAirline": al,
  286. "fnNumber": fnum,
  287. "depAp": dep,
  288. "arrAp": des,
  289. "crewlinkService": "showCrew",
  290. "crewlinkOperation": "showCrew",
  291. "crewlinkSourcePage": "spCrew"
  292. });
  293. var msg = parse(res.data).querySelector(".errMsg")?.text;
  294. List leglist = [];
  295. if (res.statusCode == 200 && (msg ?? "") == "") {
  296. res = await _dio
  297. .get("/clApp?crewlinkService=showCrew&crewlinkOperation=legList");
  298. leglist = parse(res.data)
  299. .querySelectorAll("table tbody tr")
  300. .map((dom.Element tr) => (tr.children
  301. .map((td) => ((parse(td.innerHtml)
  302. .querySelector('[name="indexOfLeg"]')
  303. ?.attributes["value"]
  304. .toString()
  305. .trim()) ??
  306. td.text.trim()))
  307. .toList()))
  308. .toList();
  309. // treat cabin: cockpit: DH Flight:
  310. leglist = leglist.map((x) {
  311. x[7] =
  312. "${(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()}";
  313. return x;
  314. }).toList();
  315. if (crewlist) {
  316. var out = [];
  317. for (var leg in leglist) {
  318. await _dio.post("/clApp", data: {
  319. "crewlinkService": "showCrew",
  320. "crewlinkOperation": "clearCrewList",
  321. "crewlinkSourcePage": ""
  322. });
  323. var res = await _dio.post("/clApp", data: {
  324. "crewlinkService": "showCrew",
  325. "crewlinkOperation": "exploreFlight",
  326. "crewlinkSourcePage": "",
  327. "indexOfLeg": leg[0].toString()
  328. });
  329. // log("flt: ${res.data}");
  330. var flt =
  331. ((parse(res.data).querySelector("table tr th[colspan]")?.text) ??
  332. "\n\n\n\n\n\n\n")
  333. .split("\n")
  334. .skip(1)
  335. .map((e) => e.trim())
  336. .toList();
  337. // ..removeAt(6);
  338. var crew = [];
  339. if ((parse(res.data).querySelector(".errMsg")?.text != null)) {
  340. crew = [];
  341. } else {
  342. crew = (parse(res.data)
  343. .querySelectorAll("table tr")
  344. .map((e) =>
  345. e.text.split("\n").map((e) => e.trim()).skip(1).toList()
  346. ..removeAt(6))
  347. .skip(2))
  348. .toList();
  349. }
  350. // log("crew: $crew");
  351. out.add([
  352. leg[0],
  353. flt[0],
  354. flt[1],
  355. flt[2],
  356. flt[3],
  357. flt[4],
  358. flt[5],
  359. leg[7],
  360. crew
  361. ]);
  362. //log((flt).toString());
  363. }
  364. leglist = out;
  365. }
  366. }
  367. String? error;
  368. if (res.statusCode == 302) {
  369. if (!loginfirst) {
  370. return await showCrew(
  371. start: start,
  372. end: end,
  373. crewlist: crewlist,
  374. al: al,
  375. fnum: fnum,
  376. dep: dep,
  377. des: dep,
  378. loginfirst: true);
  379. }
  380. } else if (res.statusCode == 200) {
  381. error = null;
  382. } else {
  383. error = res.statusMessage;
  384. }
  385. return {
  386. 'error': error,
  387. 'data': {
  388. "leglist": leglist,
  389. "msg": msg,
  390. },
  391. 'meta': {
  392. "user": storedusername,
  393. "pass": storedpassword,
  394. "statuscode": res.statusCode
  395. }
  396. };
  397. }
  398. Future userInfo({bool loginfirst = false}) async {
  399. if (loginfirst) {
  400. var x = await login();
  401. if ((x["data"]["logged"] ?? false) == false) return x;
  402. }
  403. var res = await _dio.get("/HeaderPage.jsp");
  404. var data = (RegExp(
  405. r'(?<=\<td class="userInfo">)\s*<b>(.*?)<\/b>\s*(.+?)<br\/>\s*(.*?)\s(.{3})\s(.{3}).*?(?=\<\/td\>)',
  406. dotAll: true)
  407. .firstMatch(res.data));
  408. bool novalidsession = (res.data).indexOf("unknown user") > -1;
  409. String? error;
  410. if (novalidsession) {
  411. if (!loginfirst) return await userInfo(loginfirst: true);
  412. } else if (res.statusCode == 200) {
  413. error = null;
  414. } else {
  415. error = res.statusMessage;
  416. }
  417. return {
  418. 'error': error,
  419. 'data': {
  420. "tlc": data?.group(1),
  421. "name": data?.group(2),
  422. "qualif": (data?.group(3) ?? "").split(","),
  423. "base": data?.group(4),
  424. "sit": data?.group(5),
  425. },
  426. 'meta': {"user": storedusername, "pass": storedpassword}
  427. };
  428. }
  429. Future changePass({required String newpass, bool loginfirst = false}) async {
  430. if (loginfirst) {
  431. var x = await login();
  432. if ((x["data"]["logged"] ?? false) == false) return x;
  433. }
  434. var res = await _dio.get(
  435. "/clApp?crewlinkService=changePassword&crewlinkOperation=default&crewlinkSourcePage=spCrew");
  436. res = await _dio.post("/clApp", data: {
  437. "oldPassword": storedpassword,
  438. "newPassword1": newpass,
  439. "newPassword2": newpass,
  440. "crewlinkService": "changePassword",
  441. "crewlinkOperation": "changePassword",
  442. "change": "Change"
  443. });
  444. var infomsg = parse(res.data).querySelector(".infoMsg")?.text;
  445. var msg = parse(res.data).querySelector(".errMsg")?.text;
  446. var errormsg = parse(res.data).querySelector("h1.error")?.text;
  447. String? error;
  448. if (res.statusCode == 302) {
  449. if (!loginfirst) {
  450. return await changePass(newpass: newpass, loginfirst: true);
  451. }
  452. } else if (res.statusCode == 200) {
  453. error = null;
  454. } else {
  455. error = res.statusMessage;
  456. }
  457. return {
  458. 'error': error,
  459. 'data': {"info": infomsg, "msg": msg, "error": errormsg},
  460. 'meta': {
  461. "user": storedusername,
  462. "pass": storedpassword,
  463. "statuscode": res.statusCode
  464. }
  465. };
  466. }
  467. Future logout() async {
  468. var res = await _dio.get("/closeSession.jsp");
  469. storedusername = "";
  470. storedpassword = "";
  471. String? error;
  472. if (res.statusCode == 200) {
  473. error = null;
  474. } else {
  475. error = res.statusMessage;
  476. }
  477. log(res.toString());
  478. return {
  479. 'error': error,
  480. 'data': {},
  481. 'meta': {
  482. "user": storedusername,
  483. "pass": storedpassword,
  484. "statuscode": res.statusCode
  485. }
  486. };
  487. }
  488. Future notif(
  489. {bool download = false,
  490. required String fileid,
  491. CancelToken? cancelToken,
  492. ProgressCallback? progressCallback,
  493. bool loginfirst = false}) async {
  494. if (loginfirst) {
  495. var x = await login();
  496. if ((x["data"]["logged"] ?? false) == false) return x;
  497. }
  498. var res = await _dio.get(
  499. "/clApp?crewlinkService=notification&crewlinkOperation=default&crewlinkSourcePage=spCrew");
  500. String? pdflink = (RegExp(
  501. r"(?<='js\/pdfjs\/web\/viewer\.html\?file=).+.pdf?(?=')",
  502. dotAll: true)
  503. .stringMatch(res.data));
  504. String? msg = parse(res.data).querySelector(".msgDetails")?.text;
  505. // String fileid =
  506. // "${(await getApplicationDocumentsDirectory()).path}/${DateFormat("yyyyMMddHHmm").format(DateTime.now())}_${storedusername}_notif.pdf";
  507. String? error;
  508. if (res.statusCode == 302) {
  509. if (!loginfirst) {
  510. return await notif(
  511. download: download,
  512. fileid: fileid,
  513. cancelToken: cancelToken,
  514. progressCallback: progressCallback,
  515. loginfirst: true);
  516. }
  517. } else if (res.statusCode == 200) {
  518. error = null;
  519. if (download && pdflink != null) {
  520. await _dio.download(
  521. "${Uri.parse(realUrl).scheme}://${Uri.parse(realUrl).host}$pdflink",
  522. fileid,
  523. );
  524. }
  525. } else {
  526. error = res.statusMessage;
  527. }
  528. return {
  529. 'error': error,
  530. 'data': {
  531. "pdflink": pdflink,
  532. "msg": msg,
  533. ...pdflink != null ? {"id": fileid} : {}
  534. },
  535. 'meta': {
  536. "user": storedusername,
  537. "pass": storedpassword,
  538. "statuscode": res.statusCode
  539. }
  540. };
  541. }
  542. Future confirmNotif({bool loginfirst = false}) async {
  543. if (loginfirst) {
  544. var x = await login();
  545. if ((x["data"]["logged"] ?? false) == false) return x;
  546. }
  547. var res = await _dio.post("/clApp", data: {
  548. 'crewlinkService': 'notification',
  549. 'crewlinkOperation': 'confirmNotif',
  550. });
  551. var msg = parse(res.data).querySelector(".errMsg")?.text;
  552. var errormsg = parse(res.data).querySelector("h1.error")?.text;
  553. String? error;
  554. if (res.statusCode == 302) {
  555. if (!loginfirst) return await confirmNotif(loginfirst: true);
  556. } else if (res.statusCode == 200) {
  557. error = null;
  558. } else {
  559. error = res.statusMessage;
  560. }
  561. // print("==============");
  562. // print({
  563. // 'error': error,
  564. // 'data': {"msg": msg, "error": errormsg},
  565. // 'meta': {
  566. // "user": storedusername,
  567. // "pass": storedpassword,
  568. // "statuscode": res.statusCode
  569. // }
  570. // });
  571. // print("==============");
  572. return {
  573. 'error': error,
  574. 'data': {"msg": msg, "error": errormsg},
  575. 'meta': {
  576. "user": storedusername,
  577. "pass": storedpassword,
  578. "statuscode": res.statusCode
  579. }
  580. };
  581. }
  582. bool credSaved() {
  583. return (Hive.box("settings").get("crewlink_user", defaultValue: "") !=
  584. "") &&
  585. (Hive.box("settings").get("crewlink_pass", defaultValue: "") != "");
  586. }
  587. String storedusername = "";
  588. String storedpassword = "";
  589. bool get logged => storedusername != "" && storedpassword != "";
  590. String rostermin = "";
  591. String rostermax = "";
  592. String showcrewmin = "";
  593. String showcrewmax = "";
  594. Map<String, dynamic> decodeRoster(String pdffile) {
  595. return _decodeRoster(pdffile);
  596. }
  597. Map<String, dynamic> _decodeRoster(String pdffile) {
  598. // try {
  599. const weekdays = "Mon|Tue|Wed|Thu|Fri|Sat|Sun";
  600. const monthday = '[0-3]\\d';
  601. const month = "Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec";
  602. const date = "$monthday($month)[0-5]\\d";
  603. const station = "[A-Z]{3}";
  604. const al = "[A-Z]{2}";
  605. const fnum = "\\d{1,4}[A-Z^R]?";
  606. const actype = "\\d{2}[A-Z0-9]";
  607. const label = "(OFF|[A-Z]{2,}|[A-Z]+([0-9?])|[A-Z]+/[A-Z]+[0-9]?)";
  608. const time = "[0-2]\\d[0-5]\\d";
  609. PdfDocument document =
  610. PdfDocument(inputBytes: File(pdffile).readAsBytesSync());
  611. var text = PdfTextExtractor(document).extractText(layoutText: true);
  612. document.dispose();
  613. text = text.replaceAll("\r\n", "#");
  614. // var text = ros.replaceAll("\n", "#");
  615. Map info = {};
  616. var d = RegExp(r'for(\d+)(.+?),(.+?)#').firstMatch(text);
  617. info["tlc"] = d?.group(1);
  618. info["lname"] = d?.group(2);
  619. info["fname"] = d?.group(3);
  620. d = RegExp(
  621. r'NetLine/Crew\((.+?)\)printedbyCREWLINK(.{7})(\d\d:\d\d)Page\d+#')
  622. .firstMatch(text);
  623. info["date"] = d?.group(2);
  624. info["time"] = d?.group(3);
  625. d = RegExp(r'Period:(.{7})-(.{7})#').firstMatch(text);
  626. info["startdate"] = d?.group(1);
  627. info["enddate"] = d?.group(2);
  628. String header =
  629. RegExp("(?<=Period:$date-$date#)(.*?)(?=dateHdutyRdeparrACinfo)")
  630. .stringMatch(text) ??
  631. "";
  632. String body = text.replaceAll(
  633. RegExp(
  634. "Individualdutyplan(.+?)${RegExp.escape(header)}(dateHdutyRdeparrACinfo#dateHdutyRdeparrACinfo#dateHdutyRdeparrACinfo#)?"),
  635. "");
  636. // String footer = RegExp("(Flighttime.*)\$").stringMatch(body) ?? "";
  637. String onlyprog = RegExp("^(.*?)(?=Flighttime)").stringMatch(body) ?? "";
  638. onlyprog = onlyprog.replaceAllMapped(
  639. RegExp("(?:$weekdays)$monthday.+?(?=(($weekdays)$monthday)|\$)"),
  640. (m) => "${m.group(0)}\n");
  641. const ground =
  642. "(?<label>(?:SR|$label(?=R)|$label)?)(?<req>R)?(?<dep>$station)(?<hdep>$time)(?<hdes>$time)(?<actype>$actype?)?";
  643. const checkin = "C/I(?<station>$station)(?<time>$time)";
  644. const checkout = "C/O";
  645. const checkoutData = "(?<time>$time)(?<station>$station)";
  646. const flight =
  647. "(?<al>$al?)(?<fnum>$fnum?)(?<fltday>\\/$monthday)?(?<req>R)?(?<dep>$station?)(?<hdep>$time?)(?<hdes>$time?)(?<des>$station?)(?<actype>$actype?)";
  648. const dhflight =
  649. "DH\\/(?<al>$al?)(?<fnum>$fnum?)(?<fltday>\\/$monthday)?(?<req>R)?(?<dep>$station?)(?<hdep>$time?)(?<hdes>$time?)(?<des>$station?)";
  650. const dhlimo =
  651. "(?<label>$label?)(?<req>R)?(?<dep>$station?)(?<hdep>$time?)(?<hdes>$time?)(?<des>$station?)";
  652. const credit = "\\[Credit(?<credit>\\d+\\:\\d{2}?)\\]";
  653. const wholeday = "(?<label>$label)(?<req>R)?(?<dep>$station)";
  654. const changed = "CHANGED";
  655. var linedate = _ddmmmyy2date(info["startdate"]);
  656. Map<String, dynamic> liste = {
  657. "changed": changed,
  658. "checkin": checkin,
  659. "flight": flight,
  660. "dhflight": dhflight,
  661. "dhlimo": dhlimo,
  662. "ground": ground,
  663. "checkout": checkout,
  664. "credit": credit,
  665. "wholeday": wholeday,
  666. };
  667. Map<String, List<Map<String, dynamic>>> roster = {};
  668. for (var line in onlyprog.split("\n")..remove("")) {
  669. // log("... decoding $line");
  670. // var dateblock = RegExp("(($weekdays)$monthday)").stringMatch(line);
  671. // log(dateblock.toString());
  672. while (roster.keys.contains(_date2ymd(linedate))) {
  673. linedate = linedate.add(const Duration(days: 1));
  674. }
  675. var details = line.replaceAll(RegExp("($weekdays)$monthday"), "");
  676. // log("... decoding $details");
  677. List<Map<String, dynamic>> dayduty = [];
  678. //log(details);
  679. var elements = details.split("#");
  680. // log("${elements.length} $elements");
  681. for (var i = 0;
  682. (i < elements.length - 1) ||
  683. (i < elements.length && elements.last != "");
  684. i++) {
  685. var e = elements[i];
  686. bool decoded = false;
  687. String? remark;
  688. // log("_______ $i __el: $e");
  689. for (var dutytype in liste.keys) {
  690. if (RegExp(liste[dutytype]).hasMatch(e)) {
  691. late RegExpMatch? res;
  692. if (dutytype == "checkout" && i + 1 < elements.length) {
  693. res = RegExp(checkoutData).firstMatch(elements[i + 1]);
  694. i++;
  695. } else {
  696. res = RegExp(liste[dutytype]).firstMatch(e);
  697. }
  698. if (["flight", "dhflight", "ground", "dhlimo"].contains(dutytype) &&
  699. i + 1 < elements.length &&
  700. !liste.values.any((r) => RegExp(r).hasMatch(elements[i + 1]))) {
  701. remark = elements[i + 1];
  702. i++;
  703. }
  704. // log("_________$dutytype ${res?.groupNames.map((name) => "$name: ${res?.namedGroup(name)}")}");
  705. dayduty.add({
  706. "type": dutytype,
  707. "data": res?.groupNames.toList().fold(
  708. {},
  709. (Map t, n) => t
  710. ..addAll({
  711. n: res?.namedGroup(n),
  712. if (remark != null) ...{"remark": remark}
  713. })) ??
  714. {}
  715. });
  716. decoded = true;
  717. break;
  718. }
  719. }
  720. if (!decoded) {
  721. if (e.isNotEmpty && e != "X") {
  722. log("__unknown: $e");
  723. dayduty.add({
  724. "type": "unknown",
  725. "data": {"unknown": e.trim()}
  726. });
  727. }
  728. }
  729. }
  730. roster[_date2ymd(linedate)] = dayduty;
  731. //log("$dateblock ${_date2ymd(linedate)} $dayduty");
  732. }
  733. return {
  734. ...{"info": info},
  735. ...{"roster": roster}
  736. };
  737. // } catch (e) {
  738. // log("crewlinkapi: decoderoster: ${e.toString()}");
  739. // return {};
  740. // }
  741. }
  742. DateTime _ddmmmyy2date(String date) {
  743. return DateFormat("ddMMMyy").parseUTC(date);
  744. }
  745. String _date2ymd(DateTime date) {
  746. return DateFormat("y-MM-dd").format(date);
  747. }
  748. }