timetable2lua

Export timetable data as lua table.

Verzia zo dňa 01.03.2026. Pozri najnovšiu verziu.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, Greasemonkey alebo Violentmonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, % alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey alebo Userscripts.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie správcu používateľských skriptov.

(Už mám správcu používateľských skriptov, nechajte ma ho nainštalovať!)

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

(Už mám správcu používateľských štýlov, nechajte ma ho nainštalovať!)

// ==UserScript==
// @name         timetable2lua
// @namespace    https://dvxg.de/
// @version      0.2.2
// @author       davidxuang
// @description  Export timetable data as lua table.
// @license      AGPL-3.0-only
// @icon         https://cdn.jsdelivr.net/gh/microsoft/fluentui-emoji/assets/Metro/3D/metro_3d.png
// @homepage     https://github.com/davidxuang/timetable2lua
// @homepageURL  https://github.com/davidxuang/timetable2lua
// @match        https://www.cqmetro.cn/smbsj.html
// @match        https://www.wuhanrt.com/news-content.html*
// ==/UserScript==

(function () {
  'use strict';

  function t(t2) {
    return t2 && t2.__esModule && Object.prototype.hasOwnProperty.call(t2, "default") ? t2.default : t2;
  }
  var r, e, n, o, u, f, i = { exports: {} }, c = { exports: {} };
  function l() {
    return r || (r = 1, (function(t2) {
      var r2 = (function() {
        function t3(r4, n2, o2, u2) {
          "object" == typeof n2 && (o2 = n2.depth, u2 = n2.prototype, n2.filter, n2 = n2.circular);
          var f2 = [], i2 = [], c2 = "undefined" != typeof Buffer;
          return void 0 === n2 && (n2 = true), void 0 === o2 && (o2 = 1 / 0), (function r5(o3, l2) {
            if (null === o3) return null;
            if (0 == l2) return o3;
            var s2, a2;
            if ("object" != typeof o3) return o3;
            if (t3.__isArray(o3)) s2 = [];
            else if (t3.__isRegExp(o3)) s2 = new RegExp(o3.source, e2(o3)), o3.lastIndex && (s2.lastIndex = o3.lastIndex);
            else if (t3.__isDate(o3)) s2 = new Date(o3.getTime());
            else {
              if (c2 && Buffer.isBuffer(o3)) return s2 = Buffer.allocUnsafe ? Buffer.allocUnsafe(o3.length) : new Buffer(o3.length), o3.copy(s2), s2;
              void 0 === u2 ? (a2 = Object.getPrototypeOf(o3), s2 = Object.create(a2)) : (s2 = Object.create(u2), a2 = u2);
            }
            if (n2) {
              var p2 = f2.indexOf(o3);
              if (-1 != p2) return i2[p2];
              f2.push(o3), i2.push(s2);
            }
            for (var g2 in o3) {
              var y2;
              a2 && (y2 = Object.getOwnPropertyDescriptor(a2, g2)), y2 && null == y2.set || (s2[g2] = r5(o3[g2], l2 - 1));
            }
            return s2;
          })(r4, o2);
        }
        function r3(t4) {
          return Object.prototype.toString.call(t4);
        }
        function e2(t4) {
          var r4 = "";
          return t4.global && (r4 += "g"), t4.ignoreCase && (r4 += "i"), t4.multiline && (r4 += "m"), r4;
        }
        return t3.clonePrototype = function(t4) {
          if (null === t4) return null;
          var r4 = function() {
          };
          return r4.prototype = t4, new r4();
        }, t3.__objToStr = r3, t3.__isDate = function(t4) {
          return "object" == typeof t4 && "[object Date]" === r3(t4);
        }, t3.__isArray = function(t4) {
          return "object" == typeof t4 && "[object Array]" === r3(t4);
        }, t3.__isRegExp = function(t4) {
          return "object" == typeof t4 && "[object RegExp]" === r3(t4);
        }, t3.__getRegExpFlags = e2, t3;
      })();
      t2.exports && (t2.exports = r2);
    })(c)), c.exports;
  }
  function s() {
    if (n) return e;
    n = 1;
    var t2 = l();
    return e = function(r2, e2) {
      return r2 = r2 || {}, Object.keys(e2).forEach((function(n2) {
        void 0 === r2[n2] && (r2[n2] = t2(e2[n2]));
      })), r2;
    };
  }
  function a() {
    return u ? o : (u = 1, o = [[768, 879], [1155, 1158], [1160, 1161], [1425, 1469], [1471, 1471], [1473, 1474], [1476, 1477], [1479, 1479], [1536, 1539], [1552, 1557], [1611, 1630], [1648, 1648], [1750, 1764], [1767, 1768], [1770, 1773], [1807, 1807], [1809, 1809], [1840, 1866], [1958, 1968], [2027, 2035], [2305, 2306], [2364, 2364], [2369, 2376], [2381, 2381], [2385, 2388], [2402, 2403], [2433, 2433], [2492, 2492], [2497, 2500], [2509, 2509], [2530, 2531], [2561, 2562], [2620, 2620], [2625, 2626], [2631, 2632], [2635, 2637], [2672, 2673], [2689, 2690], [2748, 2748], [2753, 2757], [2759, 2760], [2765, 2765], [2786, 2787], [2817, 2817], [2876, 2876], [2879, 2879], [2881, 2883], [2893, 2893], [2902, 2902], [2946, 2946], [3008, 3008], [3021, 3021], [3134, 3136], [3142, 3144], [3146, 3149], [3157, 3158], [3260, 3260], [3263, 3263], [3270, 3270], [3276, 3277], [3298, 3299], [3393, 3395], [3405, 3405], [3530, 3530], [3538, 3540], [3542, 3542], [3633, 3633], [3636, 3642], [3655, 3662], [3761, 3761], [3764, 3769], [3771, 3772], [3784, 3789], [3864, 3865], [3893, 3893], [3895, 3895], [3897, 3897], [3953, 3966], [3968, 3972], [3974, 3975], [3984, 3991], [3993, 4028], [4038, 4038], [4141, 4144], [4146, 4146], [4150, 4151], [4153, 4153], [4184, 4185], [4448, 4607], [4959, 4959], [5906, 5908], [5938, 5940], [5970, 5971], [6002, 6003], [6068, 6069], [6071, 6077], [6086, 6086], [6089, 6099], [6109, 6109], [6155, 6157], [6313, 6313], [6432, 6434], [6439, 6440], [6450, 6450], [6457, 6459], [6679, 6680], [6912, 6915], [6964, 6964], [6966, 6970], [6972, 6972], [6978, 6978], [7019, 7027], [7616, 7626], [7678, 7679], [8203, 8207], [8234, 8238], [8288, 8291], [8298, 8303], [8400, 8431], [12330, 12335], [12441, 12442], [43014, 43014], [43019, 43019], [43045, 43046], [64286, 64286], [65024, 65039], [65056, 65059], [65279, 65279], [65529, 65531], [68097, 68099], [68101, 68102], [68108, 68111], [68152, 68154], [68159, 68159], [119143, 119145], [119155, 119170], [119173, 119179], [119210, 119213], [119362, 119364], [917505, 917505], [917536, 917631], [917760, 917999]]);
  }
  var p = (function() {
    if (f) return i.exports;
    f = 1;
    var t2 = s(), r2 = a(), e2 = { nul: 0, control: 0 };
    function n2(t3, r3) {
      if ("string" != typeof t3) return o2(t3, r3);
      for (var e3 = 0, n3 = 0; n3 < t3.length; n3++) {
        var u2 = o2(t3.charCodeAt(n3), r3);
        if (u2 < 0) return -1;
        e3 += u2;
      }
      return e3;
    }
    function o2(t3, e3) {
      return 0 === t3 ? e3.nul : t3 < 32 || t3 >= 127 && t3 < 160 ? e3.control : (function(t4) {
        var e4, n3 = 0, o3 = r2.length - 1;
        if (t4 < r2[0][0] || t4 > r2[o3][1]) return false;
        for (; o3 >= n3; ) if (e4 = Math.floor((n3 + o3) / 2), t4 > r2[e4][1]) n3 = e4 + 1;
        else {
          if (!(t4 < r2[e4][0])) return true;
          o3 = e4 - 1;
        }
        return false;
      })(t3) ? 0 : 1 + (t3 >= 4352 && (t3 <= 4447 || 9001 == t3 || 9002 == t3 || t3 >= 11904 && t3 <= 42191 && 12351 != t3 || t3 >= 44032 && t3 <= 55203 || t3 >= 63744 && t3 <= 64255 || t3 >= 65040 && t3 <= 65049 || t3 >= 65072 && t3 <= 65135 || t3 >= 65280 && t3 <= 65376 || t3 >= 65504 && t3 <= 65510 || t3 >= 131072 && t3 <= 196605 || t3 >= 196608 && t3 <= 262141));
    }
    return i.exports = function(t3) {
      return n2(t3, e2);
    }, i.exports.config = function(r3) {
      return r3 = t2(r3 || {}, e2), function(t3) {
        return n2(t3, r3);
      };
    }, i.exports;
  })(), g = t(p);
  function y(t2, r2, e2 = {}) {
    const n2 = "number" == typeof t2;
    let o2 = n2 ? r2 : t2;
    const u2 = n2 ? t2 : r2;
    "string" == typeof e2 && (e2 = { char: e2 }), e2.char || (e2.char = " "), e2.strip || (e2.strip = false), "string" != typeof o2 && (o2 = o2.toString());
    let f2 = null, i2 = "";
    if (e2.colors) {
      const t3 = /\x1B\[(?:[0-9]{1,2}(?:;[0-9]{1,2})?)?[m|K]/g;
      f2 = o2.replace(t3, "");
    }
    const c2 = true === e2.fixed_width ? u2 - (f2 || o2).length : u2 - g.config(e2.wcwidth_options || {})(f2 || o2);
    return c2 < 0 ? e2.strip ? n2 ? o2.substr(-1 * u2) : o2.substr(0, u2) : o2 : (i2 += e2.char.repeat(c2), n2 ? i2 + o2 : o2 + i2);
  }
  function getDefaultExportFromCjs(x) {
    return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x["default"] : x;
  }
  var wcwidth$1 = { exports: {} };
  var clone = { exports: {} };
  var hasRequiredClone;
  function requireClone() {
    if (hasRequiredClone) return clone.exports;
    hasRequiredClone = 1;
    (function(module) {
      var clone2 = (function() {
        function clone3(parent, circular, depth, prototype) {
          if (typeof circular === "object") {
            depth = circular.depth;
            prototype = circular.prototype;
            circular.filter;
            circular = circular.circular;
          }
          var allParents = [];
          var allChildren = [];
          var useBuffer = typeof Buffer != "undefined";
          if (typeof circular == "undefined")
            circular = true;
          if (typeof depth == "undefined")
            depth = Infinity;
          function _clone(parent2, depth2) {
            if (parent2 === null)
              return null;
            if (depth2 == 0)
              return parent2;
            var child;
            var proto;
            if (typeof parent2 != "object") {
              return parent2;
            }
            if (clone3.__isArray(parent2)) {
              child = [];
            } else if (clone3.__isRegExp(parent2)) {
              child = new RegExp(parent2.source, __getRegExpFlags(parent2));
              if (parent2.lastIndex) child.lastIndex = parent2.lastIndex;
            } else if (clone3.__isDate(parent2)) {
              child = new Date(parent2.getTime());
            } else if (useBuffer && Buffer.isBuffer(parent2)) {
              if (Buffer.allocUnsafe) {
                child = Buffer.allocUnsafe(parent2.length);
              } else {
                child = new Buffer(parent2.length);
              }
              parent2.copy(child);
              return child;
            } else {
              if (typeof prototype == "undefined") {
                proto = Object.getPrototypeOf(parent2);
                child = Object.create(proto);
              } else {
                child = Object.create(prototype);
                proto = prototype;
              }
            }
            if (circular) {
              var index = allParents.indexOf(parent2);
              if (index != -1) {
                return allChildren[index];
              }
              allParents.push(parent2);
              allChildren.push(child);
            }
            for (var i2 in parent2) {
              var attrs;
              if (proto) {
                attrs = Object.getOwnPropertyDescriptor(proto, i2);
              }
              if (attrs && attrs.set == null) {
                continue;
              }
              child[i2] = _clone(parent2[i2], depth2 - 1);
            }
            return child;
          }
          return _clone(parent, depth);
        }
        clone3.clonePrototype = function clonePrototype(parent) {
          if (parent === null)
            return null;
          var c2 = function() {
          };
          c2.prototype = parent;
          return new c2();
        };
        function __objToStr(o2) {
          return Object.prototype.toString.call(o2);
        }
        clone3.__objToStr = __objToStr;
        function __isDate(o2) {
          return typeof o2 === "object" && __objToStr(o2) === "[object Date]";
        }
        clone3.__isDate = __isDate;
        function __isArray(o2) {
          return typeof o2 === "object" && __objToStr(o2) === "[object Array]";
        }
        clone3.__isArray = __isArray;
        function __isRegExp(o2) {
          return typeof o2 === "object" && __objToStr(o2) === "[object RegExp]";
        }
        clone3.__isRegExp = __isRegExp;
        function __getRegExpFlags(re) {
          var flags = "";
          if (re.global) flags += "g";
          if (re.ignoreCase) flags += "i";
          if (re.multiline) flags += "m";
          return flags;
        }
        clone3.__getRegExpFlags = __getRegExpFlags;
        return clone3;
      })();
      if (module.exports) {
        module.exports = clone2;
      }
    })(clone);
    return clone.exports;
  }
  var defaults;
  var hasRequiredDefaults;
  function requireDefaults() {
    if (hasRequiredDefaults) return defaults;
    hasRequiredDefaults = 1;
    var clone2 = requireClone();
    defaults = function(options, defaults2) {
      options = options || {};
      Object.keys(defaults2).forEach(function(key) {
        if (typeof options[key] === "undefined") {
          options[key] = clone2(defaults2[key]);
        }
      });
      return options;
    };
    return defaults;
  }
  var combining;
  var hasRequiredCombining;
  function requireCombining() {
    if (hasRequiredCombining) return combining;
    hasRequiredCombining = 1;
    combining = [
      [768, 879],
      [1155, 1158],
      [1160, 1161],
      [1425, 1469],
      [1471, 1471],
      [1473, 1474],
      [1476, 1477],
      [1479, 1479],
      [1536, 1539],
      [1552, 1557],
      [1611, 1630],
      [1648, 1648],
      [1750, 1764],
      [1767, 1768],
      [1770, 1773],
      [1807, 1807],
      [1809, 1809],
      [1840, 1866],
      [1958, 1968],
      [2027, 2035],
      [2305, 2306],
      [2364, 2364],
      [2369, 2376],
      [2381, 2381],
      [2385, 2388],
      [2402, 2403],
      [2433, 2433],
      [2492, 2492],
      [2497, 2500],
      [2509, 2509],
      [2530, 2531],
      [2561, 2562],
      [2620, 2620],
      [2625, 2626],
      [2631, 2632],
      [2635, 2637],
      [2672, 2673],
      [2689, 2690],
      [2748, 2748],
      [2753, 2757],
      [2759, 2760],
      [2765, 2765],
      [2786, 2787],
      [2817, 2817],
      [2876, 2876],
      [2879, 2879],
      [2881, 2883],
      [2893, 2893],
      [2902, 2902],
      [2946, 2946],
      [3008, 3008],
      [3021, 3021],
      [3134, 3136],
      [3142, 3144],
      [3146, 3149],
      [3157, 3158],
      [3260, 3260],
      [3263, 3263],
      [3270, 3270],
      [3276, 3277],
      [3298, 3299],
      [3393, 3395],
      [3405, 3405],
      [3530, 3530],
      [3538, 3540],
      [3542, 3542],
      [3633, 3633],
      [3636, 3642],
      [3655, 3662],
      [3761, 3761],
      [3764, 3769],
      [3771, 3772],
      [3784, 3789],
      [3864, 3865],
      [3893, 3893],
      [3895, 3895],
      [3897, 3897],
      [3953, 3966],
      [3968, 3972],
      [3974, 3975],
      [3984, 3991],
      [3993, 4028],
      [4038, 4038],
      [4141, 4144],
      [4146, 4146],
      [4150, 4151],
      [4153, 4153],
      [4184, 4185],
      [4448, 4607],
      [4959, 4959],
      [5906, 5908],
      [5938, 5940],
      [5970, 5971],
      [6002, 6003],
      [6068, 6069],
      [6071, 6077],
      [6086, 6086],
      [6089, 6099],
      [6109, 6109],
      [6155, 6157],
      [6313, 6313],
      [6432, 6434],
      [6439, 6440],
      [6450, 6450],
      [6457, 6459],
      [6679, 6680],
      [6912, 6915],
      [6964, 6964],
      [6966, 6970],
      [6972, 6972],
      [6978, 6978],
      [7019, 7027],
      [7616, 7626],
      [7678, 7679],
      [8203, 8207],
      [8234, 8238],
      [8288, 8291],
      [8298, 8303],
      [8400, 8431],
      [12330, 12335],
      [12441, 12442],
      [43014, 43014],
      [43019, 43019],
      [43045, 43046],
      [64286, 64286],
      [65024, 65039],
      [65056, 65059],
      [65279, 65279],
      [65529, 65531],
      [68097, 68099],
      [68101, 68102],
      [68108, 68111],
      [68152, 68154],
      [68159, 68159],
      [119143, 119145],
      [119155, 119170],
      [119173, 119179],
      [119210, 119213],
      [119362, 119364],
      [917505, 917505],
      [917536, 917631],
      [917760, 917999]
    ];
    return combining;
  }
  var hasRequiredWcwidth;
  function requireWcwidth() {
    if (hasRequiredWcwidth) return wcwidth$1.exports;
    hasRequiredWcwidth = 1;
    var defaults2 = requireDefaults();
    var combining2 = requireCombining();
    var DEFAULTS = {
      nul: 0,
      control: 0
    };
    wcwidth$1.exports = function wcwidth3(str) {
      return wcswidth(str, DEFAULTS);
    };
    wcwidth$1.exports.config = function(opts) {
      opts = defaults2(opts || {}, DEFAULTS);
      return function wcwidth3(str) {
        return wcswidth(str, opts);
      };
    };
    function wcswidth(str, opts) {
      if (typeof str !== "string") return wcwidth2(str, opts);
      var s2 = 0;
      for (var i2 = 0; i2 < str.length; i2++) {
        var n2 = wcwidth2(str.charCodeAt(i2), opts);
        if (n2 < 0) return -1;
        s2 += n2;
      }
      return s2;
    }
    function wcwidth2(ucs, opts) {
      if (ucs === 0) return opts.nul;
      if (ucs < 32 || ucs >= 127 && ucs < 160) return opts.control;
      if (bisearch(ucs)) return 0;
      return 1 + (ucs >= 4352 && (ucs <= 4447 ||
ucs == 9001 || ucs == 9002 || ucs >= 11904 && ucs <= 42191 && ucs != 12351 ||
ucs >= 44032 && ucs <= 55203 ||
ucs >= 63744 && ucs <= 64255 ||
ucs >= 65040 && ucs <= 65049 ||
ucs >= 65072 && ucs <= 65135 ||
ucs >= 65280 && ucs <= 65376 ||
ucs >= 65504 && ucs <= 65510 || ucs >= 131072 && ucs <= 196605 || ucs >= 196608 && ucs <= 262141));
    }
    function bisearch(ucs) {
      var min = 0;
      var max = combining2.length - 1;
      var mid;
      if (ucs < combining2[0][0] || ucs > combining2[max][1]) return false;
      while (max >= min) {
        mid = Math.floor((min + max) / 2);
        if (ucs > combining2[mid][1]) min = mid + 1;
        else if (ucs < combining2[mid][0]) max = mid - 1;
        else return true;
      }
      return false;
    }
    return wcwidth$1.exports;
  }
  var wcwidthExports = requireWcwidth();
  const wcwidth = getDefaultExportFromCjs(wcwidthExports);
  function assert(condition, message) {
    if (!condition) {
      throw new Error(message || "Assertion failed");
    }
  }
  function attachButton(parent, onClick) {
    const button = document.createElement("a");
    button.append("导出");
    button.addEventListener("click", onClick, false);
    button.style.cursor = "pointer";
    button.style.position = "absolute";
    button.style.zIndex = "1";
    button.style.color = "white";
    button.style.opacity = ".75";
    button.style.paddingInlineStart = ".5em";
    button.style.fontSize = ".84em";
    parent?.appendChild(button);
  }
  function compareNestedStringArray(x, y2) {
    if (x instanceof Array) {
      if (x.length != y2.length) {
        return false;
      } else {
        return x.filter((v, i2) => !compareNestedStringArray(v, y2[i2])).length == 0;
      }
    } else {
      return x === y2;
    }
  }
  function deduplicateDays(timetable) {
    let dedup = true;
    timetable.forEach((days) => {
      if (days.length > 1) {
        days.slice(1).forEach((day) => {
          if (!compareNestedStringArray(days[0], day)) {
            dedup = false;
          }
        });
      }
    });
    if (dedup) {
      return new Map(
        Array.from(timetable.entries()).map(([station, days]) => [
          station,
          [days[0]]
        ])
      );
    } else {
      return timetable;
    }
  }
  function luaifyNestedStringArray(array, padding = 0) {
    if (array instanceof Array) {
      return `{ ${array.map((child) => luaifyNestedStringArray(child, padding)).join(", ")} }`;
    } else if (array == "nil") {
      return y(padding, `nil`);
    } else {
      return y(padding, `'${array}'`);
    }
  }
  function luaifyTimetable(timetable) {
    timetable = deduplicateDays(timetable);
    const padding = Math.max(...[...timetable.keys()].map((str) => wcwidth(str))) + 4;
    return `
			stations = ${luaifyNestedStringArray([...timetable.keys()])},
			data = {
${Array.from(timetable).map(
    ([key, value]) => `				${y(`['${key}']`, padding)} = ${luaifyNestedStringArray(value, 7)},`
  ).join("\n")}
			}`;
  }
  function parseCell$1(cell, closed) {
    return cell.innerText.trim().replace("--", closed ? "nil" : "").replace(/^0([4-9]):(\d\d)$/, "$1:$2");
  }
  function parseRow(cells, days, first, last) {
    const closed = cells.slice(1).filter((cell) => cell.innerText.trim().length > 2).length == 0;
    return days.map((dayOffset) => [
      first.map((i2) => parseCell$1(cells[i2 + dayOffset], closed)),
      last.map(
        (index) => index instanceof Array ? index.map((i2) => parseCell$1(cells[i2 + dayOffset], closed)) : parseCell$1(cells[index + dayOffset], closed)
      )
    ]);
  }
  const CRT = {
    bootstrap: function() {
      document.querySelectorAll(".line-time-table").forEach((table) => {
        const caption = table.querySelector("caption");
        const copyData = function() {
          const [days, termini, sub_termini] = [0, 1, 2].map(
            (r2) => [
              ...table.tHead.rows[r2].querySelectorAll(
                ".bg-f7f7f7"
              )
            ].filter((th) => th.innerText.trim().length > 2)
          );
          if (days.length == 0) {
            days.push(document.createElement("th"));
            days[0].colSpan = termini.map((g2) => g2.colSpan).reduce((p2, c2) => p2 + c2, 0);
          }
          assert([1, 2].includes(days.length), "Invalid # of days.");
          assert(
            termini.length % days.length == 0 && termini.length >= 2,
            "Invalid # of termini."
          );
          assert(
            sub_termini.length % days.length == 0 && sub_termini.length >= 2,
            "Invalid # of child termini."
          );
          const dayWidth = days[0].colSpan;
          const dayOffsets = [];
          for (let i2 = 0; i2 < days.length; i2++) {
            assert(days[i2].colSpan == dayWidth);
            dayOffsets.push(i2 * dayWidth);
          }
          const offsetFirst = [1, 2];
          const offsetLast = [3, 4];
          const dl = [];
          const ul = [];
          if (termini.length / days.length == 2 && sub_termini.length / termini.length == 2) ;
          else if (termini.length / days.length >= 2) {
            let i2 = 0;
            for (const terminus of termini) {
              const span = sub_termini.slice(i2, i2 + terminus.colSpan);
              if (terminus.innerText.trim().startsWith("首班车")) {
                offsetFirst[0] = span.single((td) => {
                  const text = td.innerText.trim();
                  return text.match(/↓|内环|跳磴南/u) !== null;
                }).cellIndex + 1;
                offsetFirst[1] = span.single((td) => {
                  const text = td.innerText.trim();
                  return text.match(/↑|外环|富华路/u) !== null;
                }).cellIndex + 1;
              } else {
                dl.push(
                  ...span.filter((td) => {
                    const text = td.innerText.trim();
                    return text.match(/↓|内环|跳磴南/u) !== null;
                  }).map((td) => td.cellIndex + 1)
                );
                ul.push(
                  ...span.filter((td) => {
                    const text = td.innerText.trim();
                    return text.match(/↑|外环|富华路/u) !== null;
                  }).map((td) => td.cellIndex + 1)
                );
              }
              i2 += terminus.colSpan;
              if (i2 >= days[0].colSpan) {
                break;
              }
            }
            offsetLast[0] = dl.trySingle();
            offsetLast[1] = ul.trySingle();
          } else {
            throw termini.length;
          }
          const rows = [...table.tBodies[0].rows].map((row) => [...row.cells]);
          const timetable = new Map();
          for (const row of rows) {
            if (row.length == 0) {
              break;
            }
            const name = row[0].innerText.trim().replace("航站楼", "");
            if (!name || name == "--") {
              break;
            }
            timetable.set(
              name,
              parseRow(row, dayOffsets, offsetFirst, offsetLast)
            );
          }
          navigator.clipboard.writeText(luaifyTimetable(timetable));
        };
        attachButton(caption, copyData);
      });
    }
  };
  function parseCell(cell) {
    return cell.innerText.trim().replace(/——|[((]到达[))]/u, "");
  }
  const WHRT = {
    bootstrap: () => {
      const title = document.querySelector("h1");
      const copyData = function() {
        const timetable = new Map();
        const tables = document.querySelectorAll("table.Table");
        tables.forEach((table) => {
          const termini = [
            ...table.querySelector("tr:has(td + td)").querySelectorAll("td")
          ];
          const sub_termini = [
            ...table.querySelector("tr:not(:has(td[colspan]))").querySelectorAll("td")
          ];
          assert(termini.length == 2, "Invalid # of termini.");
          const terminiSpans = termini.map((td) => td.colSpan);
          assert(terminiSpans[0] >= 3, "Invalid termini span.");
          assert(terminiSpans[1] >= 3, "Invalid termini span.");
          const offsetFirst = [
            sub_termini.slice(0, terminiSpans[0]).find((td) => td.innerText.trim().startsWith("首班车")).cellIndex,
            sub_termini.find((td) => td.innerText.trim().startsWith("首班车")).cellIndex
          ];
          const offsetLast = [
            sub_termini.slice(terminiSpans[0]).filter((td) => td.innerText.trim().startsWith("末班车")).map((td) => td.cellIndex).trySingle(),
            sub_termini.slice(0, terminiSpans[0]).filter((td) => td.innerText.trim().startsWith("末班车")).map((td) => td.cellIndex).trySingle()
          ];
          offsetLast.forEach((o2) => {
            if (o2 instanceof Array) {
              o2.unshift(o2.pop());
            }
          });
          const nameOffset = sub_termini.slice(terminiSpans[0]).find((td) => td.innerText.trim().startsWith("车站")).cellIndex;
          assert(nameOffset > 0, "Invalid name offset.");
          const rows = [
            ...table.querySelectorAll(
              "tr:not(:has(td[colspan]))"
            )
          ].slice(1);
          for (const row of rows) {
            const name = row.cells[nameOffset].innerText.trim();
            const up_row = rows[rows.length - 1 - rows.indexOf(row)];
            assert(up_row !== void 0, "Invalid row.");
            if (!timetable.has(name)) {
              timetable.set(name, []);
            }
            timetable.get(name).push([
              [
                parseCell(row.cells[offsetFirst[0]]),
                parseCell(up_row.cells[offsetFirst[1]])
              ],
              [
                offsetLast[0] instanceof Array ? offsetLast[0].map((i2) => parseCell(row.cells[i2])) : parseCell(row.cells[offsetLast[0]]),
                offsetLast[1] instanceof Array ? offsetLast[1].map((i2) => parseCell(up_row.cells[i2])) : parseCell(up_row.cells[offsetLast[1]])
              ]
            ]);
          }
        });
        navigator.clipboard.writeText(luaifyTimetable(timetable));
      };
      attachButton(title, copyData);
    }
  };
  if (!Array.prototype.single) {
    Object.defineProperty(Array.prototype, "single", {
      value: function(predicate) {
        const target = predicate ? this.filter(predicate) : this;
        if (target.length === 0) {
          throw new Error("Sequence contains no matching element");
        }
        if (target.length > 1) {
          throw new Error("Sequence contains more than one matching element");
        }
        return target[0];
      },
      enumerable: false,
      configurable: true,
      writable: true
    });
    Object.defineProperty(Array.prototype, "trySingle", {
      value: function(predicate) {
        const target = predicate ? this.filter(predicate) : this;
        if (target.length === 0 || target.length > 1) {
          return target;
        }
        return target[0];
      }
    });
  }
  switch (new URL(document.URL).hostname) {
    case "www.cqmetro.cn":
      CRT.bootstrap();
      break;
    case "www.wuhanrt.com":
      WHRT.bootstrap();
      break;
  }

})();