FZ Tracker

Track game versions you finished playing on F95Zone.

  1. // ==UserScript==
  2. // @name FZ Tracker
  3. // @namespace https://f95zone.to/threads/186670/
  4. // @version 1.2.0
  5. // @author feeling_blue
  6. // @description Track game versions you finished playing on F95Zone.
  7. // @license MIT
  8. // @icon https://external-content.duckduckgo.com/ip3/f95zone.to.ico
  9. // @supportURL https://f95zone.to/threads/186670/
  10. // @match https://f95zone.to/threads/*
  11. // @match https://f95zone.to/sam/latest_alpha/*
  12. // @require https://unpkg.com/vue@3.3.13/dist/vue.global.prod.js
  13. // @require https://unpkg.com/@vueuse/shared@10.7.0/index.iife.min.js
  14. // @require data:application/javascript,%3Bwindow.Vue%3DVue%3B
  15. // @require https://unpkg.com/@vueuse/core@10.7.0/index.iife.min.js
  16. // @require https://unpkg.com/element-plus@2.4.4/dist/index.full.min.js
  17. // @require https://unpkg.com/@element-plus/icons-vue@2.3.1/dist/index.iife.min.js
  18. // @resource element-plus/dist/index.css https://unpkg.com/element-plus@2.4.4/dist/index.css
  19. // @grant GM_addStyle
  20. // @grant GM_getResourceText
  21. // ==/UserScript==
  22.  
  23. (r=>{if(typeof GM_addStyle=="function"){GM_addStyle(r);return}const o=document.createElement("style");o.textContent=r,document.head.append(o)})(' html.dark{color-scheme:dark;--el-color-primary:#409eff;--el-color-primary-light-3:#3375b9;--el-color-primary-light-5:#2a598a;--el-color-primary-light-7:#213d5b;--el-color-primary-light-8:#1d3043;--el-color-primary-light-9:#18222c;--el-color-primary-dark-2:#66b1ff;--el-color-success:#67c23a;--el-color-success-light-3:#4e8e2f;--el-color-success-light-5:#3e6b27;--el-color-success-light-7:#2d481f;--el-color-success-light-8:#25371c;--el-color-success-light-9:#1c2518;--el-color-success-dark-2:#85ce61;--el-color-warning:#e6a23c;--el-color-warning-light-3:#a77730;--el-color-warning-light-5:#7d5b28;--el-color-warning-light-7:#533f20;--el-color-warning-light-8:#3e301c;--el-color-warning-light-9:#292218;--el-color-warning-dark-2:#ebb563;--el-color-danger:#f56c6c;--el-color-danger-light-3:#b25252;--el-color-danger-light-5:#854040;--el-color-danger-light-7:#582e2e;--el-color-danger-light-8:#412626;--el-color-danger-light-9:#2b1d1d;--el-color-danger-dark-2:#f78989;--el-color-error:#f56c6c;--el-color-error-light-3:#b25252;--el-color-error-light-5:#854040;--el-color-error-light-7:#582e2e;--el-color-error-light-8:#412626;--el-color-error-light-9:#2b1d1d;--el-color-error-dark-2:#f78989;--el-color-info:#909399;--el-color-info-light-3:#6b6d71;--el-color-info-light-5:#525457;--el-color-info-light-7:#393a3c;--el-color-info-light-8:#2d2d2f;--el-color-info-light-9:#202121;--el-color-info-dark-2:#a6a9ad;--el-box-shadow:0px 12px 32px 4px rgba(0, 0, 0, .36),0px 8px 20px rgba(0, 0, 0, .72);--el-box-shadow-light:0px 0px 12px rgba(0, 0, 0, .72);--el-box-shadow-lighter:0px 0px 6px rgba(0, 0, 0, .72);--el-box-shadow-dark:0px 16px 48px 16px rgba(0, 0, 0, .72),0px 12px 32px #000000,0px 8px 16px -8px #000000;--el-bg-color-page:#0a0a0a;--el-bg-color:#141414;--el-bg-color-overlay:#1d1e1f;--el-text-color-primary:#E5EAF3;--el-text-color-regular:#CFD3DC;--el-text-color-secondary:#A3A6AD;--el-text-color-placeholder:#8D9095;--el-text-color-disabled:#6C6E72;--el-border-color-darker:#636466;--el-border-color-dark:#58585B;--el-border-color:#4C4D4F;--el-border-color-light:#414243;--el-border-color-lighter:#363637;--el-border-color-extra-light:#2B2B2C;--el-fill-color-darker:#424243;--el-fill-color-dark:#39393A;--el-fill-color:#303030;--el-fill-color-light:#262727;--el-fill-color-lighter:#1D1D1D;--el-fill-color-extra-light:#191919;--el-fill-color-blank:transparent;--el-mask-color:rgba(0, 0, 0, .8);--el-mask-color-extra-light:rgba(0, 0, 0, .3)}html.dark .el-button{--el-button-disabled-text-color:rgba(255, 255, 255, .5)}html.dark .el-card{--el-card-bg-color:var(--el-bg-color-overlay)}html.dark .el-empty{--el-empty-fill-color-0:var(--el-color-black);--el-empty-fill-color-1:#4b4b52;--el-empty-fill-color-2:#36383d;--el-empty-fill-color-3:#1e1e20;--el-empty-fill-color-4:#262629;--el-empty-fill-color-5:#202124;--el-empty-fill-color-6:#212224;--el-empty-fill-color-7:#1b1c1f;--el-empty-fill-color-8:#1c1d1f;--el-empty-fill-color-9:#18181a}#--unocss--{layer:__ALL__}.el-form-item.button-group[data-v-9a23e112] .el-form-item__content{justify-content:flex-end}.fz-tracker .p-body-header{z-index:5}.fz-tracker .p-body{z-index:4}.fz-tracker .el-icon svg path{fill:currentColor}#--unocss-layer-start--__ALL__--{start:__ALL__}*,:before,:after{--un-rotate:0;--un-rotate-x:0;--un-rotate-y:0;--un-rotate-z:0;--un-scale-x:1;--un-scale-y:1;--un-scale-z:1;--un-skew-x:0;--un-skew-y:0;--un-translate-x:0;--un-translate-y:0;--un-translate-z:0;--un-pan-x: ;--un-pan-y: ;--un-pinch-zoom: ;--un-scroll-snap-strictness:proximity;--un-ordinal: ;--un-slashed-zero: ;--un-numeric-figure: ;--un-numeric-spacing: ;--un-numeric-fraction: ;--un-border-spacing-x:0;--un-border-spacing-y:0;--un-ring-offset-shadow:0 0 rgb(0 0 0 / 0);--un-ring-shadow:0 0 rgb(0 0 0 / 0);--un-shadow-inset: ;--un-shadow:0 0 rgb(0 0 0 / 0);--un-ring-inset: ;--un-ring-offset-width:0px;--un-ring-offset-color:#fff;--un-ring-width:0px;--un-ring-color:rgb(147 197 253 / .5);--un-blur: ;--un-brightness: ;--un-contrast: ;--un-drop-shadow: ;--un-grayscale: ;--un-hue-rotate: ;--un-invert: ;--un-saturate: ;--un-sepia: ;--un-backdrop-blur: ;--un-backdrop-brightness: ;--un-backdrop-contrast: ;--un-backdrop-grayscale: ;--un-backdrop-hue-rotate: ;--un-backdrop-invert: ;--un-backdrop-opacity: ;--un-backdrop-saturate: ;--un-backdrop-sepia: }::backdrop{--un-rotate:0;--un-rotate-x:0;--un-rotate-y:0;--un-rotate-z:0;--un-scale-x:1;--un-scale-y:1;--un-scale-z:1;--un-skew-x:0;--un-skew-y:0;--un-translate-x:0;--un-translate-y:0;--un-translate-z:0;--un-pan-x: ;--un-pan-y: ;--un-pinch-zoom: ;--un-scroll-snap-strictness:proximity;--un-ordinal: ;--un-slashed-zero: ;--un-numeric-figure: ;--un-numeric-spacing: ;--un-numeric-fraction: ;--un-border-spacing-x:0;--un-border-spacing-y:0;--un-ring-offset-shadow:0 0 rgb(0 0 0 / 0);--un-ring-shadow:0 0 rgb(0 0 0 / 0);--un-shadow-inset: ;--un-shadow:0 0 rgb(0 0 0 / 0);--un-ring-inset: ;--un-ring-offset-width:0px;--un-ring-offset-color:#fff;--un-ring-width:0px;--un-ring-color:rgb(147 197 253 / .5);--un-blur: ;--un-brightness: ;--un-contrast: ;--un-drop-shadow: ;--un-grayscale: ;--un-hue-rotate: ;--un-invert: ;--un-saturate: ;--un-sepia: ;--un-backdrop-blur: ;--un-backdrop-brightness: ;--un-backdrop-contrast: ;--un-backdrop-grayscale: ;--un-backdrop-hue-rotate: ;--un-backdrop-invert: ;--un-backdrop-opacity: ;--un-backdrop-saturate: ;--un-backdrop-sepia: }.m-l-\\[10px\\]{margin-left:10px}.m-t-\\[5px\\]{margin-top:5px}.ml-4{margin-left:1rem}.inline-block{display:inline-block}[size~="20"]{width:5rem;height:5rem}[size~="35"]{width:8.75rem;height:8.75rem}.cursor-pointer{cursor:pointer}.border-rd-\\[3px\\]{border-radius:3px}.p-\\[5px\\]{padding:5px}.p-1{padding:.25rem}.p-l-\\[5px\\]{padding-left:5px}.v-middle{vertical-align:middle}.v-sub{vertical-align:sub}.text-\\[0\\.75em\\]{font-size:.75em}.text-2xl{font-size:1.5rem;line-height:2rem}.text-\\[rgb\\(147\\,152\\,160\\)\\]{--un-text-opacity:1;color:rgb(147 152 160 / var(--un-text-opacity))}.text-\\#ec5555{--un-text-opacity:1;color:rgb(236 85 85 / var(--un-text-opacity))}.text-white{--un-text-opacity:1;color:rgb(255 255 255 / var(--un-text-opacity))}.opacity-70{opacity:.7}.hover\\:opacity-100:hover{opacity:1}.backdrop-blur-5{--un-backdrop-blur:blur(5px);-webkit-backdrop-filter:var(--un-backdrop-blur) var(--un-backdrop-brightness) var(--un-backdrop-contrast) var(--un-backdrop-grayscale) var(--un-backdrop-hue-rotate) var(--un-backdrop-invert) var(--un-backdrop-opacity) var(--un-backdrop-saturate) var(--un-backdrop-sepia);backdrop-filter:var(--un-backdrop-blur) var(--un-backdrop-brightness) var(--un-backdrop-contrast) var(--un-backdrop-grayscale) var(--un-backdrop-hue-rotate) var(--un-backdrop-invert) var(--un-backdrop-opacity) var(--un-backdrop-saturate) var(--un-backdrop-sepia)}.blur{--un-blur:blur(8px);filter:var(--un-blur) var(--un-brightness) var(--un-contrast) var(--un-drop-shadow) var(--un-grayscale) var(--un-hue-rotate) var(--un-invert) var(--un-saturate) var(--un-sepia)}#--unocss-layer-end--__ALL__--{end:__ALL__} ');
  24.  
  25. (function (vue, ElementPlus, core, iconsVue) {
  26. 'use strict';
  27.  
  28. const cssLoader = (e) => {
  29. const t = GM_getResourceText(e);
  30. return GM_addStyle(t), t;
  31. };
  32. cssLoader("element-plus/dist/index.css");
  33. function gameStorageKey(gameID) {
  34. return `${gameID}-tracker`;
  35. }
  36. const _hoisted_1$1 = { class: "backdrop-blur-5 text-[0.75em] p-[5px] p-l-[5px] m-l-[10px] m-t-[5px] border-rd-[3px]" };
  37. const _sfc_main$2 = {
  38. __name: "LatestRecordTag",
  39. setup(__props) {
  40. const gameInfo = vue.inject("gameInfo");
  41. const gameData = JSON.parse(localStorage.getItem(gameStorageKey(gameInfo.ID)));
  42. const lastRecord = gameData.records[gameData.records.length - 1];
  43. const timeAgo = core.useTimeAgo(lastRecord ? lastRecord.time : null);
  44. let versionText = "";
  45. if (lastRecord && lastRecord.version !== gameInfo.version)
  46. versionText = `[${lastRecord.version}] `;
  47. return (_ctx, _cache) => {
  48. return vue.openBlock(), vue.createElementBlock("div", _hoisted_1$1, vue.toDisplayString(vue.unref(versionText)) + vue.toDisplayString(vue.unref(lastRecord) ? vue.unref(timeAgo) : ""), 1);
  49. };
  50. }
  51. };
  52. var commonjsGlobal = typeof globalThis !== "undefined" ? globalThis : typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : {};
  53. function getDefaultExportFromCjs(x) {
  54. return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x["default"] : x;
  55. }
  56. var dayjs_min = { exports: {} };
  57. (function(module, exports) {
  58. !function(t, e) {
  59. module.exports = e();
  60. }(commonjsGlobal, function() {
  61. var t = 1e3, e = 6e4, n = 36e5, r = "millisecond", i = "second", s = "minute", u = "hour", a = "day", o = "week", c = "month", f = "quarter", h = "year", d = "date", l = "Invalid Date", $ = /^(\d{4})[-/]?(\d{1,2})?[-/]?(\d{0,2})[Tt\s]*(\d{1,2})?:?(\d{1,2})?:?(\d{1,2})?[.:]?(\d+)?$/, y = /\[([^\]]+)]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g, M = { name: "en", weekdays: "Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"), months: "January_February_March_April_May_June_July_August_September_October_November_December".split("_"), ordinal: function(t2) {
  62. var e2 = ["th", "st", "nd", "rd"], n2 = t2 % 100;
  63. return "[" + t2 + (e2[(n2 - 20) % 10] || e2[n2] || e2[0]) + "]";
  64. } }, m = function(t2, e2, n2) {
  65. var r2 = String(t2);
  66. return !r2 || r2.length >= e2 ? t2 : "" + Array(e2 + 1 - r2.length).join(n2) + t2;
  67. }, v = { s: m, z: function(t2) {
  68. var e2 = -t2.utcOffset(), n2 = Math.abs(e2), r2 = Math.floor(n2 / 60), i2 = n2 % 60;
  69. return (e2 <= 0 ? "+" : "-") + m(r2, 2, "0") + ":" + m(i2, 2, "0");
  70. }, m: function t2(e2, n2) {
  71. if (e2.date() < n2.date())
  72. return -t2(n2, e2);
  73. var r2 = 12 * (n2.year() - e2.year()) + (n2.month() - e2.month()), i2 = e2.clone().add(r2, c), s2 = n2 - i2 < 0, u2 = e2.clone().add(r2 + (s2 ? -1 : 1), c);
  74. return +(-(r2 + (n2 - i2) / (s2 ? i2 - u2 : u2 - i2)) || 0);
  75. }, a: function(t2) {
  76. return t2 < 0 ? Math.ceil(t2) || 0 : Math.floor(t2);
  77. }, p: function(t2) {
  78. return { M: c, y: h, w: o, d: a, D: d, h: u, m: s, s: i, ms: r, Q: f }[t2] || String(t2 || "").toLowerCase().replace(/s$/, "");
  79. }, u: function(t2) {
  80. return void 0 === t2;
  81. } }, g = "en", D = {};
  82. D[g] = M;
  83. var p = "$isDayjsObject", S = function(t2) {
  84. return t2 instanceof _ || !(!t2 || !t2[p]);
  85. }, w = function t2(e2, n2, r2) {
  86. var i2;
  87. if (!e2)
  88. return g;
  89. if ("string" == typeof e2) {
  90. var s2 = e2.toLowerCase();
  91. D[s2] && (i2 = s2), n2 && (D[s2] = n2, i2 = s2);
  92. var u2 = e2.split("-");
  93. if (!i2 && u2.length > 1)
  94. return t2(u2[0]);
  95. } else {
  96. var a2 = e2.name;
  97. D[a2] = e2, i2 = a2;
  98. }
  99. return !r2 && i2 && (g = i2), i2 || !r2 && g;
  100. }, O = function(t2, e2) {
  101. if (S(t2))
  102. return t2.clone();
  103. var n2 = "object" == typeof e2 ? e2 : {};
  104. return n2.date = t2, n2.args = arguments, new _(n2);
  105. }, b = v;
  106. b.l = w, b.i = S, b.w = function(t2, e2) {
  107. return O(t2, { locale: e2.$L, utc: e2.$u, x: e2.$x, $offset: e2.$offset });
  108. };
  109. var _ = function() {
  110. function M2(t2) {
  111. this.$L = w(t2.locale, null, true), this.parse(t2), this.$x = this.$x || t2.x || {}, this[p] = true;
  112. }
  113. var m2 = M2.prototype;
  114. return m2.parse = function(t2) {
  115. this.$d = function(t3) {
  116. var e2 = t3.date, n2 = t3.utc;
  117. if (null === e2)
  118. return /* @__PURE__ */ new Date(NaN);
  119. if (b.u(e2))
  120. return /* @__PURE__ */ new Date();
  121. if (e2 instanceof Date)
  122. return new Date(e2);
  123. if ("string" == typeof e2 && !/Z$/i.test(e2)) {
  124. var r2 = e2.match($);
  125. if (r2) {
  126. var i2 = r2[2] - 1 || 0, s2 = (r2[7] || "0").substring(0, 3);
  127. return n2 ? new Date(Date.UTC(r2[1], i2, r2[3] || 1, r2[4] || 0, r2[5] || 0, r2[6] || 0, s2)) : new Date(r2[1], i2, r2[3] || 1, r2[4] || 0, r2[5] || 0, r2[6] || 0, s2);
  128. }
  129. }
  130. return new Date(e2);
  131. }(t2), this.init();
  132. }, m2.init = function() {
  133. var t2 = this.$d;
  134. this.$y = t2.getFullYear(), this.$M = t2.getMonth(), this.$D = t2.getDate(), this.$W = t2.getDay(), this.$H = t2.getHours(), this.$m = t2.getMinutes(), this.$s = t2.getSeconds(), this.$ms = t2.getMilliseconds();
  135. }, m2.$utils = function() {
  136. return b;
  137. }, m2.isValid = function() {
  138. return !(this.$d.toString() === l);
  139. }, m2.isSame = function(t2, e2) {
  140. var n2 = O(t2);
  141. return this.startOf(e2) <= n2 && n2 <= this.endOf(e2);
  142. }, m2.isAfter = function(t2, e2) {
  143. return O(t2) < this.startOf(e2);
  144. }, m2.isBefore = function(t2, e2) {
  145. return this.endOf(e2) < O(t2);
  146. }, m2.$g = function(t2, e2, n2) {
  147. return b.u(t2) ? this[e2] : this.set(n2, t2);
  148. }, m2.unix = function() {
  149. return Math.floor(this.valueOf() / 1e3);
  150. }, m2.valueOf = function() {
  151. return this.$d.getTime();
  152. }, m2.startOf = function(t2, e2) {
  153. var n2 = this, r2 = !!b.u(e2) || e2, f2 = b.p(t2), l2 = function(t3, e3) {
  154. var i2 = b.w(n2.$u ? Date.UTC(n2.$y, e3, t3) : new Date(n2.$y, e3, t3), n2);
  155. return r2 ? i2 : i2.endOf(a);
  156. }, $2 = function(t3, e3) {
  157. return b.w(n2.toDate()[t3].apply(n2.toDate("s"), (r2 ? [0, 0, 0, 0] : [23, 59, 59, 999]).slice(e3)), n2);
  158. }, y2 = this.$W, M3 = this.$M, m3 = this.$D, v2 = "set" + (this.$u ? "UTC" : "");
  159. switch (f2) {
  160. case h:
  161. return r2 ? l2(1, 0) : l2(31, 11);
  162. case c:
  163. return r2 ? l2(1, M3) : l2(0, M3 + 1);
  164. case o:
  165. var g2 = this.$locale().weekStart || 0, D2 = (y2 < g2 ? y2 + 7 : y2) - g2;
  166. return l2(r2 ? m3 - D2 : m3 + (6 - D2), M3);
  167. case a:
  168. case d:
  169. return $2(v2 + "Hours", 0);
  170. case u:
  171. return $2(v2 + "Minutes", 1);
  172. case s:
  173. return $2(v2 + "Seconds", 2);
  174. case i:
  175. return $2(v2 + "Milliseconds", 3);
  176. default:
  177. return this.clone();
  178. }
  179. }, m2.endOf = function(t2) {
  180. return this.startOf(t2, false);
  181. }, m2.$set = function(t2, e2) {
  182. var n2, o2 = b.p(t2), f2 = "set" + (this.$u ? "UTC" : ""), l2 = (n2 = {}, n2[a] = f2 + "Date", n2[d] = f2 + "Date", n2[c] = f2 + "Month", n2[h] = f2 + "FullYear", n2[u] = f2 + "Hours", n2[s] = f2 + "Minutes", n2[i] = f2 + "Seconds", n2[r] = f2 + "Milliseconds", n2)[o2], $2 = o2 === a ? this.$D + (e2 - this.$W) : e2;
  183. if (o2 === c || o2 === h) {
  184. var y2 = this.clone().set(d, 1);
  185. y2.$d[l2]($2), y2.init(), this.$d = y2.set(d, Math.min(this.$D, y2.daysInMonth())).$d;
  186. } else
  187. l2 && this.$d[l2]($2);
  188. return this.init(), this;
  189. }, m2.set = function(t2, e2) {
  190. return this.clone().$set(t2, e2);
  191. }, m2.get = function(t2) {
  192. return this[b.p(t2)]();
  193. }, m2.add = function(r2, f2) {
  194. var d2, l2 = this;
  195. r2 = Number(r2);
  196. var $2 = b.p(f2), y2 = function(t2) {
  197. var e2 = O(l2);
  198. return b.w(e2.date(e2.date() + Math.round(t2 * r2)), l2);
  199. };
  200. if ($2 === c)
  201. return this.set(c, this.$M + r2);
  202. if ($2 === h)
  203. return this.set(h, this.$y + r2);
  204. if ($2 === a)
  205. return y2(1);
  206. if ($2 === o)
  207. return y2(7);
  208. var M3 = (d2 = {}, d2[s] = e, d2[u] = n, d2[i] = t, d2)[$2] || 1, m3 = this.$d.getTime() + r2 * M3;
  209. return b.w(m3, this);
  210. }, m2.subtract = function(t2, e2) {
  211. return this.add(-1 * t2, e2);
  212. }, m2.format = function(t2) {
  213. var e2 = this, n2 = this.$locale();
  214. if (!this.isValid())
  215. return n2.invalidDate || l;
  216. var r2 = t2 || "YYYY-MM-DDTHH:mm:ssZ", i2 = b.z(this), s2 = this.$H, u2 = this.$m, a2 = this.$M, o2 = n2.weekdays, c2 = n2.months, f2 = n2.meridiem, h2 = function(t3, n3, i3, s3) {
  217. return t3 && (t3[n3] || t3(e2, r2)) || i3[n3].slice(0, s3);
  218. }, d2 = function(t3) {
  219. return b.s(s2 % 12 || 12, t3, "0");
  220. }, $2 = f2 || function(t3, e3, n3) {
  221. var r3 = t3 < 12 ? "AM" : "PM";
  222. return n3 ? r3.toLowerCase() : r3;
  223. };
  224. return r2.replace(y, function(t3, r3) {
  225. return r3 || function(t4) {
  226. switch (t4) {
  227. case "YY":
  228. return String(e2.$y).slice(-2);
  229. case "YYYY":
  230. return b.s(e2.$y, 4, "0");
  231. case "M":
  232. return a2 + 1;
  233. case "MM":
  234. return b.s(a2 + 1, 2, "0");
  235. case "MMM":
  236. return h2(n2.monthsShort, a2, c2, 3);
  237. case "MMMM":
  238. return h2(c2, a2);
  239. case "D":
  240. return e2.$D;
  241. case "DD":
  242. return b.s(e2.$D, 2, "0");
  243. case "d":
  244. return String(e2.$W);
  245. case "dd":
  246. return h2(n2.weekdaysMin, e2.$W, o2, 2);
  247. case "ddd":
  248. return h2(n2.weekdaysShort, e2.$W, o2, 3);
  249. case "dddd":
  250. return o2[e2.$W];
  251. case "H":
  252. return String(s2);
  253. case "HH":
  254. return b.s(s2, 2, "0");
  255. case "h":
  256. return d2(1);
  257. case "hh":
  258. return d2(2);
  259. case "a":
  260. return $2(s2, u2, true);
  261. case "A":
  262. return $2(s2, u2, false);
  263. case "m":
  264. return String(u2);
  265. case "mm":
  266. return b.s(u2, 2, "0");
  267. case "s":
  268. return String(e2.$s);
  269. case "ss":
  270. return b.s(e2.$s, 2, "0");
  271. case "SSS":
  272. return b.s(e2.$ms, 3, "0");
  273. case "Z":
  274. return i2;
  275. }
  276. return null;
  277. }(t3) || i2.replace(":", "");
  278. });
  279. }, m2.utcOffset = function() {
  280. return 15 * -Math.round(this.$d.getTimezoneOffset() / 15);
  281. }, m2.diff = function(r2, d2, l2) {
  282. var $2, y2 = this, M3 = b.p(d2), m3 = O(r2), v2 = (m3.utcOffset() - this.utcOffset()) * e, g2 = this - m3, D2 = function() {
  283. return b.m(y2, m3);
  284. };
  285. switch (M3) {
  286. case h:
  287. $2 = D2() / 12;
  288. break;
  289. case c:
  290. $2 = D2();
  291. break;
  292. case f:
  293. $2 = D2() / 3;
  294. break;
  295. case o:
  296. $2 = (g2 - v2) / 6048e5;
  297. break;
  298. case a:
  299. $2 = (g2 - v2) / 864e5;
  300. break;
  301. case u:
  302. $2 = g2 / n;
  303. break;
  304. case s:
  305. $2 = g2 / e;
  306. break;
  307. case i:
  308. $2 = g2 / t;
  309. break;
  310. default:
  311. $2 = g2;
  312. }
  313. return l2 ? $2 : b.a($2);
  314. }, m2.daysInMonth = function() {
  315. return this.endOf(c).$D;
  316. }, m2.$locale = function() {
  317. return D[this.$L];
  318. }, m2.locale = function(t2, e2) {
  319. if (!t2)
  320. return this.$L;
  321. var n2 = this.clone(), r2 = w(t2, e2, true);
  322. return r2 && (n2.$L = r2), n2;
  323. }, m2.clone = function() {
  324. return b.w(this.$d, this);
  325. }, m2.toDate = function() {
  326. return new Date(this.valueOf());
  327. }, m2.toJSON = function() {
  328. return this.isValid() ? this.toISOString() : null;
  329. }, m2.toISOString = function() {
  330. return this.$d.toISOString();
  331. }, m2.toString = function() {
  332. return this.$d.toUTCString();
  333. }, M2;
  334. }(), k = _.prototype;
  335. return O.prototype = k, [["$ms", r], ["$s", i], ["$m", s], ["$H", u], ["$W", a], ["$M", c], ["$y", h], ["$D", d]].forEach(function(t2) {
  336. k[t2[1]] = function(e2) {
  337. return this.$g(e2, t2[0], t2[1]);
  338. };
  339. }), O.extend = function(t2, e2) {
  340. return t2.$i || (t2(e2, _, O), t2.$i = true), O;
  341. }, O.locale = w, O.isDayjs = S, O.unix = function(t2) {
  342. return O(1e3 * t2);
  343. }, O.en = D[g], O.Ls = D, O.p = {}, O;
  344. });
  345. })(dayjs_min);
  346. var dayjs_minExports = dayjs_min.exports;
  347. const dayjs = /* @__PURE__ */ getDefaultExportFromCjs(dayjs_minExports);
  348. const trackerVersion = "1.2.0";
  349. const _export_sfc = (sfc, props) => {
  350. const target = sfc.__vccOpts || sfc;
  351. for (const [key, val] of props) {
  352. target[key] = val;
  353. }
  354. return target;
  355. };
  356. const _hoisted_1 = {
  357. key: 0,
  358. class: "text-2xl"
  359. };
  360. const _sfc_main$1 = {
  361. __name: "VersionTracker",
  362. setup(__props) {
  363. const gameInfo = vue.inject("gameInfo");
  364. const gameData = core.useLocalStorage(
  365. gameStorageKey(gameInfo.ID),
  366. {
  367. name: gameInfo.name,
  368. author: gameInfo.author,
  369. records: [],
  370. trackerVersion
  371. },
  372. {
  373. deep: true,
  374. listenToStorageChanges: true
  375. }
  376. );
  377. for (const prop of ["name", "author", "scriptVersion"]) {
  378. if (gameData.value[prop] !== gameInfo[prop]) {
  379. gameData.value[prop] = gameInfo[prop];
  380. }
  381. }
  382. const checked = vue.computed({
  383. get() {
  384. return gameData.value.records.some((record) => record.version === gameInfo.version);
  385. },
  386. set(newVal) {
  387. if (newVal)
  388. addRecord(gameInfo);
  389. else
  390. removeRecord(gameInfo);
  391. }
  392. });
  393. const lastRecord = vue.computed(() => {
  394. const records = gameData.value.records;
  395. if (records.length > 0) {
  396. return records[records.length - 1];
  397. }
  398. return null;
  399. });
  400. const lastRecordTime = vue.computed(() => {
  401. if (lastRecord.value) {
  402. return lastRecord.value.time;
  403. }
  404. return null;
  405. });
  406. const timeAgo = core.useTimeAgo(lastRecordTime);
  407. const lastPlayedText = vue.computed(() => {
  408. if (!lastRecord.value)
  409. return "";
  410. const lastRecordVersion = lastRecord.value.version;
  411. if (lastRecordVersion === gameInfo.version) {
  412. return "Finished ";
  413. } else {
  414. return `Finished [${lastRecordVersion}] `;
  415. }
  416. });
  417. const removeFlagConfirmVisible = vue.ref(false);
  418. function handleFlagClick() {
  419. if (checked.value) {
  420. removeFlagConfirmVisible.value = true;
  421. } else {
  422. checked.value = true;
  423. }
  424. }
  425. const form = vue.reactive({
  426. version: "",
  427. date: ""
  428. });
  429. const rules = vue.reactive({
  430. version: [
  431. {
  432. required: true,
  433. message: "Please input version",
  434. trigger: "blur"
  435. },
  436. {
  437. min: 1,
  438. max: 30,
  439. message: "Length should be 1 to 30",
  440. trigger: "blur"
  441. },
  442. {
  443. validator: (rule, value, callback) => {
  444. if (gameData.value.records.some((record) => record.version === value)) {
  445. callback(new Error(`[${value}] already recorded`));
  446. } else {
  447. callback();
  448. }
  449. },
  450. trigger: "blur"
  451. }
  452. ],
  453. date: [
  454. {
  455. type: "date",
  456. required: true,
  457. message: "Please pick a date",
  458. trigger: "change"
  459. }
  460. ]
  461. });
  462. const formRef = vue.ref(null);
  463. const popoverVisible = vue.ref(false);
  464. const onSubmit = () => {
  465. formRef.value.validate().then(() => {
  466. addRecord({
  467. version: form.version,
  468. time: form.date.getTime()
  469. });
  470. popoverVisible.value = false;
  471. form.version = "";
  472. form.date = "";
  473. }).catch(() => {
  474. });
  475. };
  476. const popoverCommonProps = {
  477. effect: "light",
  478. teleported: false,
  479. placement: "bottom"
  480. };
  481. function addRecord({ version, time }) {
  482. time = time || Date.now();
  483. gameData.value.records.push({
  484. version,
  485. time
  486. });
  487. gameData.value.records.sort((a, b) => a.time - b.time);
  488. }
  489. function removeRecord({ version }) {
  490. gameData.value.records = gameData.value.records.filter((record) => record.version !== version);
  491. }
  492. function formatTime(time) {
  493. if (dayjs(time).hour() === 0 && dayjs(time).minute() === 0 && dayjs(time).second() === 0) {
  494. return dayjs(time).format("YYYY-MM-DD");
  495. } else {
  496. return dayjs(time).format("YYYY-MM-DD HH:mm:ss");
  497. }
  498. }
  499. return (_ctx, _cache) => {
  500. const _component_el_icon = vue.resolveComponent("el-icon");
  501. const _component_el_popconfirm = vue.resolveComponent("el-popconfirm");
  502. const _component_el_tooltip = vue.resolveComponent("el-tooltip");
  503. const _component_el_input = vue.resolveComponent("el-input");
  504. const _component_el_form_item = vue.resolveComponent("el-form-item");
  505. const _component_el_date_picker = vue.resolveComponent("el-date-picker");
  506. const _component_el_button = vue.resolveComponent("el-button");
  507. const _component_el_form = vue.resolveComponent("el-form");
  508. const _component_el_popover = vue.resolveComponent("el-popover");
  509. const _component_el_table_column = vue.resolveComponent("el-table-column");
  510. const _component_el_table = vue.resolveComponent("el-table");
  511. return vue.openBlock(), vue.createElementBlock("span", null, [
  512. vue.createVNode(_component_el_popconfirm, vue.mergeProps({ visible: vue.unref(removeFlagConfirmVisible) }, popoverCommonProps, {
  513. width: "350",
  514. "confirm-button-text": "OK",
  515. "cancel-button-text": "Cancel",
  516. title: "Remove flag and delete corresponding record?",
  517. onConfirm: _cache[0] || (_cache[0] = ($event) => removeFlagConfirmVisible.value = checked.value = false),
  518. onCancel: _cache[1] || (_cache[1] = ($event) => removeFlagConfirmVisible.value = false)
  519. }), {
  520. reference: vue.withCtx(() => [
  521. vue.createElementVNode("span", {
  522. class: vue.normalizeClass(["p-1 inline-block cursor-pointer v-sub", vue.unref(checked) ? "text-#ec5555" : "text-[rgb(147,152,160)]"])
  523. }, [
  524. vue.createVNode(_component_el_icon, {
  525. size: 35,
  526. onClick: handleFlagClick
  527. }, {
  528. default: vue.withCtx(() => [
  529. vue.createVNode(vue.unref(iconsVue.Flag))
  530. ]),
  531. _: 1
  532. })
  533. ], 2)
  534. ]),
  535. _: 1
  536. }, 16, ["visible"]),
  537. vue.unref(lastPlayedText) ? (vue.openBlock(), vue.createElementBlock("span", _hoisted_1, [
  538. vue.createTextVNode(vue.toDisplayString(vue.unref(lastPlayedText)) + " ", 1),
  539. vue.createVNode(_component_el_tooltip, vue.mergeProps(popoverCommonProps, {
  540. content: formatTime(vue.unref(lastRecordTime))
  541. }), {
  542. default: vue.withCtx(() => [
  543. vue.createElementVNode("span", null, vue.toDisplayString(vue.unref(timeAgo)), 1)
  544. ]),
  545. _: 1
  546. }, 16, ["content"])
  547. ])) : vue.createCommentVNode("", true),
  548. vue.createVNode(_component_el_popover, vue.mergeProps(popoverCommonProps, {
  549. visible: vue.unref(popoverVisible),
  550. width: 350,
  551. trigger: "click"
  552. }), {
  553. reference: vue.withCtx(() => [
  554. vue.createVNode(_component_el_icon, {
  555. size: 20,
  556. class: "v-middle ml-4 cursor-pointer text-white opacity-70 hover:opacity-100"
  557. }, {
  558. default: vue.withCtx(() => [
  559. vue.createVNode(vue.unref(iconsVue.CirclePlusFilled), {
  560. onClick: _cache[2] || (_cache[2] = ($event) => popoverVisible.value = true)
  561. })
  562. ]),
  563. _: 1
  564. })
  565. ]),
  566. default: vue.withCtx(() => [
  567. vue.createVNode(_component_el_form, {
  568. ref_key: "formRef",
  569. ref: formRef,
  570. "label-width": "130px",
  571. rules: vue.unref(rules),
  572. model: vue.unref(form)
  573. }, {
  574. default: vue.withCtx(() => [
  575. vue.createVNode(_component_el_form_item, {
  576. label: "Finished Version",
  577. prop: "version"
  578. }, {
  579. default: vue.withCtx(() => [
  580. vue.createVNode(_component_el_input, {
  581. modelValue: vue.unref(form).version,
  582. "onUpdate:modelValue": _cache[3] || (_cache[3] = ($event) => vue.unref(form).version = $event)
  583. }, null, 8, ["modelValue"])
  584. ]),
  585. _: 1
  586. }),
  587. vue.createVNode(_component_el_form_item, {
  588. label: "Finished Time",
  589. prop: "date"
  590. }, {
  591. default: vue.withCtx(() => [
  592. vue.createVNode(_component_el_date_picker, {
  593. modelValue: vue.unref(form).date,
  594. "onUpdate:modelValue": _cache[4] || (_cache[4] = ($event) => vue.unref(form).date = $event),
  595. teleported: false,
  596. "data-popper-placement": "bottom",
  597. type: "date",
  598. placeholder: "Pick a date",
  599. style: { "width": "100%" }
  600. }, null, 8, ["modelValue"])
  601. ]),
  602. _: 1
  603. }),
  604. vue.createVNode(_component_el_form_item, { class: "button-group" }, {
  605. default: vue.withCtx(() => [
  606. vue.createVNode(_component_el_button, {
  607. size: "small",
  608. onClick: _cache[5] || (_cache[5] = ($event) => popoverVisible.value = false)
  609. }, {
  610. default: vue.withCtx(() => [
  611. vue.createTextVNode("Cancel")
  612. ]),
  613. _: 1
  614. }),
  615. vue.createVNode(_component_el_button, {
  616. size: "small",
  617. type: "primary",
  618. onClick: onSubmit
  619. }, {
  620. default: vue.withCtx(() => [
  621. vue.createTextVNode("Create")
  622. ]),
  623. _: 1
  624. })
  625. ]),
  626. _: 1
  627. })
  628. ]),
  629. _: 1
  630. }, 8, ["rules", "model"])
  631. ]),
  632. _: 1
  633. }, 16, ["visible"]),
  634. vue.createVNode(_component_el_popover, vue.mergeProps(popoverCommonProps, {
  635. width: 500,
  636. trigger: "click"
  637. }), {
  638. reference: vue.withCtx(() => [
  639. vue.createVNode(_component_el_icon, {
  640. size: 20,
  641. class: "v-middle ml-4 cursor-pointer text-white opacity-70 hover:opacity-100"
  642. }, {
  643. default: vue.withCtx(() => [
  644. vue.createVNode(vue.unref(iconsVue.Document), {
  645. onClick: ($event) => 1
  646. })
  647. ]),
  648. _: 1
  649. })
  650. ]),
  651. default: vue.withCtx(() => [
  652. vue.createVNode(_component_el_table, {
  653. data: vue.unref(gameData).records,
  654. "default-sort": { prop: "time", order: "descending" }
  655. }, {
  656. default: vue.withCtx(() => [
  657. vue.createVNode(_component_el_table_column, {
  658. "min-width": "120",
  659. property: "version",
  660. label: "Version"
  661. }),
  662. vue.createVNode(_component_el_table_column, {
  663. sortable: "",
  664. "min-width": "150",
  665. property: "time",
  666. label: "Time"
  667. }, {
  668. default: vue.withCtx(({ row }) => [
  669. vue.createTextVNode(vue.toDisplayString(formatTime(row.time)), 1)
  670. ]),
  671. _: 1
  672. }),
  673. vue.createVNode(_component_el_table_column, {
  674. "min-width": "100",
  675. label: "Operations"
  676. }, {
  677. default: vue.withCtx(({ row }) => [
  678. vue.createVNode(_component_el_button, {
  679. size: "small",
  680. type: "danger",
  681. onClick: ($event) => removeRecord(row)
  682. }, {
  683. default: vue.withCtx(() => [
  684. vue.createTextVNode(" Delete ")
  685. ]),
  686. _: 2
  687. }, 1032, ["onClick"])
  688. ]),
  689. _: 1
  690. })
  691. ]),
  692. _: 1
  693. }, 8, ["data"])
  694. ]),
  695. _: 1
  696. }, 16)
  697. ]);
  698. };
  699. }
  700. };
  701. const VersionTracker = /* @__PURE__ */ _export_sfc(_sfc_main$1, [["__scopeId", "data-v-9a23e112"]]);
  702. const _sfc_main = {
  703. __name: "App",
  704. setup(__props) {
  705. const titleElement2 = vue.inject("titleElement");
  706. document.documentElement.classList.add("fz-tracker");
  707. return (_ctx, _cache) => {
  708. return vue.openBlock(), vue.createBlock(vue.Teleport, { to: vue.unref(titleElement2) }, [
  709. vue.createVNode(VersionTracker)
  710. ], 8, ["to"]);
  711. };
  712. }
  713. };
  714. document.documentElement.classList.add("dark");
  715. const titleElement = document.querySelector(".p-title-value");
  716. const isGamePage = (() => {
  717. const breadcrumbUl = document.querySelector("ul.p-breadcrumbs");
  718. if (breadcrumbUl) {
  719. const breadcrumbs = [...breadcrumbUl.querySelectorAll(":scope > li")];
  720. return breadcrumbs.length === 3 && breadcrumbs[2].innerText.trim().toLowerCase().includes("games");
  721. }
  722. return false;
  723. })();
  724. const isLatestUpdatesPage = window.location.pathname.startsWith("/sam/latest_alpha");
  725. if (isGamePage) {
  726. const gameInfo = titleElement ? (() => {
  727. const titleText = [...titleElement.childNodes].find((node) => node.nodeType === Node.TEXT_NODE).textContent.trim();
  728. const matches = titleText.match(/^(.*?)\s*\[(.*?)\]\s*\[(.*?)\]$/);
  729. if (matches && matches.length === 4) {
  730. const ID = window.location.pathname.split("/")[2].split(".")[1];
  731. return {
  732. ID,
  733. name: matches[1],
  734. version: matches[2],
  735. author: matches[3]
  736. };
  737. }
  738. })() : null;
  739. if (gameInfo) {
  740. const appEl = document.createElement("div");
  741. document.body.append(appEl);
  742. vue.createApp(_sfc_main).use(ElementPlus).provide("gameInfo", gameInfo).provide("titleElement", titleElement).mount(appEl);
  743. }
  744. } else if (isLatestUpdatesPage) {
  745. const gameCardListEl = document.querySelector("#latest-page_items-wrap_inner");
  746. if (gameCardListEl) {
  747. gameCardListEl.querySelectorAll("[data-thread-id]").forEach(mountTag);
  748. core.useMutationObserver(
  749. gameCardListEl,
  750. (mutations) => {
  751. mutations.filter((mutation) => mutation.type === "childList" && mutation.addedNodes.length > 0).map((mutation) => [...mutation.addedNodes]).flat().forEach(mountTag);
  752. },
  753. { childList: true }
  754. );
  755. }
  756. }
  757. function mountTag(gameInfoEl) {
  758. const gameInfo = {
  759. ID: parseInt(gameInfoEl.getAttribute("data-thread-id"), 10),
  760. name: gameInfoEl.querySelector(".resource-tile_info-header_title").textContent.trim(),
  761. version: gameInfoEl.querySelector(".resource-tile_label-version").textContent.trim(),
  762. author: gameInfoEl.querySelector(".resource-tile_dev").textContent.trim()
  763. };
  764. if (localStorage.getItem(gameStorageKey(gameInfo.ID))) {
  765. const tageContainer = document.createElement("div");
  766. Object.assign(tageContainer.style, {
  767. position: "absolute",
  768. left: 0,
  769. top: 0
  770. });
  771. gameInfoEl.append(tageContainer);
  772. vue.createApp(_sfc_main$2).provide("gameInfo", gameInfo).mount(tageContainer);
  773. }
  774. }
  775.  
  776. })(Vue, ElementPlus, VueUse, ElementPlusIconsVue);