timetable2lua

Export timetable data as lua table.

Du musst eine Erweiterung wie Tampermonkey, Greasemonkey oder Violentmonkey installieren, um dieses Skript zu installieren.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

Sie müssten eine Skript Manager Erweiterung installieren damit sie dieses Skript installieren können

(Ich habe schon ein Skript Manager, Lass mich es installieren!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         timetable2lua
// @namespace    https://dvxg.de/
// @version      0.2.6
// @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/
// @match        https://www.wuhanrt.com/news-content.html*
// ==/UserScript==

(function() {
	"use strict";
	var __create = Object.create;
	var __defProp = Object.defineProperty;
	var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
	var __getOwnPropNames = Object.getOwnPropertyNames;
	var __getProtoOf = Object.getPrototypeOf;
	var __hasOwnProp = Object.prototype.hasOwnProperty;
	var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
	var __copyProps = (to, from, except, desc) => {
		if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
			key = keys[i];
			if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
				get: ((k) => from[k]).bind(null, key),
				enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
			});
		}
		return to;
	};
	var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
		value: mod,
		enumerable: true
	}) : target, mod));
	function t(t) {
		return t && t.__esModule && Object.prototype.hasOwnProperty.call(t, "default") ? t.default : t;
	}
	var r, e, n, o, u, f, i = { exports: {} }, c = { exports: {} };
	function l() {
		return r || (r = 1, function(t) {
			var r = function() {
				function t(r, n, o, u) {
					"object" == typeof n && (o = n.depth, u = n.prototype, n.filter, n = n.circular);
					var f = [], i = [], c = "undefined" != typeof Buffer;
					return void 0 === n && (n = !0), void 0 === o && (o = Infinity), function r(o, l) {
						if (null === o) return null;
						if (0 == l) return o;
						var s, a;
						if ("object" != typeof o) return o;
						if (t.__isArray(o)) s = [];
						else if (t.__isRegExp(o)) s = new RegExp(o.source, e(o)), o.lastIndex && (s.lastIndex = o.lastIndex);
						else if (t.__isDate(o)) s = new Date(o.getTime());
						else {
							if (c && Buffer.isBuffer(o)) return s = Buffer.allocUnsafe ? Buffer.allocUnsafe(o.length) : new Buffer(o.length), o.copy(s), s;
							void 0 === u ? (a = Object.getPrototypeOf(o), s = Object.create(a)) : (s = Object.create(u), a = u);
						}
						if (n) {
							var p = f.indexOf(o);
							if (-1 != p) return i[p];
							f.push(o), i.push(s);
						}
						for (var g in o) {
							var y;
							a && (y = Object.getOwnPropertyDescriptor(a, g)), y && null == y.set || (s[g] = r(o[g], l - 1));
						}
						return s;
					}(r, o);
				}
				function r(t) {
					return Object.prototype.toString.call(t);
				}
				function e(t) {
					var r = "";
					return t.global && (r += "g"), t.ignoreCase && (r += "i"), t.multiline && (r += "m"), r;
				}
				return t.clonePrototype = function(t) {
					if (null === t) return null;
					var r = function() {};
					return r.prototype = t, new r();
				}, t.__objToStr = r, t.__isDate = function(t) {
					return "object" == typeof t && "[object Date]" === r(t);
				}, t.__isArray = function(t) {
					return "object" == typeof t && "[object Array]" === r(t);
				}, t.__isRegExp = function(t) {
					return "object" == typeof t && "[object RegExp]" === r(t);
				}, t.__getRegExpFlags = e, t;
			}();
			t.exports && (t.exports = r);
		}(c)), c.exports;
	}
	function s() {
		if (n) return e;
		n = 1;
		var t = l();
		return e = function(r, e) {
			return r = r || {}, Object.keys(e).forEach((function(n) {
				void 0 === r[n] && (r[n] = t(e[n]));
			})), r;
		};
	}
	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 g = t(function() {
		if (f) return i.exports;
		f = 1;
		var t = s(), r = a(), e = {
			nul: 0,
			control: 0
		};
		function n(t, r) {
			if ("string" != typeof t) return o(t, r);
			for (var e = 0, n = 0; n < t.length; n++) {
				var u = o(t.charCodeAt(n), r);
				if (u < 0) return -1;
				e += u;
			}
			return e;
		}
		function o(t, e) {
			return 0 === t ? e.nul : t < 32 || t >= 127 && t < 160 ? e.control : function(t) {
				var e, n = 0, o = r.length - 1;
				if (t < r[0][0] || t > r[o][1]) return !1;
				for (; o >= n;) if (e = Math.floor((n + o) / 2), t > r[e][1]) n = e + 1;
				else {
					if (!(t < r[e][0])) return !0;
					o = e - 1;
				}
				return !1;
			}(t) ? 0 : 1 + (t >= 4352 && (t <= 4447 || 9001 == t || 9002 == t || t >= 11904 && t <= 42191 && 12351 != t || t >= 44032 && t <= 55203 || t >= 63744 && t <= 64255 || t >= 65040 && t <= 65049 || t >= 65072 && t <= 65135 || t >= 65280 && t <= 65376 || t >= 65504 && t <= 65510 || t >= 131072 && t <= 196605 || t >= 196608 && t <= 262141));
		}
		return i.exports = function(t) {
			return n(t, e);
		}, i.exports.config = function(r) {
			return r = t(r || {}, e), function(t) {
				return n(t, r);
			};
		}, i.exports;
	}());
	function y(t, r, e = {}) {
		const n = "number" == typeof t;
		let o = n ? r : t;
		const u = n ? t : r;
		"string" == typeof e && (e = { char: e }), e.char || (e.char = " "), e.strip || (e.strip = !1), "string" != typeof o && (o = o.toString());
		let f = null, i = "";
		if (e.colors) f = o.replace(/\x1B\[(?:[0-9]{1,2}(?:;[0-9]{1,2})?)?[m|K]/g, "");
		const c = !0 === e.fixed_width ? u - (f || o).length : u - g.config(e.wcwidth_options || {})(f || o);
		return c < 0 ? e.strip ? n ? o.substr(-1 * u) : o.substr(0, u) : o : (i += e.char.repeat(c), n ? i + o : o + i);
	}
	var require_clone = __commonJSMin(((exports, module) => {
		var clone = (function() {
			"use strict";
			function clone(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(parent, depth) {
					if (parent === null) return null;
					if (depth == 0) return parent;
					var child;
					var proto;
					if (typeof parent != "object") return parent;
					if (clone.__isArray(parent)) child = [];
					else if (clone.__isRegExp(parent)) {
						child = new RegExp(parent.source, __getRegExpFlags(parent));
						if (parent.lastIndex) child.lastIndex = parent.lastIndex;
					} else if (clone.__isDate(parent)) child = new Date(parent.getTime());
					else if (useBuffer && Buffer.isBuffer(parent)) {
						if (Buffer.allocUnsafe) child = Buffer.allocUnsafe(parent.length);
						else child = new Buffer(parent.length);
						parent.copy(child);
						return child;
					} else if (typeof prototype == "undefined") {
						proto = Object.getPrototypeOf(parent);
						child = Object.create(proto);
					} else {
						child = Object.create(prototype);
						proto = prototype;
					}
					if (circular) {
						var index = allParents.indexOf(parent);
						if (index != -1) return allChildren[index];
						allParents.push(parent);
						allChildren.push(child);
					}
					for (var i in parent) {
						var attrs;
						if (proto) attrs = Object.getOwnPropertyDescriptor(proto, i);
						if (attrs && attrs.set == null) continue;
						child[i] = _clone(parent[i], depth - 1);
					}
					return child;
				}
				return _clone(parent, depth);
			}
			clone.clonePrototype = function clonePrototype(parent) {
				if (parent === null) return null;
				var c = function() {};
				c.prototype = parent;
				return new c();
			};
			function __objToStr(o) {
				return Object.prototype.toString.call(o);
			}
			clone.__objToStr = __objToStr;
			function __isDate(o) {
				return typeof o === "object" && __objToStr(o) === "[object Date]";
			}
			clone.__isDate = __isDate;
			function __isArray(o) {
				return typeof o === "object" && __objToStr(o) === "[object Array]";
			}
			clone.__isArray = __isArray;
			function __isRegExp(o) {
				return typeof o === "object" && __objToStr(o) === "[object RegExp]";
			}
			clone.__isRegExp = __isRegExp;
			function __getRegExpFlags(re) {
				var flags = "";
				if (re.global) flags += "g";
				if (re.ignoreCase) flags += "i";
				if (re.multiline) flags += "m";
				return flags;
			}
			clone.__getRegExpFlags = __getRegExpFlags;
			return clone;
		})();
		if (typeof module === "object" && module.exports) module.exports = clone;
	}));
	var require_defaults = __commonJSMin(((exports, module) => {
		var clone = require_clone();
		module.exports = function(options, defaults) {
			options = options || {};
			Object.keys(defaults).forEach(function(key) {
				if (typeof options[key] === "undefined") options[key] = clone(defaults[key]);
			});
			return options;
		};
	}));
	var require_combining = __commonJSMin(((exports, module) => {
		module.exports = [
			[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 import_wcwidth = __toESM(__commonJSMin(((exports, module) => {
		var defaults = require_defaults();
		var combining = require_combining();
		var DEFAULTS = {
			nul: 0,
			control: 0
		};
		module.exports = function wcwidth(str) {
			return wcswidth(str, DEFAULTS);
		};
		module.exports.config = function(opts) {
			opts = defaults(opts || {}, DEFAULTS);
			return function wcwidth(str) {
				return wcswidth(str, opts);
			};
		};
		function wcswidth(str, opts) {
			if (typeof str !== "string") return wcwidth(str, opts);
			var s = 0;
			for (var i = 0; i < str.length; i++) {
				var n = wcwidth(str.charCodeAt(i), opts);
				if (n < 0) return -1;
				s += n;
			}
			return s;
		}
		function wcwidth(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 = combining.length - 1;
			var mid;
			if (ucs < combining[0][0] || ucs > combining[max][1]) return false;
			while (max >= min) {
				mid = Math.floor((min + max) / 2);
				if (ucs > combining[mid][1]) min = mid + 1;
				else if (ucs < combining[mid][0]) max = mid - 1;
				else return true;
			}
			return false;
		}
	}))(), 1);
	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, y) {
		if (x instanceof Array) if (x.length != y.length) return false;
		else return x.filter((v, i) => !compareNestedStringArray(v, y[i])).length == 0;
		else return x === y;
	}
	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}'`);
	}
	var Timetable = {
		reverse: function(timetable) {
			return new Map([...timetable.entries()].reverse().map(([name, times]) => [name, times.map(([first, last]) => [first.reverse(), last.reverse()])]));
		},
		luaify: function(timetable) {
			timetable = deduplicateDays(timetable);
			const padding = Math.max(...[...timetable.keys()].map((str) => (0, import_wcwidth.default)(str))) + 4;
			return `
\t\t\tstations = ${luaifyNestedStringArray([...timetable.keys()])},
\t\t\tdata = {
${Array.from(timetable).map(([key, value]) => `\t\t\t\t${y(`['${key}']`, padding)} = ${luaifyNestedStringArray(value, 7)},`).join("\n")}
\t\t\t}`;
		}
	};
	function parseCell$1(cell, closed) {
		return cell.innerText.trim().replace(/\p{Pd}+/u, 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((i) => parseCell$1(cells[i + dayOffset], closed)), last.map((index) => index instanceof Array ? index.map((i) => parseCell$1(cells[i + dayOffset], closed)) : parseCell$1(cells[index + dayOffset], closed))]);
	}
	function parseRows(rows, days, first, last) {
		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, days, first, last));
		}
		return timetable;
	}
	var CRT = { bootstrap: function() {
		document.querySelectorAll(".schedule-card").forEach((card) => {
			const caption = [...card.querySelectorAll(".schedule-card-head")].single();
			const copyData = function() {
				const table = [...card.querySelectorAll("table")].single();
				const [thDays, thTimes, thTermini] = (() => {
					const rows = table.tHead?.rows;
					if (rows?.length == 3) return [
						0,
						1,
						2
					].map((r) => [...table.tHead.rows[r].querySelectorAll("th:not([rowspan])")].filter((th) => th.innerText.trim().length > 2));
					else if (rows?.length == 2) {
						function getCommon(left, right) {
							return left.split("").filter((char, c) => char == right[c]).join("");
						}
						let days;
						if (rows[0].cells.length == 5) {
							days = [document.createElement("th"), document.createElement("th")];
							days[0].innerText = getCommon(rows[0].cells[1].innerText.trim(), rows[0].cells[2].innerText.trim());
							days[1].innerText = getCommon(rows[0].cells[3].innerText.trim(), rows[0].cells[4].innerText.trim());
							days.forEach((th) => th.setAttribute("colspan", (rows[0].cells[1].colSpan * 2).toString()));
						} else throw rows[0].cells;
						return [days, ...[0, 1].map((r) => [...table.tHead.rows[r].querySelectorAll("th:not([rowspan])")].filter((th) => th.innerText.trim().length > 2))];
					} else throw rows?.[0]?.cells;
				})();
				if (thDays.length == 0) {
					thDays.push(document.createElement("th"));
					thDays[0].colSpan = thTimes.map((g) => g.colSpan).reduce((p, c) => p + c, 0);
				}
				assert([1, 2].includes(thDays.length), "Invalid # of days.");
				assert(thTimes.length % thDays.length == 0 && thTimes.length >= 2, "Invalid # of termini.");
				assert(thTermini.length % thDays.length == 0 && thTermini.length >= 2, "Invalid # of child termini.");
				const dayWidth = thDays[0].colSpan;
				const days = [];
				for (let i = 0; i < thDays.length; i++) {
					assert(thDays[i].colSpan == dayWidth);
					days.push(i * dayWidth);
				}
				const first = [1, 2];
				const last = [3, 4];
				const dl = [];
				const ul = [];
				if (thTimes.length / thDays.length == 2 && thTermini.length / thTimes.length == 2) {
					if (thTermini[0].innerText.trim().includes(table.tBodies[0].rows[0].cells[0].innerText.trim())) {
						first.reverse();
						last.reverse();
					}
				} else if (thTimes.length / thDays.length >= 2) {
					let i = 0;
					for (const terminus of thTimes) {
						const span = thTermini.slice(i, i + terminus.colSpan);
						if (terminus.innerText.trim().startsWith("首班车")) {
							first[0] = span.single((td) => {
								return td.innerText.trim().match(/↓|内环/u) !== null;
							}).cellIndex + 1;
							first[1] = span.single((td) => {
								return td.innerText.trim().match(/↑|外环/u) !== null;
							}).cellIndex + 1;
						} else {
							dl.push(...span.filter((td) => {
								return td.innerText.trim().match(/↓|内环/u) !== null;
							}).map((td) => td.cellIndex + 1));
							ul.push(...span.filter((td) => {
								return td.innerText.trim().match(/↑|外环/u) !== null;
							}).map((td) => td.cellIndex + 1));
						}
						i += terminus.colSpan;
						if (i >= thDays[0].colSpan) break;
					}
					[last[0], last[1]] = dl.length == 1 && ul.length == 1 ? [dl[0], ul[0]] : [dl, ul];
				} else throw thTimes.length;
				let timetable = parseRows([...table.tBodies[0].rows].map((row) => [...row.cells]), days, first, last);
				if (caption.innerText.includes("璧铜") && timetable.keys().next().value?.includes("璧山")) timetable = Timetable.reverse(timetable);
				navigator.clipboard.writeText(Timetable.luaify(timetable));
			};
			attachButton(caption, copyData);
			new MutationObserver(() => {
				if (caption.querySelector("a") == null) {
					attachButton(caption, copyData);
					const rows = [...[...card.querySelectorAll("table")].single().tBodies[0].rows].map((row) => [...row.cells]);
					const i = rows.findIndex((row) => row.every((cell) => cell.innerText.trim().length <= 2));
					if (i < 0) return;
					const j = rows.findIndex((row, r) => r > i && row.some((cell) => cell.innerText.includes("↓")));
					if (j < 0) return;
					attachButton(rows[j][0], function() {
						const d = rows[j].map((cell, c) => cell.innerText.includes("↓") ? c : null).filter((c) => c !== null);
						const u = rows[j].map((cell, c) => cell.innerText.includes("↑") ? c : null).filter((c) => c !== null);
						let days;
						assert(d.length == u.length && d.length >= 2, "Invalid # of termini.");
						const first = [d[0], u[0]];
						const last = [d[1], u[1]];
						if (d.length == 2) days = [d[0] - 1];
						else if (d.length == 4) days = [d[0] - 1, d[2] - 1];
						else throw [d, u];
						const timetable = parseRows(rows.slice(i + 2), days, first, last);
						navigator.clipboard.writeText(Timetable.luaify(timetable));
					});
				}
			}).observe(card, {
				childList: true,
				subtree: true
			});
		});
	} };
	function parseCell(cell) {
		return cell.innerText.trim().replace(/\p{Pd}+|[((]到达[))]/u, "");
	}
	var WHRT = { bootstrap: () => {
		const title = document.querySelector("h1");
		const copyData = function() {
			const timetable = new Map();
			document.querySelectorAll("table.Table").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 dl = sub_termini.slice(terminiSpans[0]).filter((td) => td.innerText.trim().startsWith("末班车")).map((td) => td.cellIndex);
				const ul = sub_termini.slice(0, terminiSpans[0]).filter((td) => td.innerText.trim().startsWith("末班车")).map((td) => td.cellIndex);
				const offsetLast = dl.length == 1 && ul.length == 1 ? [dl[0], ul[0]] : [dl, ul];
				offsetLast.forEach((o) => {
					if (o instanceof Array) o.unshift(o.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((i) => parseCell(row.cells[i])) : parseCell(row.cells[offsetLast[0]]), offsetLast[1] instanceof Array ? offsetLast[1].map((i) => parseCell(up_row.cells[i])) : parseCell(up_row.cells[offsetLast[1]])]]);
				}
			});
			navigator.clipboard.writeText(Timetable.luaify(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
	});
	switch (new URL(document.URL).hostname) {
		case "www.cqmetro.cn":
			CRT.bootstrap();
			break;
		case "www.wuhanrt.com":
			WHRT.bootstrap();
			break;
	}
})();