// ==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();
})();