您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
自动签到脚本(掘金签到/抽奖), 需要配合自动化脚本(MacOS: Script Editor / Windows: bat)使用, 开机启动(MacOS: 启动项管理 / Windows: Task Scheduler)
// ==UserScript== // @name auto-signin-scripts // @namespace SublimeCT // @version 0.1.1 // @author Ryan // @description 自动签到脚本(掘金签到/抽奖), 需要配合自动化脚本(MacOS: Script Editor / Windows: bat)使用, 开机启动(MacOS: 启动项管理 / Windows: Task Scheduler) // @license MIT // @icon https://vitejs.dev/logo.svg // @include /^https:\/\/juejin\.cn/ // ==/UserScript== (function () { 'use strict'; var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); var Pages = /* @__PURE__ */ ((Pages2) => { Pages2[Pages2["juejin"] = 0] = "juejin"; return Pages2; })(Pages || {}); const PagesInfoMap = { [ 0 /* juejin */ ]: { name: "掘金签到页", pattern: /^https:\/\/juejin\.cn/ // 由于掘金是 SPA, 所以只需要匹配域名即可, 如果需要匹配具体的页面, 会导致从其他页面进入签到页时, 无法触发签到逻辑 // pattern: /^https:\/\/juejin\.cn\/user\/center\/(signin|lottery)/, } }; function delay(timeout) { return new Promise((resolve) => setTimeout(resolve, timeout)); } async function waitFor(fn, times = 60) { for (let _times = times; _times--; ) { const result = fn(); if (result === undefined) { await delay(200); continue; } return result; } return undefined; } async function waitForElement(selector, times) { return waitFor(() => document.querySelector(selector) || undefined, times); } const _Application = class _Application { static use(plugin) { _Application.modules.push(plugin); return this; } /** 触发 onLoad 钩子 */ onLoad() { if (document.readyState === "loading") { window.addEventListener("DOMContentLoaded", () => this.emit("onLoad")); } else { this.emit("onLoad"); } } /** 检测当前页面是否是 angular SPA 页面, 监听路由变化并触发 routeChange 钩子 */ async listenAngularRouteChange() { const angular = await waitFor(() => window.angular); if (!angular) return; const appElement = document.querySelector("[ng-app]") || document.body; const injector = await waitFor(() => angular.element(appElement).injector()); if (!injector) throw new Error("angular injector not found"); const $rootScope = injector.get("$rootScope"); $rootScope.$on("$locationChangeSuccess", (_, newUrl, oldUrl) => { const toRoute = { path: newUrl, name: newUrl, meta: { title: newUrl } }; const fromRoute = { path: oldUrl, name: oldUrl, meta: { title: oldUrl } }; this.emit("routeChange", toRoute, fromRoute); }); } /** 检测当前页面是否是 vue SPA 页面, 监听路由变化并触发 routeChange 钩子 */ async listenVueRouteChange() { const app = await waitForElement("#app,#__nuxt"); if (!app) return; const appInstance = await waitFor(() => app == null ? undefined : app.__vue__); if (!appInstance) return; appInstance.$router.afterEach((to, from) => { this.emit("routeChange", to, from); }); } /** 判断当前页面是否匹配当前模块 */ matchModulePage(applicationModule) { return PagesInfoMap[applicationModule.page].pattern.test(location.href); } /** * 触发钩子函数 * @param hook 钩子事件函数名 */ emit(hook, ...args) { for (const m of _Application.modules) { if (this.matchModulePage(m) && typeof m[hook] === "function") { m[hook](...args); } } } }; __publicField(_Application, "modules", []); __publicField(_Application, "application", new _Application()); let Application = _Application; function getPathname() { return location.hash && location.hash.substring(0, 2) === "#/" ? location.hash.substring(1) : location.pathname; } function checkRoute(route, page) { const isCurrentPage = PagesInfoMap[page].pattern.test(location.href); const pathname = getPathname(); if (!isCurrentPage) return false; if (route instanceof RegExp) { return route.test(pathname); } else { return route === pathname; } } class JueJinLotteryModule { constructor() { __publicField(this, "page", Pages.juejin); __publicField(this, "initialized", false); } async onLoad() { console.log("lottery"); if (!checkRoute("/user/center/lottery", this.page)) return this.unMounted(); if (this.initialized) return; this._clickLotteryButton(); } routeChange() { this.onLoad(); } unMounted() { } async _clickLotteryButton() { console.warn("click lottery button"); const signinButton = await waitForElement("#turntable-item-0"); if (!signinButton || !signinButton.textContent) throw new Error("抽奖按钮未找到"); console.log(signinButton, signinButton.textContent.trim()); await delay(1e3); if (signinButton.textContent.trim().indexOf("免费抽奖次数") === -1) return console.log("今日已抽奖"); signinButton.click(); const lotteryModalSubmitButton = await waitForElement(".lottery-modal button.submit"); if (!lotteryModalSubmitButton) throw new Error("抽奖结果弹窗未找到"); lotteryModalSubmitButton.click(); } } class JueJinSigninModule { constructor() { __publicField(this, "page", Pages.juejin); __publicField(this, "initialized", false); } async onLoad() { if (!checkRoute("/user/center/signin", this.page)) return this.unMounted(); if (this.initialized) return; this._checkSignin(); } routeChange() { this.onLoad(); } unMounted() { } async _checkSignin() { const signinButton = await waitForElement("button.signin.btn"); if (!signinButton || !signinButton.textContent || signinButton.textContent.trim() !== "立即签到") throw new Error("签到按钮未找到或已经签到"); signinButton.click(); console.log("signin success"); this._toLotteryPage(); } async _toLotteryPage() { const lotteryMenuItemButton = await waitForElement('.menu .byte-menu-item[href^="/user/center/lottery"]'); if (!lotteryMenuItemButton) throw new Error("Missing lottery menu item"); lotteryMenuItemButton.click(); } } Application.use(new JueJinSigninModule()).use(new JueJinLotteryModule()); const application = new Application(); application.emit("onInit"); application.onLoad(); application.listenVueRouteChange(); application.listenAngularRouteChange(); })();