timetable2lua

Export timetable data as lua table.

2026-03-01 기준 버전입니다. 최신 버전을 확인하세요.

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         timetable2lua
// @namespace    https://dvxg.de/
// @version      0.2.1
// @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];
          if (termini.length / days.length == 2 && sub_termini.length / termini.length == 2) ;
          else if (termini.length / days.length >= 2) {
            offsetLast[0] = [];
            offsetLast[1] = [];
            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 {
                offsetLast[0].push(
                  ...span.filter((td) => {
                    const text = td.innerText.trim();
                    return text.match(/↓|内环|跳磴南/u) !== null;
                  }).map((td) => td.cellIndex + 1)
                );
                offsetLast[1].push(
                  ...span.filter((td) => {
                    const text = td.innerText.trim();
                    return text.match(/↑|外环|富华路/u) !== null;
                  }).map((td) => td.cellIndex + 1)
                );
              }
              offsetLast[0] = offsetLast[0].trySingle();
              offsetLast[1] = offsetLast[1].trySingle();
              i2 += terminus.colSpan;
              if (i2 >= days[0].colSpan) {
                break;
              }
            }
          } 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;
  }

})();