crewlink_api.dart 25 KB

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