链滴自动签到 puppeteer 脚本

链滴自动签到puppeteer脚本

// 用户名和密码,必须
const username = "";
const password = "";

// 设置浏览器安装路径,必须,如果填空,则使用puppeteer模式而不是puppeteer-core
// Windows用户可能是 "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",注意这里的路径要用\转义
const chromePath = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome";

// 通知脚本路径,可选,如果没有,填空即可,空则不通知(通知仅支持Mac)
const notifyShellPath = "";

// 警告脚本路径,可选,如果没有,填空即可,空则不警告(警告仅支持Mac)
//(和通知的区别是,警告只会在发生错误时弹出,而通知则是普通的异常,比如签到失败)
const alertShellPath = "";

// 用户代理,必须,否则无头模式下被反爬虫阻止访问,请使用真实浏览器代理,如果你不清楚就使用默认的即可
const userAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36";

/////////////////////// 以下代码通常不需要修改 //////////////////////////////////

// 是否调试模式,true为调试模式,正式环境别忘了设置为false
const DEBUG = process.argv.includes("--debug") || false;

// 登录的URL
const loginUrl = "https://ld246.com/login";

// 登录按钮选择器
const usernameFieldSelector = "#nameOrEmail";
const passwordFieldSelector = "#loginPassword";
const loginButtonSelector = "#loginBtn";
const logoutButtonSelector = "#signOut";
const loginErrorTipSelector = "#loginTip.error";

// 签到URL
const signInUrl = "https://ld246.com/activity/checkin";

// 签到按钮选择器
const signInSelector = "a.btn.green";
const hasSignInSelector = "a.btn[href$='points']";

// 引入文件系统模块
const fs = require('fs');
const puppeteer = require(chromePath?'puppeteer-core':'puppeteer');

// 创建一个写入流对象,用于向日志文件写入数据
const logStream = fs.createWriteStream('log.txt', { flags: 'a' });

// 获取临时文件夹路径
//const os = require('os');
//const tempDir = os.tmpdir();
const path = require('path');
const tempDir = path.join(__dirname, "tmp");
if(!fs.existsSync(tempDir)) fs.mkdirSync(tempDir);
const lockFilePath = path.join(tempDir, '~~ld246'+ username + new Date().toLocaleDateString().replace(/\//g, '') + '~~signin.lock');
if(DEBUG) console.log("lockFilePath: ", lockFilePath);
const cookieFilePath = path.join(tempDir, `~~ld246${username}~~cookies.json`);
if(DEBUG) console.log("cookieFilePath: ", cookieFilePath);

// 检测用户名和密码是否正确
if (!username || !password) {
  console.error('用户名和密码必填');
  logStream.end();
  process.exit(1);
}
// 检测chromePath是否正确
if(chromePath){
  if(!fs.existsSync(chromePath)){
    console.error('chromePath错误,请检查chromePath是否正确');
    logStream.end();
    process.exit(1);
  }
}

// 如果已签到则退出
if (fs.existsSync(lockFilePath)) {
    if(!DEBUG && !process.argv.includes('--force')){
        console.log("今日已签到");
        //console.log(lockFilePath);
        logStream.end();
        process.exit(0);
    }
}

(async () => {
  // 启动浏览器
  const options = {
    headless: process.argv.includes('--headless') || !DEBUG,
  };
  if(chromePath) options.executablePath = chromePath;
  const browser = await puppeteer.launch(options);
  // 创建一个新页面
  const page = await browser.newPage();
  await page.setUserAgent(userAgent || "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36");
  try {
    // 获取cookie信息
    let cookies = getCookies();
    if (cookies) {
      // 添加 cookies 到 page
      console.log('setCookies success');
      await page.setCookie(...cookies);
    }

    // 跳转签到页面
    console.log("打开签到页面", signInUrl);
    await page.goto(signInUrl, { timeout: 30000 });

    // 判断是否已登录
    const hasLogin = await page.$(logoutButtonSelector);
    if(!hasLogin) {
      // 模拟登录
      console.log("检测到未登录,正在打开登录页面", loginUrl);
      await page.goto(loginUrl, { timeout: 30000 });
      await page.locator(usernameFieldSelector).fill(username);
      await page.locator(passwordFieldSelector).fill(password);
      await page.locator(loginButtonSelector).click();
      // 等待登录完成
      try {
        await page.waitForNavigation({ timeout: 30000 });
      } catch (e) {
        // 登录发生错误时,获取页面错误信息
        try {
          // 获取网站返回错误信息
          await page.waitForSelector(loginErrorTipSelector, { timeout: 5000 });
          const errorMessage = await page.$eval(loginErrorTipSelector, el => el.textContent.trim());
          console.log('登录失败,网站返回错误信息:', errorMessage);
          logStream.write("[" + (new Date().toLocaleString()) + "] " + username + " 登录失败,网站返回错误信息:" + errorMessage + "\n");
          notice(username + " 登录失败,网站返回错误信息:" + errorMessage);
          await exit(1);
        } catch(e) {
          // 获取网站错误信息失败
          console.log('登录失败,错误信息:' + e.message);
          logStream.write("[" + (new Date().toLocaleString()) + "] " + username + " 登录失败,错误信息:" + e.message + "\n");
          notice(username + " 登录失败,错误信息:" + e.message);
          await exit(1);
        }
      }
      // 登录完成后,尝试获取登录信息
      const hasLogin = await page.$(logoutButtonSelector);
      if(!hasLogin) {
        // 未获取到登录信息
        console.log("登录失败,无法获取登录信息");
        logStream.write("[" + (new Date().toLocaleString()) + "] " + username + " 登录失败,无法获取登录信息\n");
        notice("登录失败,无法获取登录信息");
        await exit(1);
      }
      // 登录成功
      console.log("登录已完成");
      // 保存 cookies
      cookies = await page.cookies();
      fs.writeFileSync(cookieFilePath, JSON.stringify(cookies, null, 2), 'utf8');
      console.log("已保存cookies");

      // 检查当前页面是否签到页面,不是则跳转到签到页面
      if(page.url() !== signInUrl) {
        // 跳转到签到页面
        console.log("检测到当前不在签到页,正跳转到签到页面");
        await page.goto(signInUrl, { timeout: 30000 });
      }
    }

    // 检查是否有已签到元素
    const hasSignInElement = await page.$(hasSignInSelector);
    if (hasSignInElement) {
      console.log('今日已签到');
      fs.writeFileSync(lockFilePath, '');
    } else {
      // 模拟签到
      await page.locator(signInSelector).click();

      // 等待签到完成
      await page.waitForNavigation({ timeout: 30000 });
      const hasSingIn = await page.$(hasSignInSelector);
      if(!hasSingIn){
        // 签到失败退出
        console.log("签到失败,未获取到已签信息");
        logStream.write("[" + (new Date().toLocaleString()) + "] " + username + " 签到失败,未获取到已签信息\n");
        notice("签到失败,未获取到已签信息");
        await exit(1);
      }
      // 签到完成锁定签到
      console.log("签到已完成");
      fs.writeFileSync(lockFilePath, '');

      // 获取积分
      console.log("获取积分中...");
      const scoreElement = await page.$("code");
      if (scoreElement) {
        // 获取积分成功
        const score = await page.evaluate((el) => el.textContent, scoreElement);
        console.log("积分:", parseInt(score));
        logStream.write("[" + (new Date().toLocaleString()) + "] " + username + " 签到成功,积分: " + parseInt(score) + "\n");
      } else {
        // 获取积分失败
        console.log("未找到积分元素");
        logStream.write("[" + (new Date().toLocaleString()) + "] " + username + " 签到成功,但未找到积分元素\n");
      }
    }
  } catch (error) {
    // 发生错误处理
    console.error("发生错误:", error.message);
    logStream.write("[" + (new Date().toLocaleString()) + "] 发生错误: " + error.message + "\n");
    alert("发生错误:" + error.message);
    exit(1);
  } finally {
    exit(0);
  }

  // 退出进程
  async function exit(code, delay) {
    // 退出码
    code = code || 0;
    // 延迟关闭
    if(delay && typeof delay === 'number') await sleep(delay);
    // 关闭写入流
    logStream.end();
    // 关闭浏览器
    if(!DEBUG) {
      console.log("已关闭浏览器");
      await browser.close();
      process.exit(code);
    }
  }
})();

// 获取已保存的cookies
function getCookies() {
  // 尝试加载 cookies
  let cookies;
  if(!fs.existsSync(cookieFilePath)){
    return cookies;
  }
  try {
    cookies = JSON.parse(fs.readFileSync(cookieFilePath, 'utf8'));
    console.log("加载cookies成功");
  } catch (error) {
    console.log("没有找到cookies文件,将重新登录");
  }
  return cookies;
}

// 显示通知
function notice(message, title) {
    if(!notifyShellPath) return;
    console.log("发送通知", message, title);
    if (process.platform === 'darwin' && fs.existsSync(notifyShellPath)) {
        const { exec } = require('child_process');
        // 假设我们要执行 `ls` 命令,‌并传递 `-l` 和 `-a` 作为参数
        const command = `${notifyShellPath} "${message || "未知错误"}" "${title || "签到异常"}"`;
        exec(command, (error, stdout, stderr) => {
            if (error) {
                console.error(`exec error: ${error}`);
                return;
            }
            console.log(`stdout: ${stdout}`);
            if (stderr) {
                console.error(`stderr: ${stderr}`);
            }
        });
    }
}

// 显示警告弹窗
function alert(message) {
    if(!alertShellPath) return;
    console.log("发送警告", message);
    if (process.platform === 'darwin' && fs.existsSync(alertShellPath)) {
        const { exec } = require('child_process');
        // 假设我们要执行 `ls` 命令,‌并传递 `-l` 和 `-a` 作为参数
        const command = `${alertShellPath} "${message || "未知错误"}"`;
        exec(command, (error, stdout, stderr) => {
            if (error) {
                console.error(`exec error: ${error}`);
                return;
            }
            console.log(`stdout: ${stdout}`);
            if (stderr) {
                console.error(`stderr: ${stderr}`);
            }
        });
    }
}

// 延迟执行
function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}
image.png

留下你的脚步
推荐阅读