🏠 Home 

Copy token from skland for ark.yituliu.cn

Script to copy skland token to ark.yituliu.cn

  1. // ==UserScript==
  2. // @name Copy token from skland for ark.yituliu.cn
  3. // @name:zh-CN 一键复制明日方舟一图流所需的森空岛token
  4. // @namespace ling921
  5. // @version 0.2.0
  6. // @description Script to copy skland token to ark.yituliu.cn
  7. // @description:zh-CN 脚本用于复制森空岛token到明日方舟一图流中使用,并支持Shift+C快捷键
  8. // @author ling921
  9. // @match https://www.skland.com/*
  10. // @icon https://ark.yituliu.cn/favicon.ico
  11. // @grant none
  12. // @run-at document-idle
  13. // @tag utilities
  14. // @tag game
  15. // @license MIT
  16. // ==/UserScript==
  17. /**
  18. * 通知管理器类
  19. */
  20. class NotificationManager {
  21. static #instance = null;
  22. static #maxNotifications = 3;
  23. static #defaultDuration = 3000;
  24. static #queue = [];
  25. static #active = new Set();
  26. static #container = null;
  27. /**
  28. * 获取通知管理器实例
  29. * @param {{
  30. * maxNotifications?: number,
  31. * duration?: number
  32. * }} options - 配置选项
  33. */
  34. static getInstance(options = {}) {
  35. if (!NotificationManager.#instance) {
  36. if (options.maxNotifications && options.maxNotifications > 0) {
  37. NotificationManager.#maxNotifications = options.maxNotifications;
  38. }
  39. if (options.duration && options.duration > 0) {
  40. NotificationManager.#defaultDuration = options.duration;
  41. }
  42. NotificationManager.#instance = new NotificationManager(options);
  43. }
  44. return NotificationManager.#instance;
  45. }
  46. constructor() {
  47. if (NotificationManager.#instance) {
  48. return NotificationManager.#instance;
  49. }
  50. NotificationManager.#container = document.getElementById(
  51. "notification-container"
  52. );
  53. if (!NotificationManager.#container) {
  54. const style = document.createElement("style");
  55. style.textContent = `
  56. #notification-container {
  57. position: fixed;
  58. top: 20px;
  59. left: 0;
  60. right: 0;
  61. display: flex;
  62. flex-direction: column;
  63. align-items: center;
  64. gap: 12px;
  65. pointer-events: none;
  66. z-index: 10000;
  67. }
  68. #notification-container .notification {
  69. position: relative;
  70. padding: 10px 25px;
  71. padding-right: 35px !important;
  72. border-radius: 20px;
  73. font-size: 14px;
  74. color: white;
  75. font-family: "PingFang SC", "Microsoft YaHei", sans-serif;
  76. opacity: 0;
  77. transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
  78. text-align: center;
  79. backdrop-filter: blur(8px);
  80. -webkit-backdrop-filter: blur(8px);
  81. transform: translateY(-20px);
  82. pointer-events: auto;
  83. }
  84. #notification-container .notification:hover {
  85. filter: brightness(1.1);
  86. }
  87. #notification-container .notification .close {
  88. position: absolute !important;
  89. right: 10px !important;
  90. top: 50% !important;
  91. transform: translateY(-50%) !important;
  92. width: 16px !important;
  93. height: 16px !important;
  94. cursor: pointer !important;
  95. opacity: 0.7 !important;
  96. transition: opacity 0.2s !important;
  97. display: flex !important;
  98. align-items: center !important;
  99. justify-content: center !important;
  100. user-select: none !important;
  101. }
  102. #notification-container .notification .close:hover {
  103. opacity: 1 !important;
  104. }
  105. #notification-container .success {
  106. background-color: #52c41a !important;
  107. box-shadow: 0 4px 12px rgba(82, 196, 26, 0.3) !important;
  108. border-color: rgba(255, 255, 255, 0.2) !important;
  109. }
  110. #notification-container .info {
  111. background-color: #1890ff !important;
  112. box-shadow: 0 4px 12px rgba(24, 144, 255, 0.3) !important;
  113. border-color: rgba(255, 255, 255, 0.15) !important;
  114. }
  115. #notification-container .warning {
  116. background-color: #faad14 !important;
  117. box-shadow: 0 4px 12px rgba(250, 173, 20, 0.3) !important;
  118. border-color: rgba(255, 255, 255, 0.1) !important;
  119. }
  120. #notification-container .error {
  121. background-color: #ff4d4f !important;
  122. box-shadow: 0 4px 12px rgba(255, 77, 79, 0.3) !important;
  123. border-color: rgba(255, 255, 255, 0.1) !important;
  124. }
  125. `;
  126. document.head.appendChild(style);
  127. NotificationManager.#container = document.createElement("div");
  128. NotificationManager.#container.id = "notification-container";
  129. document.body.appendChild(NotificationManager.#container);
  130. }
  131. NotificationManager.#instance = this;
  132. }
  133. success(message, duration) {
  134. this.#show(message, "success", duration);
  135. }
  136. info(message, duration) {
  137. this.#show(message, "info", duration);
  138. }
  139. warning(message, duration) {
  140. this.#show(message, "warning", duration);
  141. }
  142. error(message, duration) {
  143. this.#show(message, "error", duration);
  144. }
  145. #show(message, type, duration) {
  146. if (!duration || duration <= 0) {
  147. duration = NotificationManager.#defaultDuration;
  148. }
  149. NotificationManager.#queue.push({ message, type, duration });
  150. NotificationManager.#processQueue();
  151. }
  152. static #processQueue() {
  153. if (
  154. NotificationManager.#queue.length === 0 ||
  155. NotificationManager.#active.size >= NotificationManager.#maxNotifications
  156. ) {
  157. return;
  158. }
  159. const { message, type, duration } = NotificationManager.#queue.shift();
  160. NotificationManager.#showNotification(message, type, duration);
  161. if (NotificationManager.#queue.length > 0) {
  162. NotificationManager.#processQueue();
  163. }
  164. }
  165. static #showNotification(
  166. message,
  167. type,
  168. duration = NotificationManager.#defaultDuration
  169. ) {
  170. const notify = document.createElement("div");
  171. notify.classList.add("notification", type);
  172. const messageText = document.createElement("span");
  173. messageText.textContent = message;
  174. notify.appendChild(messageText);
  175. const removeNotify = () => {
  176. notify.style.opacity = "0";
  177. notify.style.transform = "translateY(-20px)";
  178. setTimeout(() => {
  179. notify.remove();
  180. NotificationManager.#active.delete(notify);
  181. NotificationManager.#processQueue();
  182. }, 300);
  183. };
  184. const closeBtn = document.createElement("div");
  185. closeBtn.classList.add("close");
  186. closeBtn.innerHTML = "✕";
  187. closeBtn.onclick = removeNotify;
  188. notify.appendChild(closeBtn);
  189. let timer;
  190. notify.addEventListener("mouseenter", () => {
  191. clearTimeout(timer);
  192. });
  193. notify.addEventListener("mouseleave", () => {
  194. timer = setTimeout(removeNotify, duration);
  195. });
  196. NotificationManager.#container.appendChild(notify);
  197. NotificationManager.#active.add(notify);
  198. requestAnimationFrame(() => {
  199. notify.style.opacity = "1";
  200. notify.style.transform = "translateY(0)";
  201. });
  202. timer = setTimeout(removeNotify, duration);
  203. }
  204. }
  205. /**
  206. * 防抖复制令牌
  207. */
  208. const debouncedCopyToken = debounce(copyToken, 300);
  209. /**
  210. * 消息通知
  211. */
  212. const message = NotificationManager.getInstance();
  213. (function () {
  214. ("use strict");
  215. // 添加按钮
  216. const button = createButton();
  217. document.body.appendChild(button);
  218. // 添加快捷键
  219. document.addEventListener("keydown", (event) => {
  220. if (event.shiftKey && event.key.toLowerCase() === "c") {
  221. event.preventDefault();
  222. debouncedCopyToken();
  223. }
  224. });
  225. })();
  226. /**
  227. * 创建按钮
  228. * @returns {HTMLElement} - 按钮元素
  229. */
  230. function createButton() {
  231. // 创建按钮
  232. const button = document.createElement("div", { class: "copy-button" });
  233. button.innerHTML = `
  234. <div class="main-text">复制</div>
  235. <div class="shortcut">Shift + C</div>
  236. `;
  237. button.style.cssText = `
  238. position: fixed;
  239. right: 0;
  240. top: 50%;
  241. transform: translateY(-50%);
  242. background-color: rgba(71, 120, 224, 0.85);
  243. color: rgba(255, 255, 255, 0.95);
  244. border: none;
  245. padding: 10px 20px;
  246. border-radius: 20px 0 0 20px;
  247. cursor: pointer;
  248. font-family: "PingFang SC", "Microsoft YaHei", sans-serif;
  249. font-size: 14px;
  250. transition: all 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
  251. z-index: 9999;
  252. overflow: hidden;
  253. white-space: nowrap;
  254. text-align: center;
  255. line-height: 1.4;
  256. box-shadow: 0 2px 8px rgba(71, 120, 224, 0.2);
  257. min-width: 80px;
  258. `;
  259. // 添加内部元素的样式
  260. const style = document.createElement("style");
  261. style.textContent = `
  262. .copy-button {
  263. display: flex;
  264. flex-direction: column;
  265. align-items: center;
  266. transition: all 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
  267. user-select: none;
  268. }
  269. .main-text {
  270. font-size: 14px;
  271. transition: min-width 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
  272. width: 100%;
  273. }
  274. .shortcut {
  275. font-size: 10px;
  276. opacity: 0.8;
  277. margin-top: 2px;
  278. }
  279. .copy-button {
  280. transform-origin: right center;
  281. }
  282. .copy-button:hover {
  283. min-width: 140px;
  284. padding-right: 25px;
  285. box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
  286. }
  287. .copy-button:active {
  288. transform: translateY(-50%) scale(0.98);
  289. }
  290. `;
  291. document.head.appendChild(style);
  292. let isAnimating = false;
  293. let currentAnimation = null; // 用于存储当前动画的定时器
  294. const fullText = "一图流令牌";
  295. const typingSpeed = 50; // 打字速度(ms)
  296. /**
  297. * 打字动画
  298. * @param {HTMLElement} element - 要打字的元素
  299. * @param {string} text - 要打字的文本
  300. * @param {number} currentIndex - 当前索引
  301. * @returns {void}
  302. */
  303. function typeText(element, text, currentIndex = 0) {
  304. if (currentAnimation) {
  305. clearTimeout(currentAnimation);
  306. }
  307. if (currentIndex <= text.length) {
  308. element.textContent = "复制" + text.slice(0, currentIndex);
  309. currentAnimation = setTimeout(() => {
  310. typeText(element, text, currentIndex + 1);
  311. }, typingSpeed);
  312. } else {
  313. isAnimating = false;
  314. currentAnimation = null;
  315. }
  316. }
  317. /**
  318. * 删除文本
  319. * @param {HTMLElement} element - 要删除文本的元素
  320. * @param {string} text - 要删除的文本
  321. * @param {number} currentIndex - 当前索引
  322. * @returns {void}
  323. */
  324. function deleteText(element, text, currentIndex = text.length) {
  325. if (currentAnimation) {
  326. clearTimeout(currentAnimation);
  327. }
  328. if (currentIndex >= 0) {
  329. element.textContent = "复制" + text.slice(0, currentIndex);
  330. currentAnimation = setTimeout(() => {
  331. deleteText(element, text, currentIndex - 1);
  332. }, typingSpeed);
  333. } else {
  334. isAnimating = false;
  335. currentAnimation = null;
  336. }
  337. }
  338. // 添加悬停效果
  339. button.addEventListener("mouseenter", () => {
  340. if (currentAnimation) {
  341. clearTimeout(currentAnimation);
  342. }
  343. isAnimating = true;
  344. button.style.backgroundColor = "rgba(86, 146, 255, 0.95)";
  345. typeText(button.querySelector(".main-text"), fullText);
  346. });
  347. button.addEventListener("mouseleave", () => {
  348. if (currentAnimation) {
  349. clearTimeout(currentAnimation);
  350. }
  351. isAnimating = true;
  352. button.style.backgroundColor = "rgba(71, 120, 224, 0.85)";
  353. deleteText(button.querySelector(".main-text"), fullText);
  354. });
  355. // 添加点击事件
  356. button.addEventListener("click", debouncedCopyToken);
  357. return button;
  358. }
  359. /**
  360. * 复制令牌
  361. * @returns {void}
  362. */
  363. function copyToken() {
  364. try {
  365. const skOauthCredKey = localStorage.getItem("SK_OAUTH_CRED_KEY");
  366. const skTokenCacheKey = localStorage.getItem("SK_TOKEN_CACHE_KEY");
  367. if (!skOauthCredKey || !skTokenCacheKey) {
  368. const missingKeys = [];
  369. if (!skOauthCredKey) missingKeys.push("SK_OAUTH_CRED_KEY");
  370. if (!skTokenCacheKey) missingKeys.push("SK_TOKEN_CACHE_KEY");
  371. message.error(`缺少必要的密钥:${missingKeys.join("、")}`);
  372. return;
  373. }
  374. const combinedData = `${skOauthCredKey},${skTokenCacheKey}`;
  375. navigator.clipboard
  376. .writeText(combinedData)
  377. .then(() => message.success("令牌复制成功!"))
  378. .catch((err) => {
  379. console.error("复制失败!", err);
  380. message.error("复制失败!");
  381. });
  382. } catch (error) {
  383. console.error("操作过程出现错误!", error);
  384. message.error("操作过程出现错误!");
  385. }
  386. }
  387. /**
  388. * 防抖函数
  389. * @param {Function} func - 要防抖的函数
  390. * @param {number} wait - 等待时间(毫秒)
  391. * @returns {Function} - 防抖后的函数
  392. */
  393. function debounce(func, wait) {
  394. let timeout;
  395. return function executedFunction(...args) {
  396. const later = () => {
  397. clearTimeout(timeout);
  398. func(...args);
  399. };
  400. clearTimeout(timeout);
  401. timeout = setTimeout(later, wait);
  402. };
  403. }