Showing preview only (341K chars total). Download the full file or copy to clipboard to get everything.
Repository: 14790897/auto-read-liunxdo
Branch: main
Commit: 6a3a2e5b8e11
Files: 63
Total size: 286.4 KB
Directory structure:
gitextract_34ilwb11/
├── .dockerignore
├── .gitattributes
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report_template.md
│ │ ├── config.yml
│ │ └── feature_request_template.md
│ ├── PULL_REQUEST_TEMPLATE.md
│ ├── deploy-to-hf-spaces.yml
│ └── workflows/
│ ├── IssueManagementAutomation.yml
│ ├── bad-pr.yml
│ ├── cron_bypassCF.yaml
│ ├── cron_bypassCF_idc.yaml
│ ├── cron_bypassCF_likeUser.yaml
│ ├── cron_read.yaml
│ ├── db_test.yaml
│ ├── docker.yml
│ ├── release-please.yml
│ ├── sync.yml
│ ├── update-cron.yml
│ ├── validate-issue-template.yml
│ └── windows_cron_bypassCF.yaml
├── .gitignore
├── CHANGELOG.md
├── Dockerfile
├── Dockerfile-like-user
├── GITHUB_SECRETS_GUIDE.md
├── LICENSE
├── PROXY_GUIDE.md
├── README.md
├── README_en.md
├── bypasscf.js
├── bypasscf_likeUser_not_use.js
├── bypasscf_playwright.mjs
├── cron.log
├── cron.sh
├── debug_db.js
├── debug_rss.js
├── docker-compose-like-user.yml
├── docker-compose.yml
├── external.js
├── index.js
├── index_likeUser.js
├── index_likeUser_activity.js
├── index_likeUser_random.js
├── index_passage_list_old_not_use.js
├── invite_codecow.js
├── package.json
├── parsed_rss_data.json
├── pass_cf.py
├── postgresql/
│ └── root.crt
├── pteer.js
├── src/
│ ├── browser_retry.js
│ ├── db.js
│ ├── db.test.js
│ ├── format_for_telegram.js
│ ├── parse_rss.js
│ ├── proxy_config.js
│ └── topic_data.js
├── telegram_message.txt
├── test_linuxdo.js
├── test_multi_db.js
├── test_proxy.js
├── test_topic_data.js
└── 随笔.md
================================================
FILE CONTENTS
================================================
================================================
FILE: .dockerignore
================================================
.env
================================================
FILE: .gitattributes
================================================
# Auto detect text files and perform LF normalization
* text=auto
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report_template.md
================================================
---
name: "🐛 Bug Report"
about: 报告一个功能或错误的问题。
title: "[Bug] 请简要描述问题"
labels: bug
assignees: ''
---
<!-- ⚠️ 请确保填写所有必须字段以便更快获得支持 -->
### 问题描述
<!-- 清楚简洁地描述您遇到的问题 -->
### 重现步骤
1. ...
2. ...
3. ...
### 附加信息(必须给出日志)
<!--建议同时提供日志的截图有时候日志复制的时候会缺失一些无法复制的东西 参考:https://github.com/14790897/auto-read-liunxdo/issues/151#issuecomment-3776322722-->
================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false
contact_links:
- name: 📖 项目文档
url: https://github.com/14790897/auto-read-liunxdo#readme
about: 查看项目文档和使用指南
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request_template.md
================================================
---
name: "✨ Feature Request"
about: 提出一个功能请求。
title: "[Feature] 请简要描述功能请求"
labels: enhancement
assignees: ''
---
### 背景
<!-- 为什么需要此功能?它解决了什么问题? -->
### 功能描述
<!-- 详细描述你所希望的功能 -->
### 实现建议
<!-- 如果有实现建议,请详细说明 -->
================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
<!--
Thanks for creating a Pull Request! -->
<!--
Choose one of the following by uncommenting it:
-->
<!-- This is a bug fix. -->
<!-- This is an enhancement or feature. -->
<!-- This is a documentation change. -->
## Summary
<!--
Provide a description of what your pull request changes.
-->
## Context
<!--
Is this related to any GitHub issue(s)?
-->
<!--
Please confirm that you want to submit this Pull Request to auto-read-linuxdo, the free software by liuweiqing, by deleting this comment block/删除此段,以证明此为正常提交,否则自动锁定.
-->
================================================
FILE: .github/deploy-to-hf-spaces.yml
================================================
name: Deploy to HuggingFace Spaces
on:
push:
branches:
- dev
- main
paths-ignore:
- '.github/workflows/cron_*.yaml'
- '.github/workflows/cron_*.yml'
workflow_dispatch:
jobs:
check-secret:
runs-on: ubuntu-latest
outputs:
token-set: ${{ steps.check-key.outputs.defined }}
steps:
- id: check-key
env:
HF_TOKEN: ${{ secrets.HF_TOKEN }}
if: "${{ env.HF_TOKEN != '' }}"
run: echo "defined=true" >> $GITHUB_OUTPUT
deploy:
runs-on: ubuntu-latest
needs: [check-secret]
if: needs.check-secret.outputs.token-set == 'true'
env:
HF_TOKEN: ${{ secrets.HF_TOKEN }}
HF_REPO: ${{ secrets.HF_REPO }}
HF_USER: ${{ secrets.HF_USER }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Remove git history
run: rm -rf .git
- name: Prepend YAML front matter to README.md
run: |
echo "---" > temp_readme.md
echo "title: yolo_track_inference" >> temp_readme.md
echo "emoji: 🐳" >> temp_readme.md
echo "colorFrom: red" >> temp_readme.md
echo "colorTo: gray" >> temp_readme.md
echo "sdk: docker" >> temp_readme.md
echo "app_port: 7860" >> temp_readme.md
echo "---" >> temp_readme.md
cat README.md >> temp_readme.md
mv temp_readme.md README.md
- name: Configure git
run: |
git config --global user.email "liuweiqing147@gmail.com"
git config --global user.name "14790897"
- name: Set up Git and push to Space
run: |
git init --initial-branch=main
git lfs track "*.ttf"
git add .
git commit -m "yolo: ${{ github.sha }}"
git push --force https://${HF_USER}:${HF_TOKEN}@huggingface.co/spaces/${HF_USER}/${HF_REPO} main
================================================
FILE: .github/workflows/IssueManagementAutomation.yml
================================================
name: IssueManagementAutomation(勿动)
on:
# schedule:
# - cron: "0 1-9/3 * * *" #"0 18 * * *" "0 */6 * * *"
workflow_dispatch:
push:
branches:
- main
paths-ignore:
- '.github/workflows/cron_*.yaml'
- '.github/workflows/cron_*.yml'
issues:
types: [opened]
permissions:
issues: write
contents: read
jobs:
manage-issues:
if: github.repository == '14790897/auto-read-liunxdo'
runs-on: ubuntu-latest
steps:
- name: auto lock baipiao
uses: 14790897/auto-lock-baipiao@v0.1.7
with:
gh_token: ${{ secrets.GITHUB_TOKEN }}
gh_repo: ${{ github.repository }}
issue_labels: ${{ secrets.ISSUE_LABELS }}
================================================
FILE: .github/workflows/bad-pr.yml
================================================
name: Cleanup bad PR
on:
pull_request_target:
types: [opened, reopened]
permissions:
contents: read
jobs:
close-pr:
permissions:
pull-requests: write
runs-on: ubuntu-latest
if: "contains(github.event.pull_request.body, 'by deleting this comment block') || github.event.pull_request.body == ''"
steps:
- uses: actions-ecosystem/action-add-labels@v1
with:
labels: 'Type: Invalid'
- uses: actions-ecosystem/action-add-labels@v1
with:
labels: 'Type: Spam'
- uses: superbrothers/close-pull-request@v3
with:
# Optional. Post an issue comment just before closing a pull request.
comment: |
**You have created a Pull Request to the wrong repository.** This is the repository for [auto-read-linuxdo][1], the free auto read software. See [GitHub Docs: About pull requests][2] if you need help.
[1]: https://github.com/14790897/auto-read-liunxdo
[2]: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests
- uses: sudo-bot/action-pull-request-lock@v1.0.5
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
number: ${{ github.event.pull_request.number }}
lock-reason: spam
================================================
FILE: .github/workflows/cron_bypassCF.yaml
================================================
name: readLike(自动阅读随机点赞)
# GitHub.secrets优先级最高,即使没有设置对应的变量,它也会读取,这时变量为空值,导致报错,.env读取的变量无法覆盖这个值,使用了${PASSWORD_ESCAPED//\#/\\#}来对#转义,需要两个\,但是我直接在env文件使用这种方法是不行的,GitHub action是有效
on:
# schedule:
# # 每天 UTC 时间 19:40 运行
# - cron: "26 10 * * *"
workflow_dispatch: # 允许手动触发
jobs:
build:
runs-on: windows-latest
timeout-minutes: 35 # 设置作业超时时间为20分钟
strategy:
matrix:
node-version: [20.x]
env:
# 在作业级别设置环境变量
USERNAMES: ${{ secrets.USERNAMES }}
PASSWORDS: ${{ secrets.PASSWORDS }}
COOKIES: ${{ secrets.COOKIES }}
WEBSITE: ${{ secrets.WEBSITE }}
RUN_TIME_LIMIT_MINUTES: ${{ secrets.RUN_TIME_LIMIT_MINUTES }}
MAX_CONCURRENT_ACCOUNTS: ${{ secrets.MAX_CONCURRENT_ACCOUNTS }}
TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
TELEGRAM_GROUP_ID: ${{ secrets.TELEGRAM_GROUP_ID }}
SPECIFIC_USER: ${{ secrets.SPECIFIC_USER }}
HF_TOKEN: ${{ secrets.HF_TOKEN }}
LIKE_SPECIFIC_USER: ${{ secrets.LIKE_SPECIFIC_USER }}
ENABLE_RSS_FETCH: ${{ secrets.ENABLE_RSS_FETCH || '' }}
ENABLE_TOPIC_DATA_FETCH: ${{ secrets.ENABLE_TOPIC_DATA_FETCH || '' }}
HEALTH_PORT: ${{ secrets.HEALTH_PORT }}
# 代理配置
PROXY_URL: ${{ secrets.PROXY_URL }}
PROXY_TYPE: ${{ secrets.PROXY_TYPE }}
PROXY_HOST: ${{ secrets.PROXY_HOST }}
PROXY_PORT: ${{ secrets.PROXY_PORT }}
PROXY_USERNAME: ${{ secrets.PROXY_USERNAME }}
PROXY_PASSWORD: ${{ secrets.PROXY_PASSWORD }}
# 数据库配置
POSTGRES_URI: ${{ secrets.POSTGRES_URI }}
COCKROACH_URI: ${{ secrets.COCKROACH_URI }}
NEON_URI: ${{ secrets.NEON_URI }}
AIVEN_MYSQL_URI: ${{ secrets.AIVEN_MYSQL_URI }}
MONGO_URI: ${{ secrets.MONGO_URI }}
steps:
- uses: actions/checkout@v3 # 检出仓库
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- name: Cache node modules
uses: actions/cache@v3
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- name: Install dependencies
run: |
npm install
- name: Run a script
run: node bypasscf.js
================================================
FILE: .github/workflows/cron_bypassCF_idc.yaml
================================================
name: readLike(需手动取消schedule注释)(自动阅读idc随机点赞)
#只是固定了WEBSITE变量为idcflare.com
# GitHub.secrets优先级最高,即使没有设置对应的变量,它也会读取,这时变量为空值,导致报错,.env读取的变量无法覆盖这个值,使用了${PASSWORD_ESCAPED//\#/\\#}来对#转义,需要两个\,但是我直接在env文件使用这种方法是不行的,GitHub action是有效
on:
# schedule:
# # 每天 UTC 时间 19:40 运行
# - cron: "26 10 * * *"
workflow_dispatch: # 允许手动触发
jobs:
build:
runs-on: windows-latest
timeout-minutes: 35 # 设置作业超时时间为20分钟
strategy:
matrix:
node-version: [20.x]
env:
# 在作业级别设置环境变量
USERNAMES: ${{ secrets.USERNAMES }}
PASSWORDS: ${{ secrets.PASSWORDS }}
COOKIES: ${{ secrets.COOKIES }}
WEBSITE: "https://idcflare.com"
RUN_TIME_LIMIT_MINUTES: ${{ secrets.RUN_TIME_LIMIT_MINUTES }}
MAX_CONCURRENT_ACCOUNTS: ${{ secrets.MAX_CONCURRENT_ACCOUNTS }}
TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
TELEGRAM_GROUP_ID: ${{ secrets.TELEGRAM_GROUP_ID }}
SPECIFIC_USER: ${{ secrets.SPECIFIC_USER }}
HF_TOKEN: ${{ secrets.HF_TOKEN }}
LIKE_SPECIFIC_USER: ${{ secrets.LIKE_SPECIFIC_USER }}
ENABLE_RSS_FETCH: ${{ secrets.ENABLE_RSS_FETCH || '' }}
ENABLE_TOPIC_DATA_FETCH: ${{ secrets.ENABLE_TOPIC_DATA_FETCH || '' }}
HEALTH_PORT: ${{ secrets.HEALTH_PORT }}
# 代理配置
PROXY_URL: ${{ secrets.PROXY_URL }}
PROXY_TYPE: ${{ secrets.PROXY_TYPE }}
PROXY_HOST: ${{ secrets.PROXY_HOST }}
PROXY_PORT: ${{ secrets.PROXY_PORT }}
PROXY_USERNAME: ${{ secrets.PROXY_USERNAME }}
PROXY_PASSWORD: ${{ secrets.PROXY_PASSWORD }}
# 数据库配置
POSTGRES_URI: ${{ secrets.POSTGRES_URI }}
COCKROACH_URI: ${{ secrets.COCKROACH_URI }}
NEON_URI: ${{ secrets.NEON_URI }}
AIVEN_MYSQL_URI: ${{ secrets.AIVEN_MYSQL_URI }}
MONGO_URI: ${{ secrets.MONGO_URI }}
steps:
- uses: actions/checkout@v3 # 检出仓库
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- name: Cache node modules
uses: actions/cache@v3
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- name: Install dependencies
run: |
npm install
- name: Run a script
run: node bypasscf.js
================================================
FILE: .github/workflows/cron_bypassCF_likeUser.yaml
================================================
name: likeUser (点赞特定用户)
# GitHub.secrets优先级最高,即使没有设置对应的变量,它也会读取,这时变量为空值,导致报错,.env读取的变量无法覆盖这个值,使用了${PASSWORD_ESCAPED//\#/\\#}来对#转义,需要两个\,但是我直接在env文件使用这种方法是不行的,GitHub action是有效
on:
# schedule:
# # 每天 UTC 时间 18:00 运行
# - cron: '21 13 * * *'
workflow_dispatch: # 允许手动触发
jobs:
build:
runs-on: windows-latest
timeout-minutes: 35 # 设置作业超时时间为20分钟
strategy:
matrix:
node-version: [20.x]
env:
# 在作业级别设置环境变量
USERNAMES: ${{ secrets.USERNAMES }}
PASSWORDS: ${{ secrets.PASSWORDS }}
COOKIES: ${{ secrets.COOKIES }}
WEBSITE: ${{ secrets.WEBSITE }}
RUN_TIME_LIMIT_MINUTES: ${{ secrets.RUN_TIME_LIMIT_MINUTES }}
MAX_CONCURRENT_ACCOUNTS: ${{ secrets.MAX_CONCURRENT_ACCOUNTS }}
TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
TELEGRAM_GROUP_ID: ${{ secrets.TELEGRAM_GROUP_ID }}
SPECIFIC_USER: ${{ secrets.SPECIFIC_USER }}
HF_TOKEN: ${{ secrets.HF_TOKEN }}
LIKE_SPECIFIC_USER: ${{ secrets.LIKE_SPECIFIC_USER }}
ENABLE_RSS_FETCH: ${{ secrets.ENABLE_RSS_FETCH || '' }}
ENABLE_TOPIC_DATA_FETCH: ${{ secrets.ENABLE_TOPIC_DATA_FETCH || '' }}
HEALTH_PORT: ${{ secrets.HEALTH_PORT }}
# 代理配置
PROXY_URL: ${{ secrets.PROXY_URL }}
PROXY_TYPE: ${{ secrets.PROXY_TYPE }}
PROXY_HOST: ${{ secrets.PROXY_HOST }}
PROXY_PORT: ${{ secrets.PROXY_PORT }}
PROXY_USERNAME: ${{ secrets.PROXY_USERNAME }}
PROXY_PASSWORD: ${{ secrets.PROXY_PASSWORD }}
# 数据库配置
POSTGRES_URI: ${{ secrets.POSTGRES_URI }}
COCKROACH_URI: ${{ secrets.COCKROACH_URI }}
NEON_URI: ${{ secrets.NEON_URI }}
AIVEN_MYSQL_URI: ${{ secrets.AIVEN_MYSQL_URI }}
MONGO_URI: ${{ secrets.MONGO_URI }}
steps:
- uses: actions/checkout@v3 # 检出仓库
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- name: Cache node modules
uses: actions/cache@v3
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- name: Install dependencies
run: |
npm install
#github action设置的action环境变量会完全替换掉env文件的读取,所以需要在action里手动进行加载env文件
# - name: Load environment variables
# run: |
# echo "Debug: Checking if .env file exists..."
# # 检查 .env 文件是否存在
# if [ -f .env ]; then
# echo ".env file found. Loading environment variables from .env file"
# # 加载 .env 文件中的默认值
# set -a
# source .env
# set +a
# echo "Loaded .env variables:"
# else
# echo ".env file not found. Skipping loading."
# fi
# if [ -n "${{ secrets.WEBSITE }}" ] && [ ! -z "${{ secrets.WEBSITE }}" ]; then
# echo "Using GitHub Secret for WEBSITE"
# echo "WEBSITE=${{ secrets.WEBSITE }}" >> $GITHUB_ENV
# else
# echo "WEBSITE=${WEBSITE}" >> $GITHUB_ENV
# fi
# shell: bash
# - name: Run a script(linux)
# run: LIKE_SPECIFIC_USER=true node bypasscf.js --USERNAMES "$USERNAMES" --PASSWORDS "$PASSWORDS" --WEBSITE "$WEBSITE"
- name: Run a script(windows)
run: |
$env:LIKE_SPECIFIC_USER="true"
node bypasscf.js
shell: pwsh
================================================
FILE: .github/workflows/cron_read.yaml
================================================
name: read cron (勿动)
on:
# schedule:
# # 每天 UTC 时间 18:00 运行
# - cron: "0 18 * * *"
workflow_dispatch: # 添加这行以允许手动触发
jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 20 # 设置作业超时时间为20分钟
strategy:
matrix:
node-version: [20.x] # 选择你需要的 Node.js 版本
env:
# 在作业级别设置环境变量
USERNAMES: ${{ secrets.USERNAMES }}
PASSWORDS: ${{ secrets.PASSWORDS }}
WEBSITE: ${{secrets.WEBSITE}}
steps:
- uses: actions/checkout@v3 # 检出你的仓库
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- name: Install dependencies
run: |
npm install
sudo apt install -y xvfb
- name: Run auto read
run: node bypasscf.js # 替换为你想运行的脚本的实际名称
================================================
FILE: .github/workflows/db_test.yaml
================================================
name: db-test
on:
push:
branches:
- '**'
workflow_dispatch:
jobs:
db-test:
if: github.repository == '14790897/auto-read-liunxdo'
runs-on: windows-latest
timeout-minutes: 10
strategy:
matrix:
node-version: [20.x]
env:
POSTGRES_URI: ${{ secrets.POSTGRES_URI }}
COCKROACH_URI: ${{ secrets.COCKROACH_URI }}
steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- name: Cache node modules
uses: actions/cache@v3
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- name: Install dependencies
run: |
npm install
- name: Run DB test
run: node src/db.test.js
================================================
FILE: .github/workflows/docker.yml
================================================
name: AutoRead Docker Image Push (勿动)
on:
push:
branches:
- main
paths-ignore:
- '.github/workflows/cron_*.yaml'
- '.github/workflows/cron_*.yml'
jobs:
build-and-push:
if: github.repository == '14790897/auto-read-liunxdo'
runs-on: ubuntu-latest
steps:
- name: Check Out Repo
uses: actions/checkout@v2
- name: Login to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
- name: Build and Push auto-read
uses: docker/build-push-action@v2
with:
context: ./
file: ./Dockerfile
push: true
tags: 14790897/auto-read:latest
- name: Build and Push auto-like-user
uses: docker/build-push-action@v2
with:
context: ./
file: ./Dockerfile-like-user
push: true
tags: 14790897/auto-like-user:latest
================================================
FILE: .github/workflows/release-please.yml
================================================
name: Release Please(勿动)
on:
push:
branches:
- main
paths-ignore:
- '.github/workflows/cron_*.yaml'
- '.github/workflows/cron_*.yml'
jobs:
release-please:
runs-on: ubuntu-latest
steps:
- uses: GoogleCloudPlatform/release-please-action@v2
with:
token: ${{ secrets.GH_TOKEN }}
release-type: node
package-name: auto-read
permissions:
contents: write
================================================
FILE: .github/workflows/sync.yml
================================================
name: Upstream Sync
permissions:
contents: write
on:
schedule:
- cron: '0 0 * * *' # every day
workflow_dispatch:
jobs:
sync_latest_from_upstream:
name: Sync latest commits from upstream repo
runs-on: ubuntu-latest
if: ${{ github.event.repository.fork }}
steps:
# Step 1: run a standard checkout action
- name: Checkout target repo
uses: actions/checkout@v3
# Step 2: run the sync action
- name: Sync upstream changes
id: sync
uses: aormsby/Fork-Sync-With-Upstream-action@v3.4
with:
upstream_sync_repo: 14790897/auto-read-liunxdo
upstream_sync_branch: main
target_sync_branch: main
target_repo_token: ${{ secrets.GITHUB_TOKEN }} # automatically generated, no need to set
# Set test_mode true to run tests instead of the true action!!
test_mode: false
- name: Sync check
if: failure()
run: |
echo "[Error] 由于上游仓库的 workflow 文件变更,导致 GitHub 自动暂停了本次自动更新,你需要手动 Sync Fork 一次,这是 GitHub 的平台安全策略,详细教程请查看:https://github.com/Yidadaa/ChatGPT-Next-Web/blob/main/README_CN.md#%E6%89%93%E5%BC%80%E8%87%AA%E5%8A%A8%E6%9B%B4%E6%96%B0"
echo "[Error] Due to a change in the workflow file of the upstream repository, GitHub has automatically suspended the scheduled automatic update. You need to manually sync your fork. Please refer to the detailed tutorial for instructions: https://github.com/Yidadaa/ChatGPT-Next-Web#enable-automatic-updates"
exit 1
================================================
FILE: .github/workflows/update-cron.yml
================================================
name: Update Workflow Cron
on:
# schedule:
# - cron: '0 16 * * *'
workflow_dispatch: # 允许手动触发
# push:
# branches:
# - main
env:
PAT_TOKEN: ${{ secrets.PAT_TOKEN }} # Workflows Update GitHub Action workflow files. Learn more. read and write access to the repository contents. classic s
permissions:
contents: write
actions: write
jobs:
update-cron:
runs-on: ubuntu-latest
steps:
- name: checkout repository
uses: actions/checkout@v4
with:
token: ${{ secrets.PAT_TOKEN }}
# formula: RANDOM_NUM=$((RANDOM % (B - A + 1) + A))
- name: generate random time
id: random
run: |
HOUR=$((RANDOM % 17 + 7))
MIN=$((RANDOM % 60))
UTC_HOUR=$(( (HOUR + 24 - 8) % 24 ))
echo "hour=$HOUR" >> $GITHUB_OUTPUT
echo "min=$MIN" >> $GITHUB_OUTPUT
echo "utc_hour=$UTC_HOUR" >> $GITHUB_OUTPUT
echo "北京时间 $HOUR:$MIN, UTC 时间 $UTC_HOUR:$MIN"
- name: replace cron expression
run: |
sed -i "s/cron: '[^']*'/cron: '${{ steps.random.outputs.min }} ${{ steps.random.outputs.hour }} * * *'/" .github/workflows/cron_bypassCF_likeUser.yaml
sed -i "s/cron: '[^']*'/cron: '${{ steps.random.outputs.min }} ${{ steps.random.outputs.utc_hour }} * * *'/" .github/workflows/cron_bypassCF.yaml
- name: Check PAT
run: |
echo "PAT_TOKEN starts with: ${PAT_TOKEN:0:5}"
- name: commit and push changes
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
git add .
git diff --cached --quiet || git commit -m "update cron ${{ steps.random.outputs.min }} ${{ steps.random.outputs.hour }} * * *"
git push "https://x-access-token:${{ secrets.PAT_TOKEN }}@github.com/${{ github.repository }}.git" HEAD:main
================================================
FILE: .github/workflows/validate-issue-template.yml
================================================
name: Issue Template Validator
on:
issues:
types: [opened, edited]
permissions:
issues: write
jobs:
validate-issue:
if: github.repository == '14790897/auto-read-liunxdo'
runs-on: ubuntu-latest
steps:
- name: Validate Issue Template
uses: actions/github-script@v7
with:
script: |
const issue = context.payload.issue;
const issueBody = issue.body || '';
const issueTitle = issue.title || '';
// Check for Bug Report template sections
const hasBugDescription = issueBody.includes('### 问题描述');
const hasBugSteps = issueBody.includes('### 重现步骤');
const hasBugAdditionalInfo = issueBody.includes('### 附加信息');
// Check for Feature Request template sections
const hasFeatureBackground = issueBody.includes('### 背景');
const hasFeatureDescription = issueBody.includes('### 功能描述');
let isValid = false;
let missingFields = [];
// Determine template type by checking required sections
// Bug Report requires all 3 sections
const isBugReport = hasBugDescription && hasBugSteps && hasBugAdditionalInfo;
// Feature Request requires both sections
const isFeatureRequest = hasFeatureBackground && hasFeatureDescription;
if (isBugReport) {
// All required sections present for Bug Report
isValid = true;
} else if (isFeatureRequest) {
// All required sections present for Feature Request
isValid = true;
} else {
// Check which template was attempted and what's missing
const bugSectionCount = [hasBugDescription, hasBugSteps, hasBugAdditionalInfo].filter(Boolean).length;
const featureSectionCount = [hasFeatureBackground, hasFeatureDescription].filter(Boolean).length;
if (bugSectionCount > 0 || issueTitle.toLowerCase().includes('[bug]')) {
// Attempted Bug Report
if (!hasBugDescription) missingFields.push('问题描述');
if (!hasBugSteps) missingFields.push('重现步骤');
if (!hasBugAdditionalInfo) missingFields.push('附加信息');
} else if (featureSectionCount > 0 || issueTitle.toLowerCase().includes('[feature]')) {
// Attempted Feature Request
if (!hasFeatureBackground) missingFields.push('背景');
if (!hasFeatureDescription) missingFields.push('功能描述');
} else {
// No template sections found
missingFields.push('未使用任何模板');
}
isValid = false;
}
if (!isValid) {
// Close the issue with a comment
const closeMessage = [
'👋 你好!感谢你提交 Issue。',
'',
'⚠️ 此 Issue 未按照规定的模板填写,已自动关闭。',
'',
`**缺少的字段:** ${missingFields.join('、')}`,
'',
'请使用以下模板之一重新提交:',
`- 🐛 [Bug Report 模板](https://github.com/${context.repo.owner}/${context.repo.repo}/issues/new?template=bug_report_template.md)`,
`- ✨ [Feature Request 模板](https://github.com/${context.repo.owner}/${context.repo.repo}/issues/new?template=feature_request_template.md)`,
'',
'提供完整的信息有助于我们更快地处理你的问题。谢谢!'
].join('\n');
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body: closeMessage
});
await github.rest.issues.update({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
state: 'closed',
state_reason: 'not_planned'
});
core.setFailed('Issue does not follow template and has been closed.');
} else {
console.log('✅ Issue follows the template format');
}
================================================
FILE: .github/workflows/windows_cron_bypassCF.yaml
================================================
name: readLike 小众论坛(勿动)
# GitHub.secrets优先级最高,即使没有设置对应的变量,它也会读取,这时变量为空值,导致报错,.env读取的变量无法覆盖这个值,使用了${PASSWORD_ESCAPED//\#/\\#}来对#转义,需要两个\,但是我直接在env文件使用这种方法是不行的,GitHub action是有效
on:
# schedule:
# 每天 UTC 时间 18:40 运行
# - cron: "40 18 * * *"
workflow_dispatch: # 允许手动触发
jobs:
build:
runs-on: windows-latest
timeout-minutes: 35 # 设置作业超时时间为20分钟
strategy:
matrix:
node-version: [20.x]
env:
# 在作业级别设置环境变量
USERNAMES: ${{ secrets.USERNAMES }}
PASSWORDS: ${{ secrets.PASSWORDS }}
RUN_TIME_LIMIT_MINUTES: ${{secrets.RUN_TIME_LIMIT_MINUTES}}
TELEGRAM_BOT_TOKEN: ${{secrets.TELEGRAM_BOT_TOKEN}}
TELEGRAM_CHAT_ID: ${{secrets.TELEGRAM_CHAT_ID}}
steps:
- uses: actions/checkout@v3 # 检出仓库
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- name: Install dependencies
run: |
npm install
#github action设置的action环境变量会完全替换掉env文件的读取,所以需要在action里手动进行加载env文件
- name: Load environment variables
run: |
echo "Debug: Checking if .env file exists..."
# 检查 .env 文件是否存在
if [ -f .env ]; then
echo ".env file found. Loading environment variables from .env file"
# 加载 .env 文件中的默认值
set -a
source .env
set +a
echo "Loaded .env variables:"
else
echo ".env file not found. Skipping loading."
fi
if [ -n "${{ secrets.WEBSITE }}" ] && [ ! -z "${{ secrets.WEBSITE }}" ]; then
echo "Using GitHub Secret for WEBSITE"
echo "WEBSITE=${{ secrets.WEBSITE }}" >> $GITHUB_ENV
else
echo "WEBSITE=${WEBSITE}" >> $GITHUB_ENV
fi
shell: bash
- name: Run a script
run: node bypasscf.js --USERNAMES "$USERNAMES" --PASSWORDS "$PASSWORDS" --WEBSITE "$WEBSITE"
================================================
FILE: .gitignore
================================================
node_modules/
.env.local*
screenshots
.env
================================================
FILE: CHANGELOG.md
================================================
# Changelog
### [1.15.1](https://www.github.com/14790897/auto-read-liunxdo/compare/v1.15.0...v1.15.1) (2026-02-11)
### Bug Fixes
* db测试失败因为没有环境变量 ([5f198a3](https://www.github.com/14790897/auto-read-liunxdo/commit/5f198a302203b18e0f4db85be4630e7fdb72d8cf))
## [1.15.0](https://www.github.com/14790897/auto-read-liunxdo/compare/v1.14.4...v1.15.0) (2026-01-21)
### Features
* add new script for user login automation and health check endpoint ([c10b990](https://www.github.com/14790897/auto-read-liunxdo/commit/c10b990a3494255c5e53585c403da65df49ccfc3))
* add root certificate files for PostgreSQL and MongoDB ([5584234](https://www.github.com/14790897/auto-read-liunxdo/commit/5584234d80c66c8c2615392b0b12866bad710837))
* enhance environment variable handling for RSS and topic data fetch features ([4c446fd](https://www.github.com/14790897/auto-read-liunxdo/commit/4c446fdaa5ceb76abd244bbb57cb2f7d8f89be28))
### Bug Fixes
* add repository condition to workflow jobs for consistency ([bc2c5ba](https://www.github.com/14790897/auto-read-liunxdo/commit/bc2c5ba1efe076d5d55f233f42df4067cca100f6))
* ensure environment variables for RSS and topic data fetch features are set to empty string if not defined ([7759c6a](https://www.github.com/14790897/auto-read-liunxdo/commit/7759c6aef670957f7eb76a4feefd24eaf93feeec))
* increase connection timeout to 15 seconds for better handling of cross-region network latency ([1e914c5](https://www.github.com/14790897/auto-read-liunxdo/commit/1e914c5264c95642453fb5a40e34ec9af8c6d735))
* update primary database query to use CockroachDB instead of Aiven PostgreSQL ([2d4419b](https://www.github.com/14790897/auto-read-liunxdo/commit/2d4419b6cd7792bbcc15eb01714788bf21d12427))
* update RSS and topic data fetch environment variable handling for clarity ([3ae4ca6](https://www.github.com/14790897/auto-read-liunxdo/commit/3ae4ca66b16095d0e40d5a6ed541fc12990b5707))
### [1.14.4](https://www.github.com/14790897/auto-read-liunxdo/compare/v1.14.3...v1.14.4) (2025-11-03)
### Bug Fixes
* rm hg ([0cd3ab6](https://www.github.com/14790897/auto-read-liunxdo/commit/0cd3ab642fe9cfca109a1732e4ec1e55b3412b63))
### [1.14.3](https://www.github.com/14790897/auto-read-liunxdo/compare/v1.14.2...v1.14.3) (2025-10-26)
### Bug Fixes
* ignore cron push ([fbdb7dd](https://www.github.com/14790897/auto-read-liunxdo/commit/fbdb7ddb052c75c7be57c169e6e7f0af3cc82d01))
### [1.14.2](https://www.github.com/14790897/auto-read-liunxdo/compare/v1.14.1...v1.14.2) (2025-09-26)
### Bug Fixes
* // [@exclude](https://www.github.com/exclude) https://linux.do/a/9611/0 ([150d94a](https://www.github.com/14790897/auto-read-liunxdo/commit/150d94a49d6f47468ec95838ee6f70a17eb0d13b))
* TargetCloseError: Protocol error (Page.addScriptToEvaluateOnNewDocument): Target closed ([a0633d0](https://www.github.com/14790897/auto-read-liunxdo/commit/a0633d06237c60aa49509b5359c30e992fea6ab7))
### [1.14.1](https://www.github.com/14790897/auto-read-liunxdo/compare/v1.14.0...v1.14.1) (2025-06-25)
### Bug Fixes
* 变量错误 ([a0cd8d2](https://www.github.com/14790897/auto-read-liunxdo/commit/a0cd8d2336e4ec7782e42c5e784d96b09b234415))
* 环境变量错误 ([ca0426e](https://www.github.com/14790897/auto-read-liunxdo/commit/ca0426e1651f50d8bcf7b0a689fe833b6051fe45))
## [1.14.0](https://www.github.com/14790897/auto-read-liunxdo/compare/v1.13.0...v1.14.0) (2025-06-11)
### Features
* json抓取 ([cc0bbaa](https://www.github.com/14790897/auto-read-liunxdo/commit/cc0bbaa6e24edc5cc1e80d1df81064a734b2f410))
* mongodb ([acf7be0](https://www.github.com/14790897/auto-read-liunxdo/commit/acf7be0e65317f7237eea216fa846fe3c3c025ae))
* mysql ([ad79373](https://www.github.com/14790897/auto-read-liunxdo/commit/ad79373b94c7f1deb28cd89fbe553b2e5dd9d1f8))
* 修复保存逻辑 ([b36e628](https://www.github.com/14790897/auto-read-liunxdo/commit/b36e62818cf7cc497c94dbcd9177974882cb4398))
* 多数据库 ([6a9a857](https://www.github.com/14790897/auto-read-liunxdo/commit/6a9a857ecd14cc3fd53f6b82147599a1cf3b64c1))
## [1.13.0](https://www.github.com/14790897/auto-read-liunxdo/compare/v1.12.0...v1.13.0) (2025-06-05)
### Features
* 优化发送逻辑 ([6387c9e](https://www.github.com/14790897/auto-read-liunxdo/commit/6387c9e5daa0954760c6543281a207d46849cb36))
* 保存到数据库 ([ded9f4f](https://www.github.com/14790897/auto-read-liunxdo/commit/ded9f4fe7e378854ea2d9b31521f5b0bf699f18b))
* 去除锁,靠数据库排斥相同数据 ([2970b3a](https://www.github.com/14790897/auto-read-liunxdo/commit/2970b3a9910234ecba5ebeb69e0ce7bf12a91ef5))
* 抓取rss内容发送到电报 ([ac66311](https://www.github.com/14790897/auto-read-liunxdo/commit/ac66311b01c231a0d0088b8672bd6fca7ed2194b))
* 随机点赞时间 ([7630dd7](https://www.github.com/14790897/auto-read-liunxdo/commit/7630dd78ac4f101a08a03463efec486059ae8e94))
### Bug Fixes
* 之前没有添加action的其它环境变量 ([353edb3](https://www.github.com/14790897/auto-read-liunxdo/commit/353edb34fbde487642116e7bd13b0c35633d4243))
* 先保存后查重 ([932e9e6](https://www.github.com/14790897/auto-read-liunxdo/commit/932e9e65f07742cc22930ee380dbd3d8ec5a4ea6))
* 字符串 "false" 在 JavaScript 中是真值(truthy) ([3c0bcad](https://www.github.com/14790897/auto-read-liunxdo/commit/3c0bcadb26c9385923c536592a64421222d77fbf))
* 存在死锁问题 ([d45d7a3](https://www.github.com/14790897/auto-read-liunxdo/commit/d45d7a3b22bb6305905c220174354320350f56ac))
* 执行顺序 ([d0440b8](https://www.github.com/14790897/auto-read-liunxdo/commit/d0440b8242c99e959c39dfe403da228152e9bcb7))
* 电报未渲染 ([e3bc349](https://www.github.com/14790897/auto-read-liunxdo/commit/e3bc34938805d6545abcb4d9bfab40461a1ec489))
* 电报未渲染 ([27b40e2](https://www.github.com/14790897/auto-read-liunxdo/commit/27b40e28c36bcbb1e3fea4339dc273146ad9e536))
## [1.12.0](https://www.github.com/14790897/auto-read-liunxdo/compare/v1.11.0...v1.12.0) (2025-05-31)
### Features
* random time ([42253d7](https://www.github.com/14790897/auto-read-liunxdo/commit/42253d72cd4f9f5f385a2058a34afac1b7d5d1a7))
### Bug Fixes
* add 错误 ([29106ab](https://www.github.com/14790897/auto-read-liunxdo/commit/29106ab6f76fac997f25b2533b63ffc776535b5c))
* 文件名错误 ([e58bc8f](https://www.github.com/14790897/auto-read-liunxdo/commit/e58bc8f54ed107308c3327f6e89d21962ac8ad83))
* 文件名错误 ([f02dcdb](https://www.github.com/14790897/auto-read-liunxdo/commit/f02dcdb9d7d60dd7b7f7bfb383b29a6e7f50e3dd))
* 无写入权限 ([e10a452](https://www.github.com/14790897/auto-read-liunxdo/commit/e10a452297d886d6cbc1507278ee1eb9b6035fe8))
* 未使用单引号 ([1089f3d](https://www.github.com/14790897/auto-read-liunxdo/commit/1089f3dcd2ddfb8b416fb4ada24aa42a9223269e))
* 权限问题 ([975f842](https://www.github.com/14790897/auto-read-liunxdo/commit/975f842556341a83c3f0cfcde2633fe47f811bf4))
* 权限问题 ([0a3890b](https://www.github.com/14790897/auto-read-liunxdo/commit/0a3890bfc079222b566ded7ee07839a74e303bb0))
* 权限问题 ([ab0d35a](https://www.github.com/14790897/auto-read-liunxdo/commit/ab0d35ab84229146dacee3ed5bfe2ffd618882d0))
* 权限问题 ([a2be5de](https://www.github.com/14790897/auto-read-liunxdo/commit/a2be5de1f25fff8b13b4970174bc608fa2058643))
* 权限问题 ([9bedc0c](https://www.github.com/14790897/auto-read-liunxdo/commit/9bedc0cbe2f06b90aab2a451f2abf5daddc1aecc))
* 权限问题 ([391f6c1](https://www.github.com/14790897/auto-read-liunxdo/commit/391f6c16fcabd4a8172128341914e0b0f490d59e))
* 权限问题 ([367e805](https://www.github.com/14790897/auto-read-liunxdo/commit/367e805f8085422fd49a9f4d4af57f7d294a7747))
* 权限问题 ([c046c1d](https://www.github.com/14790897/auto-read-liunxdo/commit/c046c1d47c299d0e991d3219fd3c6f68c8dd0b6a))
* 权限问题 ([da69937](https://www.github.com/14790897/auto-read-liunxdo/commit/da69937f6b52721223c961e74e8ea238413aa331))
* 权限问题 ([4a99c19](https://www.github.com/14790897/auto-read-liunxdo/commit/4a99c193afe05a96950ac82e61351ba845992641))
* 权限问题 ([ee6115e](https://www.github.com/14790897/auto-read-liunxdo/commit/ee6115e240e0ee717b0b4c535961a8c74f7ef22b))
* 权限问题 ([8aa6061](https://www.github.com/14790897/auto-read-liunxdo/commit/8aa6061d74d105cf1ca96aedf9d85186faffb22c))
## [1.11.0](https://www.github.com/14790897/auto-read-liunxdo/compare/v1.10.0...v1.11.0) (2025-05-30)
### Features
* action缓存包 ([b815418](https://www.github.com/14790897/auto-read-liunxdo/commit/b815418120ec84678fb6a08480103ece8f3b920c))
### Bug Fixes
* remove apt ([9dbea4c](https://www.github.com/14790897/auto-read-liunxdo/commit/9dbea4c00cd3a78d9ba03c3ae3511858ed2da721))
## [1.10.0](https://www.github.com/14790897/auto-read-liunxdo/compare/v1.9.0...v1.10.0) (2025-03-26)
### Features
* docker 端口 映射 ([83cd9f2](https://www.github.com/14790897/auto-read-liunxdo/commit/83cd9f26d8991101907c66ae774f48932ee09a67))
* 定时清理容器的/tmp ([4265354](https://www.github.com/14790897/auto-read-liunxdo/commit/42653542540bb3ae96c77cbf701967c1e1b9941f))
* 随机加载点赞脚本 ([1878a94](https://www.github.com/14790897/auto-read-liunxdo/commit/1878a94065dda661977919606c5a7c037cb74e58))
### Bug Fixes
* env端口错误 ([4fd5d39](https://www.github.com/14790897/auto-read-liunxdo/commit/4fd5d39404a369c9b1b9ac216d7fd06a78183be3))
* hg ([8b7f5a0](https://www.github.com/14790897/auto-read-liunxdo/commit/8b7f5a09ec89e3bb1285974fa85fe3446e0e20fc))
* 伪装huggingface ([a9856dc](https://www.github.com/14790897/auto-read-liunxdo/commit/a9856dce85f062ce8867def16f7acfe8937ca1d8))
* 禁止生成core dump ([895760e](https://www.github.com/14790897/auto-read-liunxdo/commit/895760e11d493df9aa70065059e0a2f6197ca632))
## [1.9.0](https://www.github.com/14790897/auto-read-liunxdo/compare/v1.8.2...v1.9.0) (2025-02-12)
### Features
* 支持禁用自动点赞 ([f6f5e26](https://www.github.com/14790897/auto-read-liunxdo/commit/f6f5e2692eb6974117f0720c0be0623105c656b4))
### [1.8.2](https://www.github.com/14790897/auto-read-liunxdo/compare/v1.8.1...v1.8.2) (2025-02-12)
### Bug Fixes
* 存在无法点击登录按钮的情况,原因未知,在等待跳转前,加入按钮点击 ([1d67b5e](https://www.github.com/14790897/auto-read-liunxdo/commit/1d67b5e0fa531148ceff3c8c3f083d253a98db75))
### [1.8.1](https://www.github.com/14790897/auto-read-liunxdo/compare/v1.8.0...v1.8.1) (2025-01-15)
### Bug Fixes
* 展示信息 ([27ffc94](https://www.github.com/14790897/auto-read-liunxdo/commit/27ffc94610e88df9d06e847461cddb5faff27b6b))
## [1.8.0](https://www.github.com/14790897/auto-read-liunxdo/compare/v1.7.0...v1.8.0) (2025-01-14)
### Features
* 登录失败重试三次 ([2fdee4e](https://www.github.com/14790897/auto-read-liunxdo/commit/2fdee4e6f23f16ae565594a35008101596400b36))
* 超时和密码错误逻辑分开 ([c2f61e3](https://www.github.com/14790897/auto-read-liunxdo/commit/c2f61e35f16b8fb055aa3b612c2b3c1de85194e8))
### Bug Fixes
* error ([cff54ac](https://www.github.com/14790897/auto-read-liunxdo/commit/cff54ac99fe95466b7e7b5fd2f5356193465fcec))
* error ([a106c34](https://www.github.com/14790897/auto-read-liunxdo/commit/a106c34866588a0e401c5c3e8abe0619422e349c))
* error ([e689db0](https://www.github.com/14790897/auto-read-liunxdo/commit/e689db064937e9527d6208f009c5f85b2a2ae1ce))
* xvfb包 ([da5549a](https://www.github.com/14790897/auto-read-liunxdo/commit/da5549abfb71f0651ea51dc502b039c5375ef094))
* 未使用await ([2505906](https://www.github.com/14790897/auto-read-liunxdo/commit/2505906771302de2a7f1f49abd5f7e9b75421f53))
* 检测密码错误提示 ([22d3766](https://www.github.com/14790897/auto-read-liunxdo/commit/22d3766248481d1290b83e7e3c0eddd34def4573))
* 登录失败throw error ([2880de3](https://www.github.com/14790897/auto-read-liunxdo/commit/2880de320a1829b6cb070ab8f1f647c2e2e35356))
## [1.7.0](https://www.github.com/14790897/auto-read-liunxdo/compare/v1.6.0...v1.7.0) (2024-11-29)
### Features
* 健康探针 ([0768f30](https://www.github.com/14790897/auto-read-liunxdo/commit/0768f303e6c4a4074f3528970f5329c53d55ae07))
* 加上首页 ([b561fde](https://www.github.com/14790897/auto-read-liunxdo/commit/b561fde351ad031f4e0e1396aa4683ea0572ab1a))
### Bug Fixes
* 尝试改进dockerfile ([533a5c9](https://www.github.com/14790897/auto-read-liunxdo/commit/533a5c9d21c35c1b3c8d2d3d2c46a0dedd070493))
* 暴露端口问题 ([78a1aca](https://www.github.com/14790897/auto-read-liunxdo/commit/78a1aca637909f65909eadd85b45a36704646020))
## [1.6.0](https://www.github.com/14790897/auto-read-liunxdo/compare/v1.5.0...v1.6.0) (2024-10-10)
### Features
* last_read_post_number完善自动阅读跳转 ([f872df4](https://www.github.com/14790897/auto-read-liunxdo/commit/f872df46d04c5726c023a6f1a3d464bf21607cc1))
* 先点赞再阅读 ([a9f016b](https://www.github.com/14790897/auto-read-liunxdo/commit/a9f016be196074d75f549ed408892ed0809e6095))
* 增加登录成功通知 ([7d4c96d](https://www.github.com/14790897/auto-read-liunxdo/commit/7d4c96dd78f7d5332a8d9d36729f46d0746c83ef))
* 自动点赞的docker ([d4b7195](https://www.github.com/14790897/auto-read-liunxdo/commit/d4b7195047ca12f4991583d93bd1a514d56a960a))
### Bug Fixes
* action不能指定点赞用户 ([4fb63a3](https://www.github.com/14790897/auto-read-liunxdo/commit/4fb63a3b035f01f05316c3b37b4cc1c305e1b933))
* docker读取.env.local变量 ([c5a61bf](https://www.github.com/14790897/auto-read-liunxdo/commit/c5a61bf5ac38cd05fbde5204976d52541f3c6dfb))
* 启动间隔时间修复 ([3d32bba](https://www.github.com/14790897/auto-read-liunxdo/commit/3d32bba8c471750b7e0e9b508eaca9e49f258c52))
## [1.5.0](https://www.github.com/14790897/auto-read-liunxdo/compare/v1.4.0...v1.5.0) (2024-09-12)
### Features
* bypasscf使用更好的脚本 ([0fd5340](https://www.github.com/14790897/auto-read-liunxdo/commit/0fd534051629d85f9a7f31d106abf42ca4743576))
* 优化dockercompose 使得它可以直接读取env文件 ([08a9a4f](https://www.github.com/14790897/auto-read-liunxdo/commit/08a9a4fb044350d72b939531b9398fc6467cdcff))
* 使用api获得文章列表 ([ccca920](https://www.github.com/14790897/auto-read-liunxdo/commit/ccca9208b28f155af58c2513e2a0040b26b77905))
* 使用search获得不重复的post ([046e1e6](https://www.github.com/14790897/auto-read-liunxdo/commit/046e1e6026a6484ccfab4fea134f7893782c1dd5))
* 可以设置结束时间,避免action报错,默认15分钟 ([8f19e92](https://www.github.com/14790897/auto-read-liunxdo/commit/8f19e92adb2ce422787be6a4bf0abece9c385b4d))
* 多账号分批处理 ([26e516f](https://www.github.com/14790897/auto-read-liunxdo/commit/26e516fbb451769383a31804739a80786ff5c563))
* 多账号分批处理 ([a5002fb](https://www.github.com/14790897/auto-read-liunxdo/commit/a5002fb9bb104013dd5094f0a32ae37d67107e03))
* 多账号分批处理 ([500c34e](https://www.github.com/14790897/auto-read-liunxdo/commit/500c34e840c3781f6ce3236e22b5d7a2649639de))
* 点赞特定用户cron ([214b3b9](https://www.github.com/14790897/auto-read-liunxdo/commit/214b3b9507d74df9a30da8df3db0fe1459d3853f))
* 电报机器人消息推送 ([43f1f42](https://www.github.com/14790897/auto-read-liunxdo/commit/43f1f425a94589267bc716e144549088359c0ff4))
* 自动点赞特定用户 ([e2340d4](https://www.github.com/14790897/auto-read-liunxdo/commit/e2340d4433fdd8b4a05af782e89dcd6285f9b9ef))
### Bug Fixes
* cron中变量的默认值 ([10499e2](https://www.github.com/14790897/auto-read-liunxdo/commit/10499e2aa7fb02075ee7925cbeb2125a59a2d922))
* cron中变量的默认值 ([9d0302a](https://www.github.com/14790897/auto-read-liunxdo/commit/9d0302a9098267ea587456a7f607e547cd004d86))
* cron中变量的默认值 ([09fd9bd](https://www.github.com/14790897/auto-read-liunxdo/commit/09fd9bd7a0f0e2b047d3c02bd7f4d93df22503dd))
* cron中变量的默认值 ([c1c07ca](https://www.github.com/14790897/auto-read-liunxdo/commit/c1c07cae1bd109f8785907b35ebc63b28dce6e93))
* cron中变量的默认值 ([6bc8bce](https://www.github.com/14790897/auto-read-liunxdo/commit/6bc8bce2a42bbb414450454f9bd36c5a07da8fc5))
* cron中变量的默认值 ([b2227b6](https://www.github.com/14790897/auto-read-liunxdo/commit/b2227b60a0fd3af4e5abe0dbe19b85bc42ba855c))
* cron中变量的默认值 ([b9b72fc](https://www.github.com/14790897/auto-read-liunxdo/commit/b9b72fce05be1046b7f917d9820ba3d9260ab476))
* cron中变量的默认值 ([4cc640a](https://www.github.com/14790897/auto-read-liunxdo/commit/4cc640a7c15f30c0f6455a2e6343fd4681960a67))
* cron中变量的默认值 ([3b044c1](https://www.github.com/14790897/auto-read-liunxdo/commit/3b044c1cbc57cb043e53a0de15287cbd35f0fce1))
* cron中变量的默认值 ([72af582](https://www.github.com/14790897/auto-read-liunxdo/commit/72af5821a782b3eb6174372d89dbff5a78656ecf))
* cron中变量的默认值 ([8fedb7b](https://www.github.com/14790897/auto-read-liunxdo/commit/8fedb7be3930f02fa2b89a49a35484e0f8cfd273))
* env.local后才能读取环境变量,page.evaluate变量必须从外部显示的传入, 因为在浏览器上下文它是读取不了的 ([f57d512](https://www.github.com/14790897/auto-read-liunxdo/commit/f57d5128dae5e3cffc9928589cf9c427e84d648c))
* env写错了 ([19d3b64](https://www.github.com/14790897/auto-read-liunxdo/commit/19d3b644445bdd26bb4a3e5a5ebc480ed085cbee))
* run-at document-end可以修复有时候脚本不运行的问题 ([a0c35f2](https://www.github.com/14790897/auto-read-liunxdo/commit/a0c35f26a2fb187950d4a220ed096fd419e59c88))
* throw error导致无法运行 ([67811e3](https://www.github.com/14790897/auto-read-liunxdo/commit/67811e35394bf02ef1af6850dffd6b888c4091ae))
* 保存用户的时候需要清除 localStorage.removeItem("lastOffset"); ([edb72ac](https://www.github.com/14790897/auto-read-liunxdo/commit/edb72ac66ac6bcd864781d2c85d7795bc15881d9))
* 其实不需要 !topic.unseen ([d2a0ab3](https://www.github.com/14790897/auto-read-liunxdo/commit/d2a0ab3342fcd26498fab9c0e6ebac815b4c353c))
* 只有一个账号会立刻停止的问题 ([672ebee](https://www.github.com/14790897/auto-read-liunxdo/commit/672ebee00c954cd41661751a35a08868eb3d239d))
* 密码转义 ([e5802ab](https://www.github.com/14790897/auto-read-liunxdo/commit/e5802abbf770c5a65cfaee3515529d75558b8068))
* 直接使用油猴脚本 ([d429ca9](https://www.github.com/14790897/auto-read-liunxdo/commit/d429ca931cf8bddfbd14788a451e0c6d2cf05313))
* 返回 ([9f5d398](https://www.github.com/14790897/auto-read-liunxdo/commit/9f5d39814940c88628bdd648b7766143734f0201))
* 通过在主进程直接设置localstorage变量,避免单独设置 ([99b6725](https://www.github.com/14790897/auto-read-liunxdo/commit/99b67252ba7536a75708b6eb19956ace04a71122))
* 重置用户的时候需要清空post列表 ([5980c9a](https://www.github.com/14790897/auto-read-liunxdo/commit/5980c9a3b205af32fbceedc157400330eb77f3b0))
## [1.4.0](https://www.github.com/14790897/auto-read-liunxdo/compare/v1.3.0...v1.4.0) (2024-08-08)
### Features
* action中如果secrets未定义则使用env文件 ([cc2812f](https://www.github.com/14790897/auto-read-liunxdo/commit/cc2812f6a1bdf43bc03c676a963b00ce8271f732))
* 增加了对小众软件论坛的支持(https://linux.do/t/topic/169209/166) ([598913c](https://www.github.com/14790897/auto-read-liunxdo/commit/598913c09b9bc9b880fe9f974c3da490acb6ca55))
### Bug Fixes
* action 中secret读取特殊字符处理 ([5457abc](https://www.github.com/14790897/auto-read-liunxdo/commit/5457abce09c0b26a54ef6b67b0563b49ca567e97))
* docker-compose.yml命令错误 ([ec3cedc](https://www.github.com/14790897/auto-read-liunxdo/commit/ec3cedc83895cbf8dc759770c1203fa718b52dfd))
* docker-compose.yml命令错误 ([2b4d73d](https://www.github.com/14790897/auto-read-liunxdo/commit/2b4d73de2becd56f8a1c4d7ecc8aff71de619225))
* docker-compose.yml命令错误 ([2d8a099](https://www.github.com/14790897/auto-read-liunxdo/commit/2d8a099990d986741189129dfb67ec8e8869325e))
* docker-compose.yml命令错误(https://linux.do/t/topic/169209/158) ([b83b09c](https://www.github.com/14790897/auto-read-liunxdo/commit/b83b09cbc0b96b846d78d8aa1ef242e4429bac9b))
* docker命令执行的代码 ([aa36a2b](https://www.github.com/14790897/auto-read-liunxdo/commit/aa36a2b754e2591c65dfdd9314e8676aeba60b2b))
* loginbutton作用域问题 ([1f626aa](https://www.github.com/14790897/auto-read-liunxdo/commit/1f626aa8cd8299086f91a4019779500cbd9abbfb))
* Windows需要等待cf的完成 ([bdcbeaf](https://www.github.com/14790897/auto-read-liunxdo/commit/bdcbeaff2403123b74a0e031c28560b16265798b))
* 似乎不需要特殊处理 ([dc96005](https://www.github.com/14790897/auto-read-liunxdo/commit/dc960051002d88f27e5fd5ccde5a22d6be511250))
* 增加navigation超时时长 ([7c92ff0](https://www.github.com/14790897/auto-read-liunxdo/commit/7c92ff0b3d58753571674f133eaf3bd88d9c75de))
* 增加点赞间隔 ([dc472be](https://www.github.com/14790897/auto-read-liunxdo/commit/dc472be03be350e2a69d7adc25ae628f1193f241))
* 增加点赞间隔,避免频繁429 ([706198d](https://www.github.com/14790897/auto-read-liunxdo/commit/706198d1359157da83bb839827278a6f0b61c01c))
* 还是要找button ([ba801c9](https://www.github.com/14790897/auto-read-liunxdo/commit/ba801c9cc82b6ba5825fc79111c5d8d319c50cf3))
## [1.3.0](https://www.github.com/14790897/auto-read-liunxdo/compare/v1.2.1...v1.3.0) (2024-08-04)
### Features
* bypasscf可以绕过cf了 ([197e04f](https://www.github.com/14790897/auto-read-liunxdo/commit/197e04f1b67164ccabdb8e8347039c2ceb51d8e7))
* 截图记录功能 ([2e98654](https://www.github.com/14790897/auto-read-liunxdo/commit/2e986540f9170ef345b8d2a3e8df7b4b7a8a00c2))
* 新的cron ([5e005eb](https://www.github.com/14790897/auto-read-liunxdo/commit/5e005eb4591f193c3b45ba4029e4363c7356f62e))
### Bug Fixes
* action大小写问题 ([aeeb918](https://www.github.com/14790897/auto-read-liunxdo/commit/aeeb918fe199003b66279aa1e182496ed4b4d683))
* auto加双引号 ([b38c22e](https://www.github.com/14790897/auto-read-liunxdo/commit/b38c22ee52165cecdae63c296434f564af7f95f0))
* docker compose环境变量配置 ([b67d946](https://www.github.com/14790897/auto-read-liunxdo/commit/b67d94633920a9c8f2c5eaf6a8a08590d443cf7c))
* docker compose环境变量配置 ([a7f19bb](https://www.github.com/14790897/auto-read-liunxdo/commit/a7f19bbd3d139e5ede1402127fed13ce5bfea9f1))
* es6 dirname不存在 ([b5f02b4](https://www.github.com/14790897/auto-read-liunxdo/commit/b5f02b44c4b2c1d02d1ac83d1bcfe8d2f2e55096))
* Windows有头,Linux无头 ([5a39ded](https://www.github.com/14790897/auto-read-liunxdo/commit/5a39ded0ca6afc3790d891d70928cc7a863c5e01))
* 使用{ waitUntil: "domcontentloaded" }避免超时错误 ([8c3e38a](https://www.github.com/14790897/auto-read-liunxdo/commit/8c3e38a73efbc51c02191f0bb71350ff4486d9f5))
### [1.2.1](https://www.github.com/14790897/auto-read-liunxdo/compare/v1.2.0...v1.2.1) (2024-05-20)
### Bug Fixes
* env注释 ([22b118f](https://www.github.com/14790897/auto-read-liunxdo/commit/22b118f5c18f24471feb48890bcbc32f905037a0))
* 使用domcontentloaded等待页面跳转 ([611290c](https://www.github.com/14790897/auto-read-liunxdo/commit/611290c4c64aa7d75736156dae61674284d67499))
* 点赞每日重置,修复只能启动一次自动点赞 ([15529df](https://www.github.com/14790897/auto-read-liunxdo/commit/15529df487d307cd421008cb9477e369cda6075a))
* 错误处理 ([c1ad79f](https://www.github.com/14790897/auto-read-liunxdo/commit/c1ad79ff26d3a806b0e9ae450b4d36a4f8e134c1))
## [1.2.0](https://www.github.com/14790897/auto-read-liunxdo/compare/v1.1.0...v1.2.0) (2024-05-07)
### Features
* 增加local变量的读取方便调试 ([08c5631](https://www.github.com/14790897/auto-read-liunxdo/commit/08c563143d7fd1ae868e90a950cbd51ea46fe279))
### Bug Fixes
* headless改为true ([77bee42](https://www.github.com/14790897/auto-read-liunxdo/commit/77bee42accad682cc9ed4e680b1d529d7274d015))
## [1.1.0](https://www.github.com/14790897/auto-read-liunxdo/compare/v1.0.0...v1.1.0) (2024-04-30)
### Features
* cron的docker ([3f96acc](https://www.github.com/14790897/auto-read-liunxdo/commit/3f96accce19c263263ad953f3c55f422a0c16c37))
* docker 定时运行 ([963a2ed](https://www.github.com/14790897/auto-read-liunxdo/commit/963a2edf466cf113908493e1144f23c1ea9c95c6))
* docker输出日志 ([a0a13e6](https://www.github.com/14790897/auto-read-liunxdo/commit/a0a13e6f09d6226f3811cb82425852f4283f48f5))
* 环境变量可以配置阅读网站 ([fc1e52b](https://www.github.com/14790897/auto-read-liunxdo/commit/fc1e52b20fe8ad17a3c5215a3732da5d91a49d12))
* 适用于meta.discourse ([ada117a](https://www.github.com/14790897/auto-read-liunxdo/commit/ada117af31053029fcec28292844175db6b5d6a6))
### Bug Fixes
* cron ([13e2181](https://www.github.com/14790897/auto-read-liunxdo/commit/13e21815c8b4f081cef4c76d113781451e738030))
* cron ([3986f1d](https://www.github.com/14790897/auto-read-liunxdo/commit/3986f1d22c2c6be1ff0e373fad5862dcf2f3b4e5))
* cron ([d428a63](https://www.github.com/14790897/auto-read-liunxdo/commit/d428a6357f2c705c6b288188909fa5f62f35ab5a))
* cron ([82376ca](https://www.github.com/14790897/auto-read-liunxdo/commit/82376ca52bbd374ce8596b4583bc3e09e30d741c))
* cron ([b3f17c7](https://www.github.com/14790897/auto-read-liunxdo/commit/b3f17c75fbc83b2f4be005e6de8d683a4d93ad70))
* cron ([feec05e](https://www.github.com/14790897/auto-read-liunxdo/commit/feec05e10149f7314894b3d6284285f2aaa564d5))
* cron ([1ba6a4d](https://www.github.com/14790897/auto-read-liunxdo/commit/1ba6a4d7862f6d9c46b464be107b8ac7aaed66b3))
* cron ([bdd3db0](https://www.github.com/14790897/auto-read-liunxdo/commit/bdd3db0d9e0bf3109fbe2af3fee3731c9fd8478f))
* cron ([51d9566](https://www.github.com/14790897/auto-read-liunxdo/commit/51d95663b21121328ddd257e87c529fd807b7155))
* cron ([51326fe](https://www.github.com/14790897/auto-read-liunxdo/commit/51326fec312ada029e29fb8fe7b46996ae17d3a6))
* cron ([69b1801](https://www.github.com/14790897/auto-read-liunxdo/commit/69b1801b11af87685761927488937ac18592b42c))
* cron bug ([e749db7](https://www.github.com/14790897/auto-read-liunxdo/commit/e749db7d50586082e8efae2e3e3cd8b8a1f3dd56))
* cron docker ([a572a72](https://www.github.com/14790897/auto-read-liunxdo/commit/a572a720be95a8d1496699fd2203cace0cc53041))
* cron添加执行权限 ([c57da15](https://www.github.com/14790897/auto-read-liunxdo/commit/c57da15f3bd2e5f119a8f145adfb9c97d1a624e1))
* docker ([effbaa1](https://www.github.com/14790897/auto-read-liunxdo/commit/effbaa1b6982b0dd90494c0ddc9726481a824e73))
* docker 环境变量配置 ([b651584](https://www.github.com/14790897/auto-read-liunxdo/commit/b651584caa5fed554f9b95707d87e0465e3ed698))
* remove button in pteer ([edc8ef0](https://www.github.com/14790897/auto-read-liunxdo/commit/edc8ef04eb4a9034d46194722864d00f32aaadf1))
* workdir ([6083de8](https://www.github.com/14790897/auto-read-liunxdo/commit/6083de8418e1f2f8f34937f73716d30a40b673bd))
* 权限 ([4c33ce9](https://www.github.com/14790897/auto-read-liunxdo/commit/4c33ce93f29013f88611ed29d4177d2ed935fe98))
## 1.0.0 (2024-04-05)
### Features
* puppeteer ([8952911](https://www.github.com/14790897/auto-read-liunxdo/commit/895291148807dc669c10a9e0481cb9a024c57577))
* 再加一个链接 ([60cc6b0](https://www.github.com/14790897/auto-read-liunxdo/commit/60cc6b03fe884ca700b8645f646801f8d7ef088e))
* 增加脚本图标 ([61e2d35](https://www.github.com/14790897/auto-read-liunxdo/commit/61e2d354ce5b8e7c54f65233ffb2f0d89e7534fe))
* 多浏览器间隔启动 ([0647c0b](https://www.github.com/14790897/auto-read-liunxdo/commit/0647c0b721972db19451ba73a53dbe4a6831e52a))
* 多账户功能 ([c922667](https://www.github.com/14790897/auto-read-liunxdo/commit/c9226675ca22c826e09959989154cd91309d027a))
* 完善登录等待逻辑 ([6134567](https://www.github.com/14790897/auto-read-liunxdo/commit/6134567566ef695cb33244e52c08d4a0e0b1f8a7))
* 找按钮逻辑优化 ([4e392a1](https://www.github.com/14790897/auto-read-liunxdo/commit/4e392a125b6ecc7e994fa91ff67dd64cc3e01eeb))
* 收集报错 ([3b12c3b](https://www.github.com/14790897/auto-read-liunxdo/commit/3b12c3bcef358df0e7b12ccd5263ace8c9fc4eb7))
* 更好的寻找未读过的文章,但可能有问题 ([5e1e9ce](https://www.github.com/14790897/auto-read-liunxdo/commit/5e1e9ce390e886259f7e92a19a7b02201e1e1f74))
* 检查avatarImg判断登录状况 ([19efb2f](https://www.github.com/14790897/auto-read-liunxdo/commit/19efb2f74918e1e4dc8b72992f2e06a4d1d217eb))
* **点赞:** 防止已赞过的被取消 ([fcc2b40](https://www.github.com/14790897/auto-read-liunxdo/commit/fcc2b40c70c8475f83be4af2b4a7c5be601373bb))
* 自动点赞 ([a9dd8d7](https://www.github.com/14790897/auto-read-liunxdo/commit/a9dd8d74d5bcbcd9836ff0fd5df3c5014188c5a8))
* 自动点赞按钮 ([843f61f](https://www.github.com/14790897/auto-read-liunxdo/commit/843f61fe5178d7a6c4ae968a5aef2457efbda238))
* 设置正常的请求头 ([3eb58de](https://www.github.com/14790897/auto-read-liunxdo/commit/3eb58dec6e069182a852408c2900dff1b5f7fe83))
* 设置点赞上限 ([15a6ba9](https://www.github.com/14790897/auto-read-liunxdo/commit/15a6ba9cf5bccbea6ff33a5c0655e58b30e44854))
### Bug Fixes
* headless ([184461e](https://www.github.com/14790897/auto-read-liunxdo/commit/184461e27b62d0e57e0da4679b56b75e3f3a6535))
* localstorage无法访问 ([f2c1e9f](https://www.github.com/14790897/auto-read-liunxdo/commit/f2c1e9ff9ca27bd6d48296d3a3a0931b6184fba0))
* 不要刷新就启动 ([670992a](https://www.github.com/14790897/auto-read-liunxdo/commit/670992a91c031387c555682b0327cc782d309dbf))
* 修复略过已点赞按钮的逻辑错误 ([8e3089c](https://www.github.com/14790897/auto-read-liunxdo/commit/8e3089c7339fde603c26b67b0fcbb5fdc0138b3d))
* 修改等待元素 ([57ad719](https://www.github.com/14790897/auto-read-liunxdo/commit/57ad7190b0221181d746e88f1d83838b46a58dca))
* 去掉link限制,延迟2秒执行 ([98b0c93](https://www.github.com/14790897/auto-read-liunxdo/commit/98b0c936a359040ea5f5f68ed26dc02b72784c25))
* 去掉new ([f4d8c27](https://www.github.com/14790897/auto-read-liunxdo/commit/f4d8c270c20536bb60877183e9757e8069778dcb))
* 去除unread,因为可能没有文章 ([531d5b0](https://www.github.com/14790897/auto-read-liunxdo/commit/531d5b0923f4c676ff31fc1e6d5cdf43bc907443))
* 去除监听request ([32d4637](https://www.github.com/14790897/auto-read-liunxdo/commit/32d4637e79f78169f8f11f5970490a9052168b4d))
* 增加元素等待时间 ([e574e50](https://www.github.com/14790897/auto-read-liunxdo/commit/e574e509bfb676b43a5cb35bf34225ed6f7b5747))
* 增加等待时间 ([e439eee](https://www.github.com/14790897/auto-read-liunxdo/commit/e439eee13a631856fe8d524d1e7ab79eb2d618cd))
* 按钮位置移到到左下角 ([afd3394](https://www.github.com/14790897/auto-read-liunxdo/commit/afd33947af7bc86422857ad1452cb692a83707ca))
* 改变帖子位置 ([37c52ee](https://www.github.com/14790897/auto-read-liunxdo/commit/37c52eeee9296197334e0d929fd2249b8ef9adee))
* 改变帖子位置 ([0bd3aed](https://www.github.com/14790897/auto-read-liunxdo/commit/0bd3aede6a937c623d687e2edf59089511efa7e0))
* 暗色模式下看不清的问题 ([1616eb3](https://www.github.com/14790897/auto-read-liunxdo/commit/1616eb33b9432ee1636ee124acfd1860d4940669))
* 更新名字 ([3024a4c](https://www.github.com/14790897/auto-read-liunxdo/commit/3024a4c0b9ef9a691ef96b24c0e0943956e4b90d))
* 点赞429 ([a0d809c](https://www.github.com/14790897/auto-read-liunxdo/commit/a0d809ce4faeeb98c49f611eb78d384dc195b1e4))
* 点赞跳过加上英文title判断 ([36e05fb](https://www.github.com/14790897/auto-read-liunxdo/commit/36e05fb33ad9d507faae042e05a6a7821937c432))
* 环境变量名字错误 ([a3f8f1e](https://www.github.com/14790897/auto-read-liunxdo/commit/a3f8f1e1c123ff813c5443acb5a0f512493dc58f))
* 调整了浏览的速度 ([e85425f](https://www.github.com/14790897/auto-read-liunxdo/commit/e85425f3138c94a603793a1111dfabeb1c22e3c5))
* 阅读位置 ([2a3d1a3](https://www.github.com/14790897/auto-read-liunxdo/commit/2a3d1a3a25537cf9bacea3e21b2df646650fb67f))
* 页面刷新之后保持之前的状态 ([d1817b8](https://www.github.com/14790897/auto-read-liunxdo/commit/d1817b81fb9085bad392675422c1e56f5e01ce90))
* 默认不启动自动点赞 ([be0ca10](https://www.github.com/14790897/auto-read-liunxdo/commit/be0ca10aecb6ec1bcfb61af19e97b2536bfa1ad8))
================================================
FILE: Dockerfile
================================================
FROM node:22-slim
WORKDIR /app
COPY package*.json ./
RUN apt update && apt install -y \
cron \
wget \
gnupg2 \
ca-certificates \
fonts-liberation \
libappindicator3-1 \
libasound2 \
libatk-bridge2.0-0 \
libatk1.0-0 \
libc6 \
libcairo2 \
libcups2 \
libdbus-1-3 \
libexpat1 \
libfontconfig1 \
libgbm1 \
libgcc1 \
libgdk-pixbuf2.0-0 \
libglib2.0-0 \
libgtk-3-0 \
libnspr4 \
libnss3 \
libpango-1.0-0 \
libpangocairo-1.0-0 \
libstdc++6 \
libx11-6 \
libx11-xcb1 \
libxcb1 \
libxcomposite1 \
libxcursor1 \
libxdamage1 \
libxext6 \
libxfixes3 \
libxi6 \
libxrandr2 \
libxrender1 \
libxss1 \
libxtst6 \
lsb-release \
xdg-utils \
xvfb \
findutils && \
rm -rf /var/lib/apt/lists/*
RUN wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | gpg --dearmor -o /usr/share/keyrings/google-linux-signing-key.gpg && \
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/google-linux-signing-key.gpg] http://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google-chrome.list && \
apt update && apt install -y google-chrome-stable && \
rm -rf /var/lib/apt/lists/*
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true \
PUPPETEER_EXECUTABLE_PATH=/usr/bin/google-chrome
ENV TZ=Asia/Shanghai
RUN npm install
COPY . .
RUN chmod -R 777 /app
# 创建清理脚本
RUN echo '#!/bin/bash\nfind /tmp -type f -atime +1 -delete' > /usr/local/bin/clean_tmp.sh && \
chmod +x /usr/local/bin/clean_tmp.sh
# 设置 cron 任务 (每天凌晨 3:00 执行)
RUN (crontab -l ; echo "0 3 * * * /usr/local/bin/clean_tmp.sh") | crontab -
# 启动 cron 并运行主程序 (使用 CMD 作为入口点)
CMD ["sh", "-c", "ulimit -c 0 && service cron start && node /app/bypasscf.js"]
================================================
FILE: Dockerfile-like-user
================================================
# 使用官方 Node.js 作为父镜像
FROM node:22-slim
# 设置工作目录
WORKDIR /app
# 复制 package.json 和 package-lock.json (如果存在)
COPY package*.json ./
# 安装 Puppeteer 依赖
RUN apt update && apt install -y \
cron\
wget \
ca-certificates \
fonts-liberation \
libappindicator3-1 \
libasound2 \
libatk-bridge2.0-0 \
libatk1.0-0 \
libc6 \
libcairo2 \
libcups2 \
libdbus-1-3 \
libexpat1 \
libfontconfig1 \
libgbm1 \
libgcc1 \
libgdk-pixbuf2.0-0 \
libglib2.0-0 \
libgtk-3-0 \
libnspr4 \
libnss3 \
libpango-1.0-0 \
libpangocairo-1.0-0 \
libstdc++6 \
libx11-6 \
libx11-xcb1 \
libxcb1 \
libxcomposite1 \
libxcursor1 \
libxdamage1 \
libxext6 \
libxfixes3 \
libxi6 \
libxrandr2 \
libxrender1 \
libxss1 \
libxtst6 \
lsb-release \
xdg-utils \
--no-install-recommends \
xvfb \
&& rm -rf /var/lib/apt/lists/*
#时区为中国
ENV TZ=Asia/Shanghai
# 安装 Node.js 依赖
RUN npm install
# 将根目录复制到容器中
COPY . .
# 设置容器启动时运行的命令
CMD ["node", "/app/bypasscf_likeUser.js"]
================================================
FILE: GITHUB_SECRETS_GUIDE.md
================================================
# ============================= 环境变量文档 =============================
#
# 在 GitHub 仓库的 Settings -> Secrets and variables -> Actions 中配置以下 secrets:
#
# 【必需变量】
# USERNAMES - 用户名列表,多个用逗号分隔 (例如: user1,user2,user3)
# PASSWORDS - 密码列表,与用户名对应,多个用逗号分隔
# WEBSITE - 目标网站 (例如: https://linux.do)
#
# 【功能开关】(可选)
# AUTO_LIKE - 是否自动点赞 (true/false,默认: true)
# HIDE_ACCOUNT_INFO - 是否在日志中隐藏账号名 (true/false,默认: true,即隐藏)
# LIKE_SPECIFIC_USER - 是否只点赞特定用户 (true/false,默认: false)
# ENABLE_RSS_FETCH - 是否开启RSS抓取 (true/false,默认: false)
# ENABLE_TOPIC_DATA_FETCH - 是否开启话题数据抓取 (true/false,默认: false)
#
# 【运行配置】(可选)
# RUN_TIME_LIMIT_MINUTES - 运行时间限制(分钟) (默认: 20)
# SPECIFIC_USER - 特定用户ID (默认: 14790897)
# HEALTH_PORT - 健康检查端口 (默认: 7860)
#
# 【Telegram通知】(可选)
# TELEGRAM_BOT_TOKEN - Telegram机器人令牌
# TELEGRAM_CHAT_ID - Telegram聊天ID
# TELEGRAM_GROUP_ID - Telegram群组ID
#
# 【代理配置】(可选 - 两种方式任选其一)
# 方式1: 使用代理URL (推荐)
# PROXY_URL - 代理URL (例如: http://user:pass@proxy.com:8080 或 socks5://user:pass@proxy.com:1080)
#
# 方式2: 分别配置各项
# PROXY_TYPE - 代理类型 (http/socks5)
# PROXY_HOST - 代理主机地址
# PROXY_PORT - 代理端口
# PROXY_USERNAME - 代理用户名
# PROXY_PASSWORD - 代理密码
#
# 【数据库配置】(可选)
# POSTGRES_URI - PostgreSQL连接字符串 (主数据库)
# COCKROACH_URI - CockroachDB连接字符串 (备用数据库)
# NEON_URI - Neon数据库连接字符串 (备用数据库)
# AIVEN_MYSQL_URI - Aiven MySQL连接字符串
# MONGO_URI - MongoDB连接字符串
#
# 【已废弃】
# HF_TOKEN - HuggingFace令牌 (已失效,无需设置)
#
# 注意: GitHub.secrets优先级最高,即使没有设置对应的变量,它也会读取,这时变量为空值,
# 导致报错,.env读取的变量无法覆盖这个值
# ========================================================================
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2024 liuweiqing
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: PROXY_GUIDE.md
================================================
# 🚀 自动阅读 Linux.do 代理配置指南
## 📖 概述
本项目支持多种代理协议,帮助您绕过网络限制,提高访问稳定性。
## 🛠️ 支持的代理类型
- **HTTP 代理**: 最常见的代理类型
- **HTTPS 代理**: 安全的HTTP代理
- **SOCKS4 代理**: 更底层的代理协议
- **SOCKS5 代理**: 功能最强大的代理协议(推荐)
## ⚙️ 配置方法
### 方法 1: 使用代理URL (推荐)
在 `.env` 或 `.env.local` 文件中设置:
```bash
# HTTP代理 (带认证)
PROXY_URL=http://username:password@proxy.example.com:8080
# SOCKS5代理 (带认证)
PROXY_URL=socks5://username:password@proxy.example.com:1080
# 免费代理 (无认证)
PROXY_URL=http://free-proxy.example.com:8080
```
### 方法 2: 分别配置参数
```bash
PROXY_TYPE=socks5
PROXY_HOST=proxy.example.com
PROXY_PORT=1080
PROXY_USERNAME=your_username
PROXY_PASSWORD=your_password
```
## 🔍 代理测试
运行测试命令检查代理配置:
```bash
node test_proxy.js
```
测试工具会:
- ✅ 检查代理配置是否正确
- 🌐 测试代理连接性能
- 📍 显示当前IP地址
- 💡 提供故障排查建议
## 🌟 推荐的代理服务
### 免费代理
- [FreeProxyList](https://www.freeproxy.world/)
- [ProxyScrape](https://proxyscrape.com/free-proxy-list)
- [HideMy.name](https://hidemy.name/en/proxy-list/)
### 付费代理(稳定性更好)
- [Bright Data](https://brightdata.com/)
- [Smartproxy](https://smartproxy.com/)
- [Oxylabs](https://oxylabs.io/)
- [ProxyMesh](https://proxymesh.com/)
## 🚨 故障排查
### 常见错误及解决方案
#### 1. 超时错误 (Timeout)
```
ProtocolError: Network.enable timed out
```
**解决方案:**
- 检查代理服务器响应速度
- 尝试其他代理服务器
- 增加超时时间设置
- 检查网络连接稳定性
#### 2. 连接被拒绝 (Connection Refused)
```
Error: connect ECONNREFUSED
```
**解决方案:**
- 验证代理地址和端口
- 检查代理服务器状态
- 确认防火墙设置
- 检查IP是否被封禁
#### 3. 认证失败 (Authentication Failed)
```
Error: Proxy authentication required
```
**解决方案:**
- 检查用户名和密码
- 确认账户状态
- 验证IP白名单设置
#### 4. DNS解析失败
```
Error: getaddrinfo ENOTFOUND
```
**解决方案:**
- 检查代理域名是否正确
- 尝试使用IP地址替代域名
- 检查DNS服务器设置
## 🎯 最佳实践
### 1. 代理选择建议
- **速度优先**: 选择地理位置近的代理
- **稳定性优先**: 选择付费代理服务
- **隐私优先**: 选择SOCKS5代理
### 2. 配置优化
- 使用 `.env.local` 文件避免提交敏感信息
- 定期测试代理连接性
- 准备备用代理服务器
### 3. 安全注意事项
- 不要在公共场所使用免费代理
- 定期更换代理密码
- 避免通过代理传输敏感信息
## 📝 配置示例
### 完整的 .env.local 示例
```bash
# 基本配置
USERNAMES=your_username1,your_username2
PASSWORDS=your_password1,your_password2
WEBSITE=https://linux.do
# 代理配置 (选择其中一种方式)
# 方式1: 使用代理URL
PROXY_URL=socks5://username:password@proxy.example.com:1080
# 方式2: 分别配置
# PROXY_TYPE=socks5
# PROXY_HOST=proxy.example.com
# PROXY_PORT=1080
# PROXY_USERNAME=username
# PROXY_PASSWORD=password
# Telegram配置 (可选)
TELEGRAM_BOT_TOKEN=your_bot_token
TELEGRAM_CHAT_ID=your_chat_id
# 其他配置
RUN_TIME_LIMIT_MINUTES=30
AUTO_LIKE=true
```
## 🔧 高级配置
### 自定义超时设置
如果遇到频繁超时,可以在代码中调整:
```javascript
// 在 browserOptions 中设置
browserOptions.protocolTimeout = 300000; // 5分钟
browserOptions.timeout = 120000; // 2分钟
```
### 代理轮换
实现多代理轮换使用:
```bash
# 设置多个代理URL,用逗号分隔
PROXY_URLS=socks5://user1:pass1@proxy1.com:1080,http://user2:pass2@proxy2.com:8080
```
## 💬 技术支持
如果遇到问题,请:
1. 运行 `node test_proxy.js` 获取详细错误信息
2. 检查代理服务商的文档
3. 在项目 Issues 中报告问题
4. 提供完整的错误日志(隐藏敏感信息)
## 🎉 快速开始
1. 复制 `.env.example` 为 `.env.local`
2. 填入你的代理配置
3. 运行 `node test_proxy.js` 测试连接
4. 运行 `node bypasscf.js` 开始使用
祝你使用愉快! 🚀
================================================
FILE: README.md
================================================
[英文文档](./README_en.md)
## 注意事项
1. 不显示脚本运行日志,只有登录结果
2. 阅读量统计有延迟,建议看点赞记录
## 彩蛋
https://t.me/linuxdoSQL
每天随机抓取帖子发布在此频道
## 使用方法一:油猴脚本(火狐不兼容,谷歌可以用)
### 油猴失去焦点后会停止运行,适合前台运行
油猴脚本代码在 index 开头的文件 中,**建议在使用前将浏览器页面缩小**,这样子可以一次滚动更多页面,读更多的回复
油猴脚本安装地址:
1. https://greasyfork.org/en/scripts/489464-auto-read 自动阅读随机点赞
2. https://greasyfork.org/en/scripts/506371-auto-like-specific-user 基于搜索到的帖子自动点赞特定用户
3. https://greasyfork.org/zh-CN/scripts/506567-auto-like-specific-user-base-on-activity 基于用户的活动自动点赞特定用户
## 使用方法二:本地运行(Windows 默认有头浏览器(适合后台运行),Linux 默认无头浏览器)
### 1.设置环境变量
.env 里面设置用户名 密码 COOKIES 以及其它 env 里面指明的信息
设置COOKIES后无需设置密码,目前密码登录方式由于验证已失效,建议使用cookie登录,获取cookie的方法是在浏览器中打开需要阅读的网站,按F12打开开发者工具,找到Application(应用程序)选项卡,在左侧的Storage(存储)部分选择Cookies,然后找到以_t=开头的cookie值,将其复制到.env文件中的COOKIES变量中,如果有多个账户需要登录,按照逗号分隔开,例如:COOKIES="_t=lnm123,_t=abc123,_t=def456"
<!-- #### 新功能:话题数据抓取
- `ENABLE_RSS_FETCH=true` - 启用RSS数据抓取功能(默认关闭)
- `ENABLE_TOPIC_DATA_FETCH=true` - 启用话题JSON数据抓取功能(默认关闭)
话题数据抓取功能会自动获取访问的话题页面的详细信息(如标题、回复数、浏览量、点赞数等)并保存到数据库中,支持PostgreSQL、MongoDB和MySQL数据库。 -->
### 2.运行
#### Windows
```sh
npm install
# 自动阅读随机点赞
node .\bypasscf.js
# 自动点赞特定用户
## Windows cmd
set LIKE_SPECIFIC_USER=true && node .\bypasscf.js
## Windows powershell
$env:LIKE_SPECIFIC_USER = "true"
node .\bypasscf.js
```
<!-- #### Linux 额外安装以下包,运行命令相同
```sh
sudo apt update
wget -qO- https://deb.nodesource.com/setup_20.x | sudo -E bash - #安装node的最新源
sudo apt install nodejs -y
sudo apt install -y wget unzip fontconfig locales gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget xvfb
sudo snap install chromium
```
```sh
npm install
# 自动阅读随机点赞
node .\bypasscf.js
# 自动点赞特定用户
LIKE_SPECIFIC_USER=true node ./bypasscf.js
``` -->
## 使用方法三:GitHub Action 每天 随机时间 阅读
#### 说明: 每天运行,每次三十分钟(可自行调整持续时间,代码.github\workflows\cron_bypassCF.yaml 和 .github\workflows\cron_bypassCF_likeUser.yaml,持续时间由环境变量的RUN_TIME_LIMIT_MINUTES和yaml配置的timeout-minutes的最小值决定,启动时间目前为随机无法修改)
**目前需要一个额外变量 `PAT_TOKEN`,用于随机时间执行阅读任务。教程:**
在 https://github.com/settings/tokens 生成一个 classic token,**需要包含 workflow 权限**,然后加入 actions 的 secrets 中,和 README 中添加其它 secrets 的过程一致。
### 1. fork 仓库
### 2.设置环境变量
在 GitHub action 的 secrets 设置用户名密码(变量名参考.env 中给出的),这里无法读取.env 变量

除此之外要修改时间还要改action的时间变量:
https://github.com/14790897/auto-read-liunxdo/blob/117af32dfdd0d3a6c2daf08dcd69e1aa3b7c4d00/.github/workflows/cron_bypassCF.yaml#L12
### 3.启动 workflow
教程:https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web?tab=readme-ov-file#enable-automatic-updates
以下两个任务用于阅读
readLike(自动阅读随机点赞)和 likeUser (点赞特定用户)
<!--
## 使用方法四:docker 运行
### 1.立刻执行
克隆仓库,新建.env.local, 按照.env的格式在里面设置环境变量,然后运行
```sh
# 自动阅读随机点赞
docker-compose up -d
# 自动点赞特定用户
docker-compose -f docker-compose-like-user.yml up -d
```
查看日志
```sh
docker-compose logs -f
```
### 2.定时运行
```sh
chmod +x cron.sh
crontab -e
```
手动添加以下内容(功能是每天六点执行)
```sh
0 6 * * * /root/auto-read-liunxdo/cron.sh # 注意这是示例目录,要改为所在仓库目录的cron.sh(使用pwd查看所在目录)
```
-->
## 如何增加基于 discourse 的其它网站的支持?
1. 修改 index_passage_list 中的// @match ,根据其它示例网站,填写新的 url,此外在脚本开头的 possibleBaseURLs 中也添加 url
2. 服务器运行时,还需要修改.env 下的 WEBSITE 变量为对应的网址(如果网址是不存在原先脚本的,需要修改 external.js 中对应的部分,重新构建镜像)
3. 小众软件论坛只能在 Windows 下运行,所以需要使用定制版 action: [.github\workflows\windows_cron_bypassCF.yaml](https://github.com/14790897/auto-read-liunxdo/blob/main/.github/workflows/windows_cron_bypassCF.yaml)
#### 随笔
开发中遇到的问题:
问:TimeoutError: Navigation timeout of 30000 ms exceeded 为什么 puppeteer 经常出现这个错误?
答:linux 使用{waitUntil: 'domcontentloaded'}后,情况大大好转,但还是有时出现,Windows 未曾出现此问题 [见文章分析](随笔.md) 目前发现存在不点击登录按钮导致超时,已解决(原因未知)
这个也可能是因为登陆太频繁导致的,太快的登陆太多的账号
更少见的情况其实是密码错误,还有账户未激活
3.06.2026:在添加环境变量的时候,最后记得需要在action的yaml里面的env部分添加
#### 待做
1. TimeoutError 时候可以捕获错误然后关掉当前浏览器重新再开一次(已经实现刷新页面重新登录但是效果不好)
2. 自动阅读脚本可以加一个阅读速度选项(快,慢,始终),因为有用户反应读的太快了(应该是他们屏幕太小)
3. https://github.com/14790897/auto-read-liunxdo/issues/67
## 感谢
https://linux.do/t/topic/106471
#### 使用 index_likeUser 点赞记录
9.2 handsome
9.3 lwyt
9.4 hindex
9.5 endercat
9.6 mrliushaopu
9.6 MonsterKing
9.7 zhiyang
9.8 xibalama
9.9 seeyourface LangYnn
9.10 YYWD
9.11 zhong_little
9.12 LangYnn
9.13 YYWD
9.14 wii
9.15 RunningSnail
9.16 ll0, mojihua,ywxh
9.17 GlycoProtein
9.18 Clarke.L Vyvx
9.19 azrael
9.20 Philippa shenchong
9.21lllyz hwang
9.22 include Unique
9.24 taobug
9.25 CoolMan
9.26 Madara jonty
9.27 jonty(不小心点了两次)
9.29 haoc louis miku8miku
9.30 horrZzz zxcv
10.1 bbb
10.2 zyzcom
10.4 jeff0319 Game0526 LeoMeng
10.5 kobe1 pangbaibai
10.6 xfgb lentikr
10.7 PlayMcBKuwu Tim88
10.10 elfmaid
10.11 yu_sheng orxvan l444736 time-wanderer
10.14 time-wanderer OrangeQiu
Timmy_0
SINOPEC
onePiece HelShiJiasi delph1s
[](https://dartnode.com "Powered by DartNode - Free VPS for Open Source")
<!--
代码:
https://github.com/14790897/auto-read-liunxdo
## 手动运行
### 1.设置环境变量
.env 里面设置用户名 密码
### 2.运行
```sh
npm install
node .\bypasscf.js
```
## GitHub Action 每天 阅读
(可自行修改启动时间和持续时间,代码.github\workflows\cron_bypassCF.yaml)
### 1. fork 仓库
### 2.设置环境变量
在 GitHub action 的 secrets 设置用户名密码(变量名参考.env 中给出的)(.env 里面设置用户名密码在这里无效)

### 3.启动 workflow
教程:https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web?tab=readme-ov-file#enable-automatic-updates
## 演示视频
<iframe src="//player.bilibili.com/player.html?isOutside=true&aid=112902946161711&bvid=BV1QLiceMExQ&cid=500001637992386&p=1" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true"></iframe> -->
================================================
FILE: README_en.md
================================================
[中文文档](./README_zh.md)
## Method 1: Tampermonkey Script
The Tampermonkey script can be accessed in the `index_passage_list`. You can find and install the script from Greasy Fork:

[Tampermonkey Script: Auto Read](https://greasyfork.org/en/scripts/489464-auto-read)
## Method 2: Headless Execution with Puppeteer
### 1. Setting Environment Variables
Set your username and password in the `.env` file.
### 2. Execution
#### For Windows
Run the following commands:
```sh
npm install
node .\pteer.js
```
#### For Linux (additional packages needed)
Install the required packages and run the same commands as for Windows:
```sh
sudo apt-get update
sudo apt install nodejs npm -y
sudo apt-get install -y wget unzip fontconfig locales gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget
```
## Method 3: GitHub Actions for Daily Reading at Midnight
Modify the start time and duration as needed. The code is located in `.github/workflows/cron_read.yaml`.
### 1. Fork the Repository
### 2. Set Environment Variables
Set the username and password in the secrets of GitHub actions (variable names can be referred from `.env`). Note that setting the environment variables in `.env` here does not work for GitHub actions.

### 3. Start the Workflow
Tutorial: [Enable Automatic Updates](https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web?tab=readme-ov-file#enable-automatic-updates)
## Method 4: Docker Execution
### 1. Immediate Execution
Clone the repository, set environment variables in `docker-compose.yml`, and run:
```sh
docker-compose up -d
```
To view logs:
```sh
docker-compose logs -f
```
### 2. Scheduled Execution
Set permissions and edit the crontab:
```sh
chmod +x cron.sh
crontab -e
```
Manually add the following entry (to execute daily at 6 AM, adjust the directory as needed):
```sh
0 6 * * * /root/auto-read-linuxdo/cron.sh # Note this is a sample directory, change to your repository's cron.sh directory (use pwd to find your directory)
```
#### Additional Information
The external script is used for puppeteer and is modified from `index_passage_list.js`. Main modifications include removing buttons and setting automatic reading and liking to start by default:
```sh
localStorage.setItem("read", "true"); // Initially disables auto-scroll
localStorage.setItem("autoLikeEnabled", "true"); // Auto-liking is enabled by default
```
================================================
FILE: bypasscf.js
================================================
import fs from "fs";
import path from "path";
import puppeteer from "puppeteer-extra";
import StealthPlugin from "puppeteer-extra-plugin-stealth";
import dotenv from "dotenv";
import { fileURLToPath } from "url";
import { dirname, join } from "path";
import TelegramBot from "node-telegram-bot-api";
import fetch from "node-fetch";
import { parseStringPromise } from "xml2js";
import { parseRss } from "./src/parse_rss.js";
import { processAndSaveTopicData } from "./src/topic_data.js";
import {
getProxyConfig,
getPuppeteerProxyArgs,
testProxyConnection,
getCurrentIP,
} from "./src/proxy_config.js";
dotenv.config();
// 捕获未处理的异常/Promise拒绝,避免因 Target closed 之类错误导致进程退出
process.on("unhandledRejection", (reason) => {
try {
const msg = (reason && reason.message) ? reason.message : String(reason);
console.warn("[unhandledRejection]", msg);
} catch {
console.warn("[unhandledRejection] (non-string reason)");
}
});
process.on("uncaughtException", (err) => {
try {
const msg = (err && err.message) ? err.message : String(err);
console.warn("[uncaughtException]", msg);
} catch {
console.warn("[uncaughtException] (non-string error)");
}
});
// 截图保存的文件夹
// const screenshotDir = "screenshots";
// if (!fs.existsSync(screenshotDir)) {
// fs.mkdirSync(screenshotDir);
// }
puppeteer.use(StealthPlugin());
// Load the default .env file
if (fs.existsSync(".env.local")) {
console.log("Using .env.local file to supply config environment variables");
const envConfig = dotenv.parse(fs.readFileSync(".env.local"));
for (const k in envConfig) {
process.env[k] = envConfig[k];
}
} else {
console.log(
"Using .env file to supply config environment variables, you can create a .env.local file to overwrite defaults, it doesn't upload to git"
);
}
// 读取以分钟为单位的运行时间限制
const runTimeLimitMinutes = process.env.RUN_TIME_LIMIT_MINUTES || 20;
// 将分钟转换为毫秒
const runTimeLimitMillis = runTimeLimitMinutes * 60 * 1000;
console.log(
`运行时间限制为:${runTimeLimitMinutes} 分钟 (${runTimeLimitMillis} 毫秒)`
);
// 设置一个定时器,在运行时间到达时终止进程
const shutdownTimer = setTimeout(() => {
console.log("时间到,Reached time limit, shutting down the process...");
process.exit(0); // 退出进程
}, runTimeLimitMillis);
const token = process.env.TELEGRAM_BOT_TOKEN;
const chatId = process.env.TELEGRAM_CHAT_ID;
const groupId = process.env.TELEGRAM_GROUP_ID;
const specificUser = process.env.SPECIFIC_USER || "14790897";
const maxConcurrentAccounts = parseInt(process.env.MAX_CONCURRENT_ACCOUNTS) || 3; // 每批最多同时运行的账号数
const usernames = process.env.USERNAMES.split(",");
const passwords = process.env.PASSWORDS ? process.env.PASSWORDS.split(",") : [];
// 读取每个账号对应的Cookie(逗号分隔,与USERNAMES一一对应),有Cookie则跳过表单登录
const cookiesEnv = process.env.COOKIES ? process.env.COOKIES.split(",") : [];
const loginUrl = process.env.WEBSITE || "https://linux.do"; //在GitHub action环境里它不能读取默认环境变量,只能在这里设置默认值
const delayBetweenInstances = 10000;
const totalAccounts = usernames.length; // 总的账号数
const delayBetweenBatches =
runTimeLimitMillis / Math.ceil(totalAccounts / maxConcurrentAccounts);
const isLikeSpecificUser = process.env.LIKE_SPECIFIC_USER === "true"; // 只有明确设置为"true"才开启
const isAutoLike = process.env.AUTO_LIKE !== "false"; // 默认开启,只有明确设置为"false"才关闭
const hideAccountInfo = process.env.HIDE_ACCOUNT_INFO !== "false"; // 默认隐藏账号信息,只有明确设置为"false"才显示
const enableRssFetch = process.env.ENABLE_RSS_FETCH === "true"; // 是否开启抓取RSS,只有明确设置为"true"才开启,默认为false
const enableTopicDataFetch = process.env.ENABLE_TOPIC_DATA_FETCH === "true"; // 是否开启抓取话题数据,只有明确设置为"true"才开启,默认为false
// 账号名脱敏函数,默认仅显示首字母加***
function maskUsername(username) {
if (!hideAccountInfo) return username;
if (!username || username.length === 0) return "***";
return username[0] + "***";
}
console.log(
`RSS抓取功能状态: ${enableRssFetch ? "开启" : "关闭"} (环境变量值: "${process.env.ENABLE_RSS_FETCH || ''}"),勿设置`
);
console.log(
`话题数据抓取功能状态: ${
enableTopicDataFetch ? "开启" : "关闭"
} (环境变量值: "${process.env.ENABLE_TOPIC_DATA_FETCH || ''}"),勿设置`
);
// 代理配置
const proxyConfig = getProxyConfig();
if (proxyConfig) {
console.log(
`代理配置: ${proxyConfig.type}://${proxyConfig.host}:${proxyConfig.port}`
);
// 测试代理连接
console.log("正在测试代理连接...");
const proxyWorking = await testProxyConnection(proxyConfig);
if (proxyWorking) {
console.log("✅ 代理连接测试成功");
} else {
console.log("❌ 代理连接测试失败,将使用直连");
}
} else {
console.log("未配置代理,使用直连");
const currentIP = await getCurrentIP();
if (currentIP) {
console.log(`当前IP地址: ${currentIP}`);
}
}
let bot;
if (token && (chatId || groupId)) {
bot = new TelegramBot(token);
}
// 简单的 Telegram 发送重试
async function tgSendWithRetry(id, message, maxRetries = 3) {
let lastErr;
for (let i = 0; i < maxRetries; i++) {
try {
await bot.sendMessage(id, message);
return true;
} catch (e) {
lastErr = e;
const delay = 1500 * (i + 1);
console.error(
`Telegram send failed (attempt ${i + 1}/${maxRetries}): ${
e && e.message ? e.message : e
}`
);
await new Promise((r) => setTimeout(r, delay));
}
}
throw lastErr;
}
async function sendToTelegram(message) {
if (!bot || !chatId) return;
try {
await tgSendWithRetry(chatId, message, 3);
console.log("Telegram message sent successfully");
} catch (error) {
console.error(
"Error sending Telegram message:",
error && error.code ? error.code : "",
error && error.message
? error.message.slice(0, 100)
: String(error).slice(0, 100)
);
}
}
async function sendToTelegramGroup(message) {
if (!bot || !groupId) {
console.error("sendToTelegramGroup: bot 或 groupId 不存在");
return;
}
// 过滤空内容,避免 Telegram 400 错误
if (!message || !String(message).trim()) {
console.warn("Telegram 群组推送内容为空,跳过发送");
return;
}
// 分割长消息,Telegram单条最大4096字符
const MAX_LEN = 4000;
if (typeof message === "string" && message.length > MAX_LEN) {
let start = 0;
let part = 1;
while (start < message.length) {
const chunk = message.slice(start, start + MAX_LEN);
try {
await tgSendWithRetry(groupId, chunk, 3);
console.log(`Telegram group message part ${part} sent successfully`);
} catch (error) {
console.error(
`Error sending Telegram group message part ${part}:`,
error
);
}
start += MAX_LEN;
part++;
}
} else {
try {
await tgSendWithRetry(groupId, message, 3);
console.log("Telegram group message sent successfully");
} catch (error) {
console.error("Error sending Telegram group message:", error);
}
}
}
//随机等待时间
function delayClick(time) {
return new Promise(function (resolve) {
setTimeout(resolve, time);
});
}
(async () => {
try {
// 有Cookie则跳过密码数量校验
if (
cookiesEnv.filter((c) => c && c.trim()).length === 0 &&
passwords.length !== usernames.length
) {
console.log(
`usernames: ${usernames.length}, passwords: ${passwords.length}`,
);
throw new Error("用户名和密码的数量不匹配!");
}
// 并发启动浏览器实例进行登录
const loginTasks = usernames.map((username, index) => {
const password = passwords[index] || "";
const cookie = cookiesEnv[index] ? cookiesEnv[index].trim() : null;
const delay = (index % maxConcurrentAccounts) * delayBetweenInstances; // 使得每一组内的浏览器可以分开启动
return () => {
// 确保这里返回的是函数,因为settimeout本身是异步的所以必须在外面给他一个promise await才能让它同步的等待这个时间才能执行
// 可以改为 return async () => {
// await new Promise((resolve) => setTimeout(resolve, delay));
// return await launchBrowserForUser(username, password, cookie);
// }; 更好理解
return new Promise((resolve, reject) => {
setTimeout(() => {
launchBrowserForUser(username, password, cookie)
.then(resolve)
.catch(reject);
}, delay);
});
};
});
// 依次执行每个批次的任务
for (let i = 0; i < totalAccounts; i += maxConcurrentAccounts) {
console.log(`当前批次:${i + 1} - ${i + maxConcurrentAccounts}`);
// 执行每批次最多 4 个账号
const batch = loginTasks
.slice(i, i + maxConcurrentAccounts)
.map(async (task) => {
const { browser } = await task(); // 运行任务并获取浏览器实例
return browser;
}); // 等待当前批次的任务完成
const browsers = await Promise.all(batch); // Task里面的任务本身是没有进行await的, 所以会继续执行下面的代码
// 如果还有下一个批次,等待指定的时间,同时,如果总共只有一个账号,也需要继续运行
if (i + maxConcurrentAccounts < totalAccounts || i === 0) {
console.log(`等待 ${delayBetweenBatches / 1000} 秒`);
await new Promise((resolve) =>
setTimeout(resolve, delayBetweenBatches),
);
} else {
console.log("没有下一个批次,即将结束");
}
console.log(
`批次 ${
Math.floor(i / maxConcurrentAccounts) + 1
} 完成,关闭浏览器...,浏览器对象:${browsers}`,
);
// 关闭所有浏览器实例
for (const browser of browsers) {
await browser.close();
}
}
console.log("所有账号登录操作已完成");
// 等待所有登录操作完成
// await Promise.all(loginTasks);
} catch (error) {
// 错误处理逻辑
console.error("发生错误:", error);
if (token && chatId) {
sendToTelegram(`${error.message}`);
}
}
})();
// 将浏览器Cookie字符串(如 "name=value; name2=value2")解析为 puppeteer setCookie 所需的对象数组
function parseCookieString(cookieStr, domain) {
return cookieStr
.split(";")
.map((part) => part.trim())
.filter((part) => part.includes("="))
.map((part) => {
const eqIndex = part.indexOf("=");
const name = part.substring(0, eqIndex).trim();
const value = part.substring(eqIndex + 1).trim();
return { name, value, domain, path: "/" };
});
}
async function launchBrowserForUser(username, password, cookie = null) {
let browser = null; // 在 try 之外声明 browser 变量
try {
console.log("当前用户:", maskUsername(username));
const browserOptions = {
headless: "auto",
args: ["--no-sandbox", "--disable-setuid-sandbox"], // Linux 需要的安全设置
};
// 添加代理配置到浏览器选项
const proxyConfig = getProxyConfig();
if (proxyConfig) {
const proxyArgs = getPuppeteerProxyArgs(proxyConfig);
browserOptions.args.push(...proxyArgs);
console.log(
`为用户 ${maskUsername(username)} 启用代理: ${proxyConfig.type}://${proxyConfig.host}:${proxyConfig.port}`
);
// 如果有用户名密码,puppeteer-real-browser会自动处理
if (proxyConfig.username && proxyConfig.password) {
browserOptions.proxy = {
host: proxyConfig.host,
port: proxyConfig.port,
username: proxyConfig.username,
password: proxyConfig.password,
};
}
}
var { connect } = await import("puppeteer-real-browser");
const { page, browser: newBrowser } = await connect(browserOptions);
browser = newBrowser; // 将 browser 初始化
// 启动截图功能
// takeScreenshots(page);
//登录操作
await navigatePage(loginUrl, page, browser);
await delayClick(8000);
// 设置额外的 headers
await page.setExtraHTTPHeaders({
"accept-language": "en-US,en;q=0.9",
});
// 验证 `navigator.webdriver` 属性是否为 undefined
// const isWebDriverUndefined = await page.evaluate(() => {
// return `${navigator.webdriver}`;
// });
// console.log("navigator.webdriver is :", isWebDriverUndefined); // 输出应为 false
page.on("pageerror", (error) => {
console.error(`Page error: ${error.message}`);
});
page.on("error", async (error) => {
// console.error(`Error: ${error.message}`);
// 检查是否是 localStorage 的访问权限错误
if (
error.message.includes(
"Failed to read the 'localStorage' property from 'Window'"
)
) {
console.log("Trying to refresh the page to resolve the issue...");
await page.reload(); // 刷新页面
// 重新尝试你的操作...
}
});
page.on("console", async (msg) => {
// console.log("PAGE LOG:", msg.text());
// 使用一个标志变量来检测是否已经刷新过页面
if (
!page._isReloaded &&
msg.text().includes("the server responded with a status of 429")
) {
// 设置标志变量为 true,表示即将刷新页面
page._isReloaded = true;
//由于油候脚本它这个时候可能会导航到新的网页,会导致直接执行代码报错,所以使用这个来在每个新网页加载之前来执行
try {
await page.evaluateOnNewDocument(() => {
localStorage.setItem("autoLikeEnabled", "false");
});
} catch (e) {
// Fallback to immediate evaluate when target already navigated/closed
try {
if (!page.isClosed || !page.isClosed()) {
await page.evaluate(() => {
localStorage.setItem("autoLikeEnabled", "false");
});
}
} catch (e2) {
console.warn(
`Skip disabling autoLike due to closed target: ${
(e2 && e2.message) ? e2.message : e2
}`
);
}
}
// 等待一段时间,比如 3 秒
await new Promise((resolve) => setTimeout(resolve, 3000));
console.log("Retrying now...");
// 尝试刷新页面
// await page.reload();
}
});
// 登录操作:优先使用Cookie,否则使用表单登录
if (cookie) {
console.log("检测到Cookie,跳过表单登录,直接设置Cookie");
const domain = new URL(loginUrl).hostname;
const cookieObjects = parseCookieString(cookie, domain);
await page.setCookie(...cookieObjects);
console.log(`已设置 ${cookieObjects.length} 个Cookie,正在刷新页面...`);
await page.reload({ waitUntil: "domcontentloaded" });
await delayClick(2000);
} else {
console.log("登录操作");
await login(page, username, password);
}
// 查找具有类名 "avatar" 的 img 元素验证登录是否成功
// 若存在 span.auth-buttons 则说明处于未登录状态
const avatarImg = await page.$("img.avatar");
const authButtons = await page.$("span.auth-buttons");
if (authButtons) {
console.log("找到 auth-buttons,用户未登录,登录失败");
throw new Error("登录失败:页面显示未登录状态(auth-buttons)");
} else if (avatarImg) {
console.log("找到avatarImg,登录成功");
} else {
console.log("未找到avatarImg,登录失败");
throw new Error("登录失败");
}
//真正执行阅读脚本
let externalScriptPath;
if (isLikeSpecificUser === "true") {
const randomChoice = Math.random() < 0.5; // 生成一个随机数,50% 概率为 true
if (randomChoice) {
externalScriptPath = path.join(
dirname(fileURLToPath(import.meta.url)),
"index_likeUser_activity.js"
);
console.log("使用index_likeUser_activity");
} else {
externalScriptPath = path.join(
dirname(fileURLToPath(import.meta.url)),
"index_likeUser.js"
);
console.log("使用index_likeUser");
}
} else {
externalScriptPath = path.join(
dirname(fileURLToPath(import.meta.url)),
"index.js"
);
}
const externalScript = fs.readFileSync(externalScriptPath, "utf8");
// 在每个新的文档加载时执行外部脚本
await page.evaluateOnNewDocument(
(...args) => {
const [specificUser, scriptToEval, isAutoLike] = args;
localStorage.setItem("read", true);
localStorage.setItem("specificUser", specificUser);
localStorage.setItem("isFirstRun", "false");
localStorage.setItem("autoLikeEnabled", isAutoLike);
console.log("当前点赞用户:", specificUser);
eval(scriptToEval);
},
specificUser,
externalScript,
isAutoLike
); //变量必须从外部显示的传入, 因为在浏览器上下文它是读取不了的
// 添加一个监听器来监听每次页面加载完成的事件
page.on("load", async () => {
// await page.evaluate(externalScript); //因为这个是在页面加载好之后执行的,而脚本是在页面加载好时刻来判断是否要执行,由于已经加载好了,脚本就不会起作用
});
// 如果是Linuxdo,就导航到我的帖子,但我感觉这里写没什么用,因为外部脚本已经定义好了,不对,这里不会点击按钮,所以不会跳转,需要手动跳转
if (loginUrl == "https://linux.do") {
await page.goto("https://linux.do/t/topic/13716/790", {
waitUntil: "domcontentloaded",
timeout: parseInt(process.env.NAV_TIMEOUT_MS || process.env.NAV_TIMEOUT || "120000", 10),
});
} else if (loginUrl == "https://meta.appinn.net") {
await page.goto("https://meta.appinn.net/t/topic/52006", {
waitUntil: "domcontentloaded",
timeout: parseInt(process.env.NAV_TIMEOUT_MS || process.env.NAV_TIMEOUT || "120000", 10),
});
} else {
await page.goto(`${loginUrl}/t/topic/1`, {
waitUntil: "domcontentloaded",
timeout: parseInt(process.env.NAV_TIMEOUT_MS || process.env.NAV_TIMEOUT || "120000", 10),
});
}
// Ensure automation injected after navigation (fallback in case init-script failed)
try {
await page.evaluate(
(specificUser, scriptToEval, isAutoLike) => {
if (!window.__autoInjected) {
localStorage.setItem("read", true);
localStorage.setItem("specificUser", specificUser);
localStorage.setItem("isFirstRun", "false");
localStorage.setItem("autoLikeEnabled", isAutoLike);
try { eval(scriptToEval); } catch (e) { console.error("eval external script failed", e); }
window.__autoInjected = true;
}
},
specificUser,
externalScript,
isAutoLike
);
} catch (e) {
console.warn(`Post-navigation inject failed: ${e && e.message ? e.message : e}`);
}
if (token && chatId) {
sendToTelegram(`${username} 登录成功`);
} // 监听页面跳转到新话题,自动推送RSS example:https://linux.do/t/topic/525305.rss
// 记录已推送过的 topicId,防止重复推送
if (enableRssFetch || enableTopicDataFetch) {
const pushedTopicIds = new Set();
const processedTopicIds = new Set(); // 用于话题数据处理的记录
page.on("framenavigated", async (frame) => {
if (frame.parentFrame() !== null) return;
const url = frame.url();
const match = url.match(/https:\/\/linux\.do\/t\/topic\/(\d+)/);
if (match) {
const topicId = match[1];
// RSS抓取处理
if (enableRssFetch && !pushedTopicIds.has(topicId)) {
pushedTopicIds.add(topicId);
const rssUrl = `https://linux.do/t/topic/${topicId}.rss`;
console.log("检测到话题跳转,抓取RSS:", rssUrl);
try {
// 停顿1.5秒再抓取
await new Promise((r) => setTimeout(r, 1500));
const rssPage = await browser.newPage();
await rssPage.goto(rssUrl, {
waitUntil: "domcontentloaded",
timeout: 20000,
});
// 停顿0.5秒再获取内容,确保页面渲染完成
await new Promise((r) => setTimeout(r, 1000));
const xml = await rssPage.evaluate(() => document.body.innerText);
await rssPage.close();
const parsedData = await parseRss(xml);
sendToTelegramGroup(parsedData);
} catch (e) {
console.error("抓取或发送RSS失败:", e, "可能是非公开话题");
}
}
// 话题数据抓取处理
if (enableTopicDataFetch && !processedTopicIds.has(topicId)) {
processedTopicIds.add(topicId);
console.log("检测到话题跳转,抓取话题数据:", url);
try {
// 停顿1秒再处理话题数据
await new Promise((r) => setTimeout(r, 1000));
await processAndSaveTopicData(page, url);
} catch (e) {
console.error("抓取或保存话题数据失败:", e);
}
}
}
// 停顿0.5秒后允许下次抓取
await new Promise((r) => setTimeout(r, 500));
});
}
return { browser };
} catch (err) {
// throw new Error(err);
console.log("Error in launchBrowserForUser:", err);
if (token && chatId) {
sendToTelegram(`${err.message}`);
}
return { browser }; // 错误时仍然返回 browser
}
}
async function login(page, username, password, retryCount = 3) {
// 使用XPath查询找到包含"登录"或"login"文本的按钮
let loginButtonFound = await page.evaluate(() => {
let loginButton = Array.from(document.querySelectorAll("button")).find(
(button) =>
button.textContent.includes("登录") ||
button.textContent.includes("login")
); // 注意loginButton 变量在外部作用域中是无法被 page.evaluate 内部的代码直接修改的。page.evaluate 的代码是在浏览器环境中执行的,这意味着它们无法直接影响 Node.js 环境中的变量
// 如果没有找到,尝试根据类名查找
if (!loginButton) {
loginButton = document.querySelector(".login-button");
}
if (loginButton) {
loginButton.click();
console.log("Login button clicked.");
return true; // 返回true表示找到了按钮并点击了
} else {
console.log("Login button not found.");
return false; // 返回false表示没有找到按钮
}
});
if (!loginButtonFound) {
if (loginUrl == "https://meta.appinn.net") {
await page.goto("https://meta.appinn.net/t/topic/52006", {
waitUntil: "domcontentloaded",
timeout: parseInt(process.env.NAV_TIMEOUT_MS || process.env.NAV_TIMEOUT || "120000", 10),
});
await page.click(".discourse-reactions-reaction-button");
} else {
await page.goto(`${loginUrl}/t/topic/1`, {
waitUntil: "domcontentloaded",
timeout: parseInt(process.env.NAV_TIMEOUT_MS || process.env.NAV_TIMEOUT || "120000", 10),
});
try {
await page.click(".discourse-reactions-reaction-button");
} catch (error) {
console.log("没有找到点赞按钮,可能是页面没有加载完成或按钮不存在");
}
}
}
// 等待用户名输入框加载
await page.waitForSelector("#login-account-name");
// 模拟人类在找到输入框后的短暂停顿
await delayClick(1000); // 延迟500毫秒
// 清空输入框并输入用户名
await page.click("#login-account-name", { clickCount: 3 });
await page.type("#login-account-name", username, {
delay: 100,
}); // 输入时在每个按键之间添加额外的延迟
await delayClick(1000);
// 等待密码输入框加载
// await page.waitForSelector("#login-account-password");
// 模拟人类在输入用户名后的短暂停顿
// delayClick; // 清空输入框并输入密码
await page.click("#login-account-password", { clickCount: 3 });
await page.type("#login-account-password", password, {
delay: 100,
});
// 模拟人类在输入完成后思考的短暂停顿
await delayClick(1000);
// 假设登录按钮的ID是'login-button',点击登录按钮
await page.waitForSelector("#login-button");
await delayClick(1000); // 模拟在点击登录按钮前的短暂停顿
await page.click("#login-button");
try {
await Promise.all([
page.waitForNavigation({ waitUntil: "domcontentloaded" }), // 等待 页面跳转 DOMContentLoaded 事件
// 去掉上面一行会报错:Error: Execution context was destroyed, most likely because of a navigation. 可能是因为之后没等页面加载完成就执行了脚本
page.click("#login-button", { force: true }), // 点击登录按钮触发跳转
]); //注意如果登录失败,这里会一直等待跳转,导致脚本执行失败 这点四个月之前你就发现了结果今天又遇到(有个用户遇到了https://linux.do/t/topic/169209/82),但是你没有在这个报错你提示我8.5
} catch (error) {
const alertError = await page.$(".alert.alert-error");
if (alertError) {
const alertText = await page.evaluate((el) => el.innerText, alertError); // 使用 evaluate 获取 innerText
if (
alertText.includes("incorrect") ||
alertText.includes("Incorrect ") ||
alertText.includes("不正确")
) {
throw new Error(
`非超时错误,请检查用户名密码是否正确,失败用户 ${maskUsername(username)}, 错误信息:${alertText}`
);
} else {
throw new Error(
`非超时错误,也不是密码错误,可能是IP导致,需使用中国美国香港台湾IP,失败用户 ${maskUsername(username)},错误信息:${alertText}`
);
}
} else {
if (retryCount > 0) {
console.log("Retrying login...");
await page.reload({ waitUntil: "domcontentloaded", timeout: parseInt(process.env.NAV_TIMEOUT_MS || process.env.NAV_TIMEOUT || "120000", 10) });
await delayClick(2000); // 增加重试前的延迟
return await login(page, username, password, retryCount - 1);
} else {
throw new Error(
`Navigation timed out in login.超时了,可能是IP质量问题,失败用户 ${maskUsername(username)},
${error}`
); //{password}
}
}
}
await delayClick(1000);
}
async function navigatePage(url, page, browser) {
try {
page.setDefaultNavigationTimeout(
parseInt(process.env.NAV_TIMEOUT_MS || process.env.NAV_TIMEOUT || "120000", 10)
);
} catch {}
await page.goto(url, { waitUntil: "domcontentloaded" }); //如果使用默认的load,linux下页面会一直加载导致无法继续执行
const startTime = Date.now(); // 记录开始时间
let pageTitle = await page.title(); // 获取当前页面标题
while (pageTitle.includes("Just a moment") || pageTitle.includes("请稍候")) {
console.log("The page is under Cloudflare protection. Waiting...");
await delayClick(2000); // 每次检查间隔2秒
// 重新获取页面标题
pageTitle = await page.title();
// 检查是否超过15秒
if (Date.now() - startTime > 35000) {
console.log("Timeout exceeded, aborting actions.");
sendToTelegram(`超时了,无法通过Cloudflare验证`);
await browser.close();
// todo: 这里其实不能关的m因为我们是在最后统一关的你不能在这里关m如果你在这关,后面就会触发attempted to sue detached frame
return; // 超时则退出函数
}
}
console.log("页面标题:", pageTitle);
}
// 每秒截图功能
async function takeScreenshots(page) {
let screenshotIndex = 0;
setInterval(async () => {
screenshotIndex++;
const screenshotPath = path.join(
screenshotDir,
`screenshot-${screenshotIndex}.png`
);
try {
await page.screenshot({ path: screenshotPath, fullPage: true });
console.log(`Screenshot saved: ${screenshotPath}`);
} catch (error) {
console.error("Error taking screenshot:", error);
}
}, 1000);
// 注册退出时删除文件夹的回调函数
process.on("exit", () => {
try {
fs.rmdirSync(screenshotDir, { recursive: true });
console.log(`Deleted folder: ${screenshotDir}`);
} catch (error) {
console.error(`Error deleting folder ${screenshotDir}:`, error);
}
});
}
import express from "express";
const healthApp = express();
const HEALTH_PORT = process.env.HEALTH_PORT || 7860;
// 健康探针路由
healthApp.get("/health", (req, res) => {
const memoryUsage = process.memoryUsage();
// 将字节转换为MB
const memoryUsageMB = {
rss: `${(memoryUsage.rss / (1024 * 1024)).toFixed(2)} MB`, // 转换为MB并保留两位小数
heapTotal: `${(memoryUsage.heapTotal / (1024 * 1024)).toFixed(2)} MB`,
heapUsed: `${(memoryUsage.heapUsed / (1024 * 1024)).toFixed(2)} MB`,
external: `${(memoryUsage.external / (1024 * 1024)).toFixed(2)} MB`,
arrayBuffers: `${(memoryUsage.arrayBuffers / (1024 * 1024)).toFixed(2)} MB`,
};
const healthData = {
status: "OK",
timestamp: new Date().toISOString(),
memoryUsage: memoryUsageMB,
uptime: process.uptime().toFixed(2), // 保留两位小数
};
res.status(200).json(healthData);
});
healthApp.get("/", (req, res) => {
res.send(`
<html>
<head>
<title>Auto Read</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f4f4f4;
color: #333;
margin: 0;
padding: 20px;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
.container {
background-color: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 0 15px rgba(0, 0, 0, 0.1);
max-width: 600px;
text-align: center;
}
h1 {
color: #007bff;
}
p {
font-size: 18px;
margin: 15px 0;
}
a {
color: #007bff;
text-decoration: none;
font-weight: bold;
}
a:hover {
text-decoration: underline;
}
footer {
margin-top: 20px;
font-size: 14px;
color: #555;
}
</style>
</head>
<body>
<div class="container">
<h1>Welcome to the Auto Read App</h1>
<p>You can check the server's health at <a href="/health">/health</a>.</p>
<p>GitHub: <a href="https://github.com/14790897/auto-read-liunxdo" target="_blank">https://github.com/14790897/auto-read-liunxdo</a></p>
<footer>© 2024 Auto Read App</footer>
</div>
</body>
</html>
`);
});
healthApp.listen(HEALTH_PORT, () => {
console.log(
`Health check endpoint is running at http://localhost:${HEALTH_PORT}/health`
);
});
================================================
FILE: bypasscf_likeUser_not_use.js
================================================
import fs from "fs";
import path from "path";
import puppeteer from "puppeteer-extra";
import StealthPlugin from "puppeteer-extra-plugin-stealth";
import dotenv from "dotenv";
import { fileURLToPath } from "url";
import { dirname, join } from "path";
import TelegramBot from "node-telegram-bot-api";
dotenv.config();
// 截图保存的文件夹
// const screenshotDir = "screenshots";
// if (!fs.existsSync(screenshotDir)) {
// fs.mkdirSync(screenshotDir);
// }
puppeteer.use(StealthPlugin());
// Load the default .env file
if (fs.existsSync(".env.local")) {
console.log("Using .env.local file to supply config environment variables");
const envConfig = dotenv.parse(fs.readFileSync(".env.local"));
for (const k in envConfig) {
process.env[k] = envConfig[k];
}
} else {
console.log(
"Using .env file to supply config environment variables, you can create a .env.local file to overwrite defaults, it doesn't upload to git"
);
}
// 读取以分钟为单位的运行时间限制
const runTimeLimitMinutes = process.env.RUN_TIME_LIMIT_MINUTES || 20;
// 将分钟转换为毫秒
const runTimeLimitMillis = runTimeLimitMinutes * 60 * 1000;
console.log(
`运行时间限制为:${runTimeLimitMinutes} 分钟 (${runTimeLimitMillis} 毫秒)`
);
// 设置一个定时器,在运行时间到达时终止进程
const shutdownTimer = setTimeout(() => {
console.log("时间到,Reached time limit, shutting down the process...");
process.exit(0); // 退出进程
}, runTimeLimitMillis);
const token = process.env.TELEGRAM_BOT_TOKEN;
const chatId = process.env.TELEGRAM_CHAT_ID;
const specificUser = process.env.SPECIFIC_USER || "14790897";
const maxConcurrentAccounts = 4; // 每批最多同时运行的账号数
const usernames = process.env.USERNAMES.split(",");
const passwords = process.env.PASSWORDS.split(",");
const loginUrl = process.env.WEBSITE || "https://linux.do"; //在GitHub action环境里它不能读取默认环境变量,只能在这里设置默认值
const delayBetweenInstances = 10000;
const totalAccounts = usernames.length; // 总的账号数
const delayBetweenBatches =
runTimeLimitMillis / Math.ceil(totalAccounts / maxConcurrentAccounts);
let bot;
if (token && chatId) {
bot = new TelegramBot(token);
}
function sendToTelegram(message) {
if (!bot) return;
bot
.sendMessage(chatId, message)
.then(() => {
console.log("Telegram message sent successfully");
})
.catch((error) => {
console.error("Error sending Telegram message:", error);
});
}
//随机等待时间
function delayClick(time) {
return new Promise(function (resolve) {
setTimeout(resolve, time);
});
}
(async () => {
try {
if (usernames.length !== passwords.length) {
console.log(usernames.length, usernames, passwords.length, passwords);
throw new Error("用户名和密码的数量不匹配!");
}
// 并发启动浏览器实例进行登录
const loginTasks = usernames.map((username, index) => {
const password = passwords[index];
const delay = (index % maxConcurrentAccounts) * delayBetweenInstances; // 使得每一组内的浏览器可以分开启动
return () => {
// 确保这里返回的是函数
return new Promise((resolve, reject) => {
setTimeout(() => {
launchBrowserForUser(username, password)
.then(resolve)
.catch(reject);
}, delay);
});
};
});
// 依次执行每个批次的任务
for (let i = 0; i < totalAccounts; i += maxConcurrentAccounts) {
console.log(`当前批次:${i + 1} - ${i + maxConcurrentAccounts}`);
// 执行每批次最多 4 个账号
const batch = loginTasks
.slice(i, i + maxConcurrentAccounts)
.map(async (task) => {
const { browser } = await task(); // 运行任务并获取浏览器实例
return browser;
}); // 等待当前批次的任务完成
const browsers = await Promise.all(batch); // Task里面的任务本身是没有进行await的, 所以会继续执行下面的代码
// 如果还有下一个批次,等待指定的时间,同时,如果总共只有一个账号,也需要继续运行
if (i + maxConcurrentAccounts < totalAccounts || i === 0) {
console.log(`等待 ${delayBetweenBatches / 1000} 秒`);
await new Promise((resolve) =>
setTimeout(resolve, delayBetweenBatches)
);
} else {
console.log("没有下一个批次,即将结束");
}
console.log(
`批次 ${
Math.floor(i / maxConcurrentAccounts) + 1
} 完成,关闭浏览器...,浏览器对象:${browsers}`
);
// 关闭所有浏览器实例
for (const browser of browsers) {
await browser.close();
}
}
console.log("所有账号登录操作已完成");
// 等待所有登录操作完成
// await Promise.all(loginTasks);
} catch (error) {
// 错误处理逻辑
console.error("发生错误:", error);
if (token && chatId) {
sendToTelegram(`${error.message}`);
}
}
})();
async function launchBrowserForUser(username, password) {
let browser = null; // 在 try 之外声明 browser 变量
try {
console.log("当前用户:", username);
const browserOptions = {
headless: "auto",
args: ["--no-sandbox", "--disable-setuid-sandbox"], // Linux 需要的安全设置
};
// 如果环境变量不是 'dev',则添加代理配置
// if (process.env.ENVIRONMENT !== "dev") {
// browserOptions["proxy"] = {
// host: "38.154.227.167",
// port: "5868",
// username: "pqxujuyl",
// password: "y1nmb5kjbz9t",
// };
// }
var { connect } = await import("puppeteer-real-browser");
const { page, browser: newBrowser } = await connect(browserOptions);
browser = newBrowser; // 将 browser 初始化
// 启动截图功能
// takeScreenshots(page);
//登录操作
await navigatePage(loginUrl, page, browser);
await delayClick(8000);
// 设置额外的 headers
await page.setExtraHTTPHeaders({
"accept-language": "en-US,en;q=0.9",
});
// 验证 `navigator.webdriver` 属性是否为 undefined
// const isWebDriverUndefined = await page.evaluate(() => {
// return `${navigator.webdriver}`;
// });
// console.log("navigator.webdriver is :", isWebDriverUndefined); // 输出应为 false
page.on("pageerror", (error) => {
console.error(`Page error: ${error.message}`);
});
page.on("error", async (error) => {
// console.error(`Error: ${error.message}`);
// 检查是否是 localStorage 的访问权限错误
if (
error.message.includes(
"Failed to read the 'localStorage' property from 'Window'"
)
) {
console.log("Trying to refresh the page to resolve the issue...");
await page.reload(); // 刷新页面
// 重新尝试你的操作...
}
});
page.on("console", async (msg) => {
// console.log("PAGE LOG:", msg.text());
// 使用一个标志变量来检测是否已经刷新过页面
if (
!page._isReloaded &&
msg.text().includes("the server responded with a status of 429")
) {
// 设置标志变量为 true,表示即将刷新页面
page._isReloaded = true;
//由于油候脚本它这个时候可能会导航到新的网页,会导致直接执行代码报错,所以使用这个来在每个新网页加载之前来执行
await page.evaluateOnNewDocument(() => {
localStorage.setItem("autoLikeEnabled", "false");
});
// 等待一段时间,比如 3 秒
await new Promise((resolve) => setTimeout(resolve, 3000));
console.log("Retrying now...");
// 尝试刷新页面
// await page.reload();
}
});
// //登录操作
console.log("登录操作");
await login(page, username, password);
// 查找具有类名 "avatar" 的 img 元素验证登录是否成功
const avatarImg = await page.$("img.avatar");
if (avatarImg) {
console.log("找到avatarImg,登录成功");
} else {
console.log("未找到avatarImg,登录失败");
throw new Error("登录失败");
}
//真正执行阅读脚本
const externalScriptPath = path.join(
dirname(fileURLToPath(import.meta.url)),
"index_likeUser.js"
);
const externalScript = fs.readFileSync(externalScriptPath, "utf8");
// 在每个新的文档加载时执行外部脚本
await page.evaluateOnNewDocument(
(...args) => {
const [specificUser, scriptToEval] = args;
localStorage.setItem("read", true);
localStorage.setItem("specificUser", specificUser);
localStorage.setItem("isFirstRun", "false");
console.log("当前点赞用户:", specificUser);
eval(scriptToEval);
},
specificUser,
externalScript
); //变量必须从外部显示的传入, 因为在浏览器上下文它是读取不了的
// 添加一个监听器来监听每次页面加载完成的事件
page.on("load", async () => {
// await page.evaluate(externalScript); //因为这个是在页面加载好之后执行的,而脚本是在页面加载好时刻来判断是否要执行,由于已经加载好了,脚本就不会起作用
});
// 如果是Linuxdo,就导航到我的帖子,但我感觉这里写没什么用,因为外部脚本已经定义好了,不对,这里不会点击按钮,所以不会跳转,需要手动跳转
if (loginUrl == "https://linux.do") {
await page.goto("https://linux.do/t/topic/13716/630", {
waitUntil: "domcontentloaded",
});
} else if (loginUrl == "https://meta.appinn.net") {
await page.goto("https://meta.appinn.net/t/topic/52006", {
waitUntil: "domcontentloaded",
});
} else {
await page.goto(`${loginUrl}/t/topic/1`, {
waitUntil: "domcontentloaded",
});
}
if (token && chatId) {
sendToTelegram(`${username} 登录成功`);
}
return { browser };
} catch (err) {
// throw new Error(err);
console.log("Error in launchBrowserForUser:", err);
if (token && chatId) {
sendToTelegram(`${err.message}`);
}
return { browser }; // 错误时仍然返回 browser
}
}
async function login(page, username, password, retryCount = 3) {
// 使用XPath查询找到包含"登录"或"login"文本的按钮
let loginButtonFound = await page.evaluate(() => {
let loginButton = Array.from(document.querySelectorAll("button")).find(
(button) =>
button.textContent.includes("登录") ||
button.textContent.includes("login")
); // 注意loginButton 变量在外部作用域中是无法被 page.evaluate 内部的代码直接修改的。page.evaluate 的代码是在浏览器环境中执行的,这意味着它们无法直接影响 Node.js 环境中的变量
// 如果没有找到,尝试根据类名查找
if (!loginButton) {
loginButton = document.querySelector(".login-button");
}
if (loginButton) {
loginButton.click();
console.log("Login button clicked.");
return true; // 返回true表示找到了按钮并点击了
} else {
console.log("Login button not found.");
return false; // 返回false表示没有找到按钮
}
});
if (!loginButtonFound) {
if (loginUrl == "https://meta.appinn.net") {
await page.goto("https://meta.appinn.net/t/topic/52006", {
waitUntil: "domcontentloaded",
});
await page.click(".discourse-reactions-reaction-button");
} else {
await page.goto(`${loginUrl}/t/topic/1`, {
waitUntil: "domcontentloaded",
});
await page.click(".discourse-reactions-reaction-button");
}
}
// 等待用户名输入框加载
await page.waitForSelector("#login-account-name");
// 模拟人类在找到输入框后的短暂停顿
await delayClick(500); // 延迟500毫秒
// 清空输入框并输入用户名
await page.click("#login-account-name", { clickCount: 3 });
await page.type("#login-account-name", username, {
delay: 100,
}); // 输入时在每个按键之间添加额外的延迟
// 等待密码输入框加载
await page.waitForSelector("#login-account-password");
// 模拟人类在输入用户名后的短暂停顿
// delayClick; // 清空输入框并输入密码
await page.click("#login-account-password", { clickCount: 3 });
await page.type("#login-account-password", password, {
delay: 100,
});
// 模拟人类在输入完成后思考的短暂停顿
await delayClick(1000);
// 假设登录按钮的ID是'login-button',点击登录按钮
await page.waitForSelector("#login-button");
await page.click("#login-button");
await delayClick(500); // 模拟在点击登录按钮前的短暂停顿
try {
await Promise.all([
page.waitForNavigation({ waitUntil: "domcontentloaded" }), // 等待 页面跳转 DOMContentLoaded 事件
// 去掉上面一行会报错:Error: Execution context was destroyed, most likely because of a navigation. 可能是因为之后没等页面加载完成就执行了脚本
page.click("#login-button", { force: true }), // 点击登录按钮触发跳转
]); //注意如果登录失败,这里会一直等待跳转,导致脚本执行失败 这点四个月之前你就发现了结果今天又遇到(有个用户遇到了https://linux.do/t/topic/169209/82),但是你没有在这个报错你提示我8.5
} catch (error) {
const alertError = await page.$(".alert.alert-error");
if (alertError) {
const alertText = await page.evaluate((el) => el.innerText, alertError); // 使用 evaluate 获取 innerText
if (
alertText.includes("incorrect") ||
alertText.includes("Incorrect ") ||
alertText.includes("不正确")
) {
throw new Error(
`非超时错误,请检查用户名密码是否正确,失败用户 ${username}, 错误信息:${alertText}`
);
} else {
throw new Error(
`非超时错误,也不是密码错误,失败用户 ${username},错误信息:${alertText}`
);
}
} else {
if (retryCount > 0) {
console.log("Retrying login...");
await delayClick(2000); // 增加重试前的延迟
return await login(page, username, password, retryCount - 1);
} else {
throw new Error(
`Navigation timed out in login.超时了,可能是IP质量问题,失败用户 ${username},
${error}`
); //{password}
}
}
}
await delayClick(1000);
}
async function navigatePage(url, page, browser) {
await page.goto(url, { waitUntil: "domcontentloaded" }); //如果使用默认的load,linux下页面会一直加载导致无法继续执行
const startTime = Date.now(); // 记录开始时间
let pageTitle = await page.title(); // 获取当前页面标题
while (pageTitle.includes("Just a moment")) {
console.log("The page is under Cloudflare protection. Waiting...");
await delayClick(2000); // 每次检查间隔2秒
// 重新获取页面标题
pageTitle = await page.title();
// 检查是否超过15秒
if (Date.now() - startTime > 35000) {
console.log("Timeout exceeded, aborting actions.");
await browser.close();
return; // 超时则退出函数
}
}
console.log("页面标题:", pageTitle);
}
// 每秒截图功能
async function takeScreenshots(page) {
let screenshotIndex = 0;
setInterval(async () => {
screenshotIndex++;
const screenshotPath = path.join(
screenshotDir,
`screenshot-${screenshotIndex}.png`
);
try {
await page.screenshot({ path: screenshotPath, fullPage: true });
console.log(`Screenshot saved: ${screenshotPath}`);
} catch (error) {
console.error("Error taking screenshot:", error);
}
}, 1000);
// 注册退出时删除文件夹的回调函数
process.on("exit", () => {
try {
fs.rmdirSync(screenshotDir, { recursive: true });
console.log(`Deleted folder: ${screenshotDir}`);
} catch (error) {
console.error(`Error deleting folder ${screenshotDir}:`, error);
}
});
}
import express from "express";
const healthApp = express();
const HEALTH_PORT = process.env.HEALTH_PORT || 7860;
// 健康探针路由
healthApp.get("/health", (req, res) => {
const memoryUsage = process.memoryUsage();
// 将字节转换为MB
const memoryUsageMB = {
rss: `${(memoryUsage.rss / (1024 * 1024)).toFixed(2)} MB`, // 转换为MB并保留两位小数
heapTotal: `${(memoryUsage.heapTotal / (1024 * 1024)).toFixed(2)} MB`,
heapUsed: `${(memoryUsage.heapUsed / (1024 * 1024)).toFixed(2)} MB`,
external: `${(memoryUsage.external / (1024 * 1024)).toFixed(2)} MB`,
arrayBuffers: `${(memoryUsage.arrayBuffers / (1024 * 1024)).toFixed(2)} MB`,
};
const healthData = {
status: "OK",
timestamp: new Date().toISOString(),
memoryUsage: memoryUsageMB,
uptime: process.uptime().toFixed(2), // 保留两位小数
};
res.status(200).json(healthData);
});
healthApp.get("/", (req, res) => {
res.send(`
<html>
<head>
<title>Auto Read</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f4f4f4;
color: #333;
margin: 0;
padding: 20px;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
.container {
background-color: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 0 15px rgba(0, 0, 0, 0.1);
max-width: 600px;
text-align: center;
}
h1 {
color: #007bff;
}
p {
font-size: 18px;
margin: 15px 0;
}
a {
color: #007bff;
text-decoration: none;
font-weight: bold;
}
a:hover {
text-decoration: underline;
}
footer {
margin-top: 20px;
font-size: 14px;
color: #555;
}
</style>
</head>
<body>
<div class="container">
<h1>Welcome to the Auto Read App</h1>
<p>You can check the server's health at <a href="/health">/health</a>.</p>
<p>GitHub: <a href="https://github.com/14790897/auto-read-liunxdo" target="_blank">https://github.com/14790897/auto-read-liunxdo</a></p>
<footer>© 2024 Auto Read App</footer>
</div>
</body>
</html>
`);
});
healthApp.listen(HEALTH_PORT, () => {
console.log(
`Health check endpoint is running at http://localhost:${HEALTH_PORT}/health`
);
});
================================================
FILE: bypasscf_playwright.mjs
================================================
/*
Auto Read App (Playwright 版本)
---------------------------------
将原 Puppeteer + puppeteer-extra + puppeteer-real-browser 的脚本整体迁移到 Playwright。
主要差异:
1. 使用 playwright‑extra + stealth 插件来隐藏自动化特征。
2. Puppeteer 的 page.evaluateOnNewDocument → Playwright 的 page.addInitScript。
3. Puppeteer 的 type/click 改为 Playwright 的 fill/click/locator 组合。
4. Browser 实例 → Context → Page 三层模型。
5. 其余业务逻辑、并发调度、Telegram、Express 健康探针保持不变。
依赖安装:
----------------
npm i playwright-extra playwright-extra-plugin-stealth playwright @playwright/test node-telegram-bot-api dotenv express
# 若需要下载浏览器:
npx playwright install chromium
运行:
node auto-read-playwright.mjs
*/
import fs from "fs";
import path, { dirname, join } from "path";
import { fileURLToPath } from "url";
import dotenv from "dotenv";
import TelegramBot from "node-telegram-bot-api";
import express from "express";
// --- Playwright & Stealth ----------------------------------------------
import { chromium } from "playwright-extra";
import StealthPlugin from 'puppeteer-extra-plugin-stealth'
const stealth = StealthPlugin();
chromium.use(stealth);
// 代理配置
import {
getProxyConfig,
getPlaywrightProxyConfig,
testProxyConnection,
getCurrentIP
} from "./src/proxy_config.js";
// -----------------------------------------------------------------------
// 环境变量加载
// -----------------------------------------------------------------------
if (fs.existsSync(".env.local")) {
console.log("Using .env.local file to supply config environment variables");
const envConfig = dotenv.parse(fs.readFileSync(".env.local"));
for (const k in envConfig) process.env[k] = envConfig[k];
} else {
dotenv.config();
console.log("Using .env file to supply config environment variables…");
}
// 运行时间限制
const runTimeLimitMinutes = Number(process.env.RUN_TIME_LIMIT_MINUTES || 20);
const runTimeLimitMillis = runTimeLimitMinutes * 60 * 1000;
console.log(`运行时间限制:${runTimeLimitMinutes} 分钟 (${runTimeLimitMillis} ms)`);
setTimeout(() => {
console.log("Reached time limit, shutting down…");
process.exit(0);
}, runTimeLimitMillis);
// Telegram --------------------------------------------------------------
const token = process.env.TELEGRAM_BOT_TOKEN;
const chatId = process.env.TELEGRAM_CHAT_ID;
let bot = null;
if (token && chatId) bot = new TelegramBot(token);
const sendToTelegram = (msg) => bot?.sendMessage(chatId, msg).catch(console.error);
// 全局配置 --------------------------------------------------------------
const maxConcurrentAccounts = 4;
const usernames = (process.env.USERNAMES || "").split(",").filter(Boolean);
const passwords = (process.env.PASSWORDS || "").split(",").filter(Boolean);
if (usernames.length !== passwords.length) throw new Error("USERNAMES 与 PASSWORDS 数量不一致");
const loginUrl = process.env.WEBSITE || "https://linux.do";
const totalAccounts = usernames.length;
const delayBetweenInstances = 10_000; // 每批账号实例间隔
const delayBetweenBatches = runTimeLimitMillis / Math.ceil(totalAccounts / maxConcurrentAccounts);
const specificUser = process.env.SPECIFIC_USER || "14790897";
const isLikeSpecificUser = process.env.LIKE_SPECIFIC_USER === "true";
const isAutoLike = process.env.AUTO_LIKE !== "false"; // 默认自动
const delay = (ms) => new Promise((r) => setTimeout(r, ms));
// -----------------------------------------------------------------------
// 主流程
// -----------------------------------------------------------------------
(async () => {
try {
// 代理配置检查
const proxyConfig = getProxyConfig();
if (proxyConfig) {
console.log(`代理配置: ${proxyConfig.type}://${proxyConfig.host}:${proxyConfig.port}`);
// 测试代理连接
console.log("正在测试代理连接...");
const proxyWorking = await testProxyConnection(proxyConfig);
if (proxyWorking) {
console.log("✅ 代理连接测试成功");
} else {
console.log("❌ 代理连接测试失败,将使用直连");
}
} else {
console.log("未配置代理,使用直连");
const currentIP = await getCurrentIP();
if (currentIP) {
console.log(`当前IP地址: ${currentIP}`);
}
}
const batches = [];
for (let i = 0; i < totalAccounts; i += maxConcurrentAccounts) {
batches.push(usernames.slice(i, i + maxConcurrentAccounts).map((u, idx) => ({
username: u,
password: passwords[i + idx],
delay: idx * delayBetweenInstances,
})));
}
// 按批次顺序执行
for (let b = 0; b < batches.length; b++) {
console.log(`开始第 ${b + 1}/${batches.length} 批`);
const browsers = await Promise.all(
batches[b].map(({ username, password, delay: d }) =>
launchBrowserForUser(username, password, d)
)
);
// 若有下批次则等待
if (b <= batches.length - 1) {
console.log(`等待 ${delayBetweenBatches / 1000}s 进入下一批…`);
await delay(delayBetweenBatches);
}
// 关闭浏览器
for (const br of browsers) await br?.close();
}
console.log("所有账号处理完成 ✨");
} catch (err) {
console.error(err);
sendToTelegram?.(`脚本异常: ${err.message}`);
}
})();
// -----------------------------------------------------------------------
// 核心:为单个账号启动 Playwright、完成登录与业务逻辑
// -----------------------------------------------------------------------
async function launchBrowserForUser(username, password, instanceDelay) {
await delay(instanceDelay); // 分散启动时序
const launchOpts = {
headless: false, // 改为 false,显示浏览器窗口
args: ["--no-sandbox", "--disable-setuid-sandbox"],
};
// 添加代理配置
const proxyConfig = getProxyConfig();
const playwrightProxy = getPlaywrightProxyConfig(proxyConfig);
const browser = await chromium.launch(launchOpts);
const contextOptions = {
locale: "en-US",
userAgent: `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36`,
viewport: {
width: 1280 + Math.floor(Math.random() * 100),
height: 720 + Math.floor(Math.random() * 100),
},
};
// 如果有代理配置,添加到context选项
if (playwrightProxy) {
contextOptions.proxy = playwrightProxy;
console.log(`为用户 ${username} 启用代理: ${playwrightProxy.server}`);
}
const context = await browser.newContext(contextOptions);
await context.setExtraHTTPHeaders({ "accept-language": "en-US,en;q=0.9" });
const page = await context.newPage();
page.on("pageerror", (err) => console.error("Page error:", err.message));
page.on("console", (msg) => console.log("[console]", msg.text()));
// Cloudflare challenge 处理
await navigatePage(loginUrl, page);
// 登录
await login(page, username, password);
await page.waitForTimeout(5000); // 登录后等待2秒
// 跳到目标帖子
const target =
loginUrl === "https://linux.do"
? "https://linux.do/t/topic/13716/700"
: loginUrl === "https://meta.appinn.net"
? "https://meta.appinn.net/t/topic/52006"
: `${loginUrl}/t/topic/1`;
await page.goto(target, { waitUntil: "domcontentloaded" });
await page.waitForTimeout(2000); // 跳转后等待2秒
// 登录成功后注入业务脚本
const scriptPath = resolveBusinessScript();
const externalScript = fs.readFileSync(scriptPath, "utf8");
await page.addInitScript(
([u, script, autoLike]) => {
localStorage.setItem("read", true);
localStorage.setItem("specificUser", u);
localStorage.setItem("isFirstRun", "false");
localStorage.setItem("autoLikeEnabled", autoLike);
console.log("当前点赞用户:", u);
eval(script);
},
[specificUser, externalScript, isAutoLike]
);
sendToTelegram?.(`${username} 登录成功`);
return browser;
}
function resolveBusinessScript() {
const base = dirname(fileURLToPath(import.meta.url));
if (isLikeSpecificUser) {
return join(base, Math.random() < 0.5 ? "index_likeUser_activity.js" : "index_likeUser.js");
}
return join(base, "index.js");
}
// -----------------------------------------------------------------------
// Cloudflare challenge 简易检测:页面标题含 Just a moment
// -----------------------------------------------------------------------
async function navigatePage(url, page) {
await page.goto(url, { waitUntil: "domcontentloaded" });
const start = Date.now();
while ((await page.title()).includes("Just a moment")) {
console.log("Cloudflare challenge… 等待中");
await delay(2000);
if (Date.now() - start > 35_000) throw new Error("Cloudflare 验证超时");
}
console.log("已通过 Cloudflare, 页面标题:", await page.title());
}
// -----------------------------------------------------------------------
// 登录逻辑:根据页面实际结构适当调整选择器
// -----------------------------------------------------------------------
async function login(page, username, password, retry = 3) {
// 点击登录按钮(中文/英文)或者类 .login-button
const loginBtn = page.locator("button:text-is('登录'), button:text-is('login'), .login-button");
if (await loginBtn.count()) await loginBtn.first().click();
await page.locator("#login-account-name").waitFor();
await page.fill("#login-account-name", username);
await delay(1000); // 填写用户名后等待1秒
await page.fill("#login-account-password", password);
await delay(1000); // 填写密码后等待1秒
await delay(800);
await Promise.all([
page.waitForNavigation({ waitUntil: "domcontentloaded" }),
page.click("#login-button"),
]).catch(async (err) => {
if (retry > 0) {
console.log("登录失败,重试…", err.message);
await page.reload({ waitUntil: "domcontentloaded" });
await delay(2000);
return login(page, username, password, retry - 1);
}
throw new Error(`登录失败(${username}): ${err.message}`);
});
// 检查头像
if (!(await page.locator("img.avatar").count())) throw new Error("登录后未找到头像, 疑似失败");
}
// -----------------------------------------------------------------------
// Express 健康监测
// -----------------------------------------------------------------------
const HEALTH_PORT = process.env.HEALTH_PORT || 7860;
const app = express();
app.get("/health", (_req, res) => {
const m = process.memoryUsage();
const fmt = (b) => `${(b / 1024 / 1024).toFixed(2)} MB`;
res.json({
status: "OK",
timestamp: new Date().toISOString(),
uptime: process.uptime().toFixed(2),
memory: Object.fromEntries(Object.entries(m).map(([k, v]) => [k, fmt(v)])),
});
});
app.get("/", (_req, res) => res.send(`<h1>Auto Read (Playwright)</h1><p>Health: <a href="/health">/health</a></p>`));
app.listen(HEALTH_PORT, () => console.log(`Health endpoint: http://localhost:${HEALTH_PORT}/health`));
================================================
FILE: cron.log
================================================
================================================
FILE: cron.sh
================================================
#!/bin/bash
#设置为中文
export LANG=zh_CN.UTF-8
export LC_ALL=zh_CN.UTF-8
# 获取当前工作目录
WORKDIR=$(dirname $(readlink -f $0))
# 进入工作目录
cd $WORKDIR
# 停止 Docker Compose
/usr/local/bin/docker-compose down --remove-orphans --volumes
# 重新启动 Docker Compose
/usr/local/bin/docker-compose up -d >> ./cron.log 2>&1
# 等待20分钟
sleep 20m
/usr/local/bin/docker-compose logs >> ./cron.log 2>&1
# 停止 Docker Compose
/usr/local/bin/docker-compose down --remove-orphans --volumes >> ./cron.log 2>&1
================================================
FILE: debug_db.js
================================================
import { Pool } from 'pg';
import fs from 'fs';
import dotenv from 'dotenv';
dotenv.config();
if (fs.existsSync(".env.local")) {
console.log("Using .env.local file to supply config environment variables");
const envConfig = dotenv.parse(fs.readFileSync(".env.local"));
for (const k in envConfig) {
process.env[k] = envConfig[k];
}
}
const pool = new Pool({
connectionString: process.env.POSTGRES_URI,
max: 5,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000,
ssl: { rejectUnauthorized: false }
});
async function debugDatabase() {
try {
// 检查最近的一些posts
const res = await pool.query('SELECT guid, title, created_at FROM posts ORDER BY created_at DESC LIMIT 10');
console.log('Recent posts in database:');
res.rows.forEach(row => {
console.log(`GUID: ${row.guid}, Title: ${row.title.substring(0, 50)}..., Created: ${row.created_at}`);
});
// 检查特定GUID是否存在
const testGuids = [
'https://linux.do/t/topic/298776',
'https://linux.do/t/topic/298777',
'https://linux.do/t/topic/298778'
];
console.log('\nChecking specific GUIDs:');
for (const guid of testGuids) {
const check = await pool.query('SELECT COUNT(*) FROM posts WHERE guid = $1', [guid]);
console.log(`GUID ${guid}: exists = ${check.rows[0].count > 0}`);
}
// 查看总记录数
const countRes = await pool.query('SELECT COUNT(*) FROM posts');
console.log(`\nTotal posts in database: ${countRes.rows[0].count}`);
} catch (error) {
console.error('Database error:', error);
} finally {
await pool.end();
}
}
debugDatabase();
================================================
FILE: debug_rss.js
================================================
import { parseRss } from './src/parse_rss.js';
import { isGuidExists } from './src/db.js';
import fs from 'fs';
import dotenv from 'dotenv';
dotenv.config();
if (fs.existsSync(".env.local")) {
const envConfig = dotenv.parse(fs.readFileSync(".env.local"));
for (const k in envConfig) {
process.env[k] = envConfig[k];
}
}
async function debugRssAndGuids() {
try {
// 模拟获取RSS数据
const response = await fetch('https://linux.do/latest.rss');
const xmlData = await response.text();
// 分析RSS原始结构
const xml2js = await import('xml2js');
const parser = new xml2js.Parser({ explicitArray: false, trim: true });
const result = await parser.parseStringPromise(xmlData);
const items = result.rss.channel.item;
console.log('原始RSS数据分析:');
console.log('Items数量:', Array.isArray(items) ? items.length : 1);
if (Array.isArray(items)) {
console.log('\n前3个items的GUID信息:');
for (let i = 0; i < Math.min(3, items.length); i++) {
const item = items[i];
console.log(`Item ${i + 1}:`);
console.log(' title:', item.title);
console.log(' link:', item.link);
console.log(' guid对象:', JSON.stringify(item.guid, null, 2));
console.log(' guid._:', item.guid?._);
console.log(' guid.isPermaLink:', item.guid?.$?.isPermaLink);
// 检查这个GUID是否在数据库中存在
const guid = item.guid?._|| item.guid;
console.log(' 提取的guid:', guid);
if (guid) {
const exists = await isGuidExists(guid);
console.log(' 数据库中存在:', exists);
}
console.log('');
}
}
} catch (error) {
console.error('Error:', error);
}
}
debugRssAndGuids();
================================================
FILE: docker-compose-like-user.yml
================================================
services:
autolikeuser:
image: 14790897/auto-like-user:latest
container_name: auto-like-user
env_file:
- ./.env
- ./.env.local
restart: unless-stopped # 容器退出时重启策略
================================================
FILE: docker-compose.yml
================================================
services:
autoread:
image: 14790897/auto-read:latest
container_name: auto-read
env_file:
- path: ./.env
required: true # 这个文件必须存在,否则报错
- path: ./.env.local
required: false # 这个文件是可选的:存在就覆盖前者,不存在就忽略
restart: unless-stopped
ports:
- "7860:7860"
================================================
FILE: external.js
================================================
// ==UserScript==
// @name Auto Read
// @namespace http://tampermonkey.net/
// @version 1.3.1
// @description 自动刷linuxdo文章
// @author liuweiqing
// @match https://meta.discourse.org/*
// @match https://linux.do/*
// @match https://meta.appinn.net/*
// @match https://community.openai.com/
// @grant none
// @license MIT
// @icon https://www.google.com/s2/favicons?domain=linux.do
// ==/UserScript==
(function () {
("use strict");
// 定义可能的基本URL
const possibleBaseURLs = [
"https://meta.discourse.org",
"https://linux.do",
"https://meta.appinn.net",
"https://community.openai.com",
];
// 获取当前页面的URL
const currentURL = window.location.href;
// 确定当前页面对应的BASE_URL
let BASE_URL = possibleBaseURLs.find((url) => currentURL.startsWith(url));
// 环境变量:阅读网址,如果没有找到匹配的URL,则默认为第一个
if (!BASE_URL) {
BASE_URL = possibleBaseURLs[0];
console.log("默认BASE_URL设置为: " + BASE_URL);
} else {
console.log("当前BASE_URL是: " + BASE_URL);
}
// 以下是脚本的其余部分
console.log("脚本正在运行在: " + BASE_URL);
//1.进入网页 https://linux.do/t/topic/数字(1,2,3,4)
//2.使滚轮均衡的往下移动模拟刷文章
// 检查是否是第一次运行脚本
function checkFirstRun() {
if (localStorage.getItem("isFirstRun") === null) {
// 是第一次运行,执行初始化操作
console.log("脚本第一次运行,执行初始化操作...");
updateInitialData();
// 设置 isFirstRun 标记为 false
localStorage.setItem("isFirstRun", "false");
} else {
// 非第一次运行
console.log("脚本非第一次运行");
}
}
// 更新初始数据的函数
function updateInitialData() {
localStorage.setItem("read", "true"); // 开始时自动滚动关闭
localStorage.setItem("autoLikeEnabled", "true"); //默认关闭自动点赞
console.log("执行了初始数据更新操作");
}
const delay = 2000; // 滚动检查的间隔(毫秒)
let scrollInterval = null;
let checkScrollTimeout = null;
let autoLikeInterval = null;
function scrollToBottomSlowly(
stopDistance = 9999999999,
callback = undefined,
distancePerStep = 20,
delayPerStep = 50
) {
if (scrollInterval !== null) {
clearInterval(scrollInterval);
}
scrollInterval = setInterval(() => {
if (
window.innerHeight + window.scrollY >=
document.body.offsetHeight - 100 ||
window.innerHeight + window.scrollY >= stopDistance
) {
clearInterval(scrollInterval);
scrollInterval = null;
if (typeof callback === "function") {
callback(); // 当滚动结束时调用回调函数
}
} else {
window.scrollBy(0, distancePerStep);
}
}, delayPerStep);
}
// 功能:跳转到下一个话题
function navigateToNextTopic() {
// 定义包含文章列表的数组
const urls = [
`${BASE_URL}/latest`,
`${BASE_URL}/top`,
`${BASE_URL}/latest?ascending=false&order=posts`,
// `${BASE_URL}/unread`, // 示例:如果你想将这个URL启用,只需去掉前面的注释
];
// 生成一个随机索引
const randomIndex = Math.floor(Math.random() * urls.length);
// 根据随机索引选择一个URL
const nextTopicURL = urls[randomIndex]; // 在跳转之前,标记即将跳转到下一个话题
localStorage.setItem("navigatingToNextTopic", "true");
// 尝试导航到下一个话题
window.location.href = nextTopicURL;
}
// 检查是否已滚动到底部(不断重复执行)
function checkScroll() {
if (localStorage.getItem("read")) {
if (
window.innerHeight + window.scrollY >=
document.body.offsetHeight - 100
) {
console.log("已滚动到底部");
navigateToNextTopic();
} else {
scrollToBottomSlowly();
if (checkScrollTimeout !== null) {
clearTimeout(checkScrollTimeout);
}
checkScrollTimeout = setTimeout(checkScroll, delay);
}
}
}
// 入口函数
window.addEventListener("load", () => {
checkFirstRun();
console.log(
"autoRead",
localStorage.getItem("read"),
"autoLikeEnabled",
localStorage.getItem("autoLikeEnabled")
);
if (localStorage.getItem("read") === "true") {
// 检查是否正在导航到下一个话题
if (localStorage.getItem("navigatingToNextTopic") === "true") {
console.log("正在导航到下一个话题");
// 等待一段时间或直到页面完全加载
// 页面加载完成后,移除标记
localStorage.removeItem("navigatingToNextTopic");
// 使用setTimeout延迟执行
setTimeout(() => {
// 先随机滚动一段距离然后再查找链接
scrollToBottomSlowly(
Math.random() * document.body.offsetHeight * 3,
searchLinkClick,
20,
20
);
}, 2000); // 延迟2000毫秒(即2秒)
} else {
console.log("执行正常的滚动和检查逻辑");
// 执行正常的滚动和检查逻辑
checkScroll();
if (isAutoLikeEnabled()) {
//自动点赞
autoLike();
}
}
}
});
// 创建一个控制滚动的按钮
function searchLinkClick() {
// 在新页面加载后执行检查
// 使用CSS属性选择器寻找href属性符合特定格式的<a>标签
const links = document.querySelectorAll('a[href^="/t/"]');
// const alreadyReadLinks = JSON.parse(
// localStorage.getItem("alreadyReadLinks") || "[]"
// ); // 获取已阅读链接列表
// 筛选出未阅读的链接
const unreadLinks = Array.from(links).filter((link) => {
// 检查链接是否已经被读过
// const isAlreadyRead = alreadyReadLinks.includes(link.href);
// if (isAlreadyRead) {
// return false; // 如果链接已被读过,直接排除
// }
// 向上遍历DOM树,查找包含'visited'类的父级元素,最多查找三次
let parent = link.parentElement;
let times = 0; // 查找次数计数器
while (parent && times < 3) {
if (parent.classList.contains("visited")) {
// 如果找到包含'visited'类的父级元素,中断循环
return false; // 父级元素包含'visited'类,排除这个链接
}
parent = parent.parentElement; // 继续向上查找
times++; // 增加查找次数
}
// 如果链接未被读过,且在向上查找三次内,其父级元素中没有包含'visited'类,则保留这个链接
return true;
});
// 如果找到了这样的链接
if (unreadLinks.length > 0) {
// 从所有匹配的链接中随机选择一个
const randomIndex = Math.floor(Math.random() * unreadLinks.length);
const link = unreadLinks[randomIndex];
// 打印找到的链接(可选)
console.log("Found link:", link.href);
// // 模拟点击该链接
// setTimeout(() => {
// link.click();
// }, delay);
// 将链接添加到已阅读列表并更新localStorage
// alreadyReadLinks.push(link.href);
// localStorage.setItem(
// "alreadyReadLinks",
// JSON.stringify(alreadyReadLinks)
// );
// 导航到该链接
window.location.href = link.href;
} else {
// 如果没有找到符合条件的链接,打印消息(可选)
console.log("No link with the specified format was found.");
scrollToBottomSlowly(
Math.random() * document.body.offsetHeight * 3,
searchLinkClick
);
}
}
// 获取当前时间戳
const currentTime = Date.now();
// 获取存储的时间戳
const defaultTimestamp = new Date("1999-01-01T00:00:00Z").getTime(); //默认值为1999年
const storedTime = parseInt(
localStorage.getItem("clickCounterTimestamp") ||
defaultTimestamp.toString(),
10
);
// 获取当前的点击计数,如果不存在则初始化为0
let clickCounter = parseInt(localStorage.getItem("clickCounter") || "0", 10);
// 检查是否超过24小时(24小时 = 24 * 60 * 60 * 1000 毫秒)
if (currentTime - storedTime > 24 * 60 * 60 * 1000) {
// 超过24小时,清空点击计数器并更新时间戳
clickCounter = 0;
localStorage.setItem("clickCounter", "0");
localStorage.setItem("clickCounterTimestamp", currentTime.toString());
}
console.log(`Initial clickCounter: ${clickCounter}`);
function triggerClick(button) {
const event = new MouseEvent("click", {
bubbles: true,
cancelable: true,
view: window,
});
button.dispatchEvent(event);
}
function autoLike() {
console.log(`Initial clickCounter: ${clickCounter}`);
// 寻找所有的discourse-reactions-reaction-button
const buttons = document.querySelectorAll(
".discourse-reactions-reaction-button"
);
if (buttons.length === 0) {
console.error(
"No buttons found with the selector '.discourse-reactions-reaction-button'"
);
return;
}
console.log(`Found ${buttons.length} buttons.`); // 调试信息
// 逐个点击找到的按钮
buttons.forEach((button, index) => {
if (
(button.title !== "点赞此帖子" && button.title !== "Like this post") ||
clickCounter >= 50
) {
return;
}
// 使用setTimeout来错开每次点击的时间,避免同时触发点击
autoLikeInterval = setTimeout(() => {
// 模拟点击
triggerClick(button); // 使用自定义的触发点击方法
console.log(`Clicked like button ${index + 1}`);
clickCounter++; // 更新点击计数器
// 将新的点击计数存储到localStorage
localStorage.setItem("clickCounter", clickCounter.toString());
// 如果点击次数达到50次,则设置点赞变量为false
if (clickCounter === 50) {
console.log("Reached 50 likes, setting the like variable to false.");
localStorage.setItem("autoLikeEnabled", "false"); // 使用localStorage存储点赞变量状态
} else {
console.log("clickCounter:", clickCounter);
}
}, index * 3000); // 这里的3000毫秒是两次点击之间的间隔,可以根据需要调整
});
}
const button = document.createElement("button");
// 初始化按钮文本基于当前的阅读状态
button.textContent =
localStorage.getItem("read") === "true" ? "停止阅读" : "开始阅读";
button.style.position = "fixed";
button.style.bottom = "10px"; // 之前是 top
button.style.left = "10px"; // 之前是 right
button.style.zIndex = 1000;
button.style.backgroundColor = "#f0f0f0"; // 浅灰色背景
button.style.color = "#000"; // 黑色文本
button.style.border = "1px solid #ddd"; // 浅灰色边框
button.style.padding = "5px 10px"; // 内边距
button.style.borderRadius = "5px"; // 圆角
// document.body.appendChild(button);
button.onclick = function () {
const currentlyReading = localStorage.getItem("read") === "true";
const newReadState = !currentlyReading;
localStorage.setItem("read", newReadState.toString());
button.textContent = newReadState ? "停止阅读" : "开始阅读";
if (!newReadState) {
if (scrollInterval !== null) {
clearInterval(scrollInterval);
scrollInterval = null;
}
if (checkScrollTimeout !== null) {
clearTimeout(checkScrollTimeout);
checkScrollTimeout = null;
}
localStorage.removeItem("navigatingToNextTopic");
} else {
// 如果是Linuxdo,就导航到我的帖子
if (BASE_URL == "https://linux.do") {
window.location.href = "https://linux.do/t/topic/13716/340";
} else if (BASE_URL == "https://meta.appinn.net") {
window.location.href = "https://meta.appinn.net/t/topic/52006";
} else {
window.location.href = `${BASE_URL}/t/topic/1`;
}
checkScroll();
}
};
//自动点赞按钮
// 在页面上添加一个控制自动点赞的按钮
const toggleAutoLikeButton = document.createElement("button");
toggleAutoLikeButton.textContent = isAutoLikeEnabled()
? "禁用自动点赞"
: "启用自动点赞";
toggleAutoLikeButton.style.position = "fixed";
toggleAutoLikeButton.style.bottom = "50px"; // 之前是 top,且与另一个按钮错开位置
toggleAutoLikeButton.style.left = "10px"; // 之前是 right
toggleAutoLikeButton.style.zIndex = "1000";
toggleAutoLikeButton.style.backgroundColor = "#f0f0f0"; // 浅灰色背景
toggleAutoLikeButton.style.color = "#000"; // 黑色文本
toggleAutoLikeButton.style.border = "1px solid #ddd"; // 浅灰色边框
toggleAutoLikeButton.style.padding = "5px 10px"; // 内边距
toggleAutoLikeButton.style.borderRadius = "5px"; // 圆角
// document.body.appendChild(toggleAutoLikeButton);
// 为按钮添加点击事件处理函数
toggleAutoLikeButton.addEventListener("click", () => {
const isEnabled = !isAutoLikeEnabled();
setAutoLikeEnabled(isEnabled);
toggleAutoLikeButton.textContent = isEnabled
? "禁用自动点赞"
: "启用自动点赞";
});
// 判断是否启用自动点赞
function isAutoLikeEnabled() {
// 从localStorage获取autoLikeEnabled的值,如果未设置,默认为"true"
return localStorage.getItem("autoLikeEnabled") !== "false";
}
// 设置自动点赞的启用状态
function setAutoLikeEnabled(enabled) {
localStorage.setItem("autoLikeEnabled", enabled ? "true" : "false");
}
})();
================================================
FILE: index.js
================================================
// ==UserScript==
// @name Auto Read
// @namespace http://tampermonkey.net/
// @version 1.4.6
// @description 自动刷linuxdo文章
// @author liuweiqing
// @match https://meta.discourse.org/*
// @match https://linux.do/*
// @match https://meta.appinn.net/*
// @match https://community.openai.com/
// @match https://idcflare.com/*
// @exclude https://linux.do/a/9611/0
// @grant none
// @license MIT
// @icon https://www.google.com/s2/favicons?domain=linux.do
// @downloadURL https://update.greasyfork.org/scripts/489464/Auto%20Read.user.js
// @updateURL https://update.greasyfork.org/scripts/489464/Auto%20Read.meta.js
// ==/UserScript==
(function () {
("use strict");
// 定义可能的基本URL
const possibleBaseURLs = [
"https://linux.do",
"https://meta.discourse.org",
"https://meta.appinn.net",
"https://community.openai.com",
"https://idcflare.com/",
];
const commentLimit = 1000;
const topicListLimit = 100;
const likeLimit = 50;
// 获取当前页面的URL
const currentURL = window.location.href;
// 确定当前页面对应的BASE_URL
let BASE_URL = possibleBaseURLs.find((url) => currentURL.startsWith(url));
console.log("currentURL:", currentURL);
// 环境变量:阅读网址,如果没有找到匹配的URL,则默认为第一个
if (!BASE_URL) {
BASE_URL = possibleBaseURLs[0];
console.log("默认BASE_URL设置为: " + BASE_URL);
} else {
console.log("当前BASE_URL是: " + BASE_URL);
}
console.log("脚本正在运行在: " + BASE_URL);
//1.进入网页 https://linux.do/t/topic/数字(1,2,3,4)
//2.使滚轮均衡的往下移动模拟刷文章
// 检查是否是第一次运行脚本
function checkFirstRun() {
if (localStorage.getItem("isFirstRun") === null) {
console.log("脚本第一次运行,执行初始化操作...");
updateInitialData();
localStorage.setItem("isFirstRun", "false");
} else {
console.log("脚本非第一次运行");
}
}
// 更新初始数据的函数
function updateInitialData() {
localStorage.setItem("read", "false"); // 开始时自动滚动关闭
localStorage.setItem("autoLikeEnabled", "false"); //默认关闭自动点赞
console.log("执行了初始数据更新操作");
}
const delay = 2000; // 滚动检查的间隔(毫秒)
let scrollInterval = null;
let checkScrollTimeout = null;
let autoLikeInterval = null;
function scrollToBottomSlowly(distancePerStep = 20, delayPerStep = 50) {
if (scrollInterval !== null) {
clearInterval(scrollInterval);
}
scrollInterval = setInterval(() => {
window.scrollBy(0, distancePerStep);
}, delayPerStep); // 每50毫秒滚动20像素
}
function getLatestTopic() {
let latestPage = Number(localStorage.getItem("latestPage")) || 0;
let topicList = [];
let isDataSufficient = false;
while (!isDataSufficient) {
latestPage++;
const url = `${BASE_URL}/latest.json?no_definitions=true&page=${latestPage}`;
$.ajax({
url: url,
async: false,
success: function (result) {
if (
result &&
result.topic_list &&
result.topic_list.topics.length > 0
) {
result.topic_list.topics.forEach((topic) => {
// 未读且评论数小于 commentLimit
if (commentLimit > topic.posts_count) {
//其实不需要 !topic.unseen &&
topicList.push(topic);
}
});
// 检查是否已获得足够的 topics
if (topicList.length >= topicListLimit) {
isDataSufficient = true;
}
} else {
isDataSufficient = true; // 没有更多内容时停止请求
}
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
console.error(XMLHttpRequest, textStatus, errorThrown);
isDataSufficient = true; // 遇到错误时也停止请求
},
});
}
if (topicList.length > topicListLimit) {
topicList = topicList.slice(0, topicListLimit);
}
// 其实不需要对latestPage操作
// localStorage.setItem("latestPage", latestPage);
localStorage.setItem("topicList", JSON.stringify(topicList));
}
function openNewTopic() {
let topicListStr = localStorage.getItem("topicList");
let topicList = topicListStr ? JSON.parse(topicListStr) : [];
// 如果列表为空,则获取最新文章
if (topicList.length === 0) {
getLatestTopic();
topicListStr = localStorage.getItem("topicList");
topicList = topicListStr ? JSON.parse(topicListStr) : [];
}
// 如果获取到新文章,打开第一个
if (topicList.length > 0) {
const topic = topicList.shift();
localStorage.setItem("topicList", JSON.stringify(topicList));
if (topic.last_read_post_number) {
window.location.href = `${BASE_URL}/t/topic/${topic.id}/${topic.last_read_post_number}`;
} else {
window.location.href = `${BASE_URL}/t/topic/${topic.id}`;
}
}
}
// 检查是否已滚动到底部(不断重复执行),到底部时跳转到下一个话题
function checkScroll() {
if (localStorage.getItem("read")) {
if (
window.innerHeight + window.scrollY >=
document.body.offsetHeight - 100
) {
console.log("已滚动到底部");
openNewTopic();
} else {
scrollToBottomSlowly();
if (checkScrollTimeout !== null) {
clearTimeout(checkScrollTimeout);
}
checkScrollTimeout = setTimeout(checkScroll, delay);
}
}
}
// 入口函数
window.addEventListener("load", () => {
checkFirstRun();
console.log(
"autoRead",
localStorage.getItem("read"),
"autoLikeEnabled",
localStorage.getItem("autoLikeEnabled")
);
if (localStorage.getItem("read") === "true") {
console.log("执行正常的滚动和检查逻辑");
checkScroll();
if (isAutoLikeEnabled()) {
autoLike();
}
}
});
// 获取当前时间戳
const currentTime = Date.now();
// 获取存储的时间戳
const defaultTimestamp = new Date("1999-01-01T00:00:00Z").getTime(); //默认值为1999年
const storedTime = parseInt(
localStorage.getItem("clickCounterTimestamp") ||
defaultTimestamp.toString(),
10
);
// 获取当前的点击计数,如果不存在则初始化为0
let clickCounter = parseInt(localStorage.getItem("clickCounter") || "0", 10);
// 检查是否超过24小时(24小时 = 24 * 60 * 60 * 1000 毫秒)
if (currentTime - storedTime > 24 * 60 * 60 * 1000) {
// 超过24小时,清空点击计数器并更新时间戳
clickCounter = 0;
localStorage.setItem("clickCounter", "0");
localStorage.setItem("clickCounterTimestamp", currentTime.toString());
}
console.log(`Initial clickCounter: ${clickCounter}`);
function triggerClick(button) {
const event = new MouseEvent("click", {
bubbles: true,
cancelable: true,
view: window,
});
button.dispatchEvent(event);
}
function autoLike() {
console.log(`Initial clickCounter: ${clickCounter}`);
// 寻找所有的discourse-reactions-reaction-button
const buttons = document.querySelectorAll(
".discourse-reactions-reaction-button"
);
if (buttons.length === 0) {
console.error(
"No buttons found with the selector '.discourse-reactions-reaction-button'"
);
return;
}
console.log(`Found ${buttons.length} buttons.`); // 调试信息
// 逐个点击找到的按钮
buttons.forEach((button, index) => {
if (
(button.title !== "点赞此帖子" && button.title !== "Like this post") ||
clickCounter >= likeLimit
) {
return;
}
// 新增:点赞前加一个随机概率判断(如30%概率)
const likeProbability = 0.3; // 0~1之间,0.3表示30%概率
if (Math.random() > likeProbability) {
console.log(`跳过第${index + 1}个按钮(未通过概率判断)`);
return;
}
// 点赞间隔时间也随机(2~5秒之间)
const randomDelay = 2000 + Math.floor(Math.random() * 3000);
autoLikeInterval = setTimeout(() => {
// 模拟点击
triggerClick(button); // 使用自定义的触发点击方法
console.log(`Clicked like button ${index + 1}`);
clickCounter++; // 更新点击计数器
// 将新的点击计数存储到localStorage
localStorage.setItem("clickCounter", clickCounter.toString());
// 如果点击次数达到likeLimit次,则设置点赞变量为false
if (clickCounter === likeLimit) {
console.log(
`Reached ${likeLimit} likes, setting the like variable to false.`
);
localStorage.setItem("autoLikeEnabled", "false"); // 使用localStorage存储点赞变量状态
} else {
console.log("clickCounter:", clickCounter);
}
}, index * randomDelay); // 每次点赞的延迟为随机值
});
}
const button = document.createElement("button");
// 初始化按钮文本基于当前的阅读状态
button.textContent =
localStorage.getItem("read") === "true" ? "停止阅读" : "开始阅读";
button.style.position = "fixed";
button.style.bottom = "10px"; // 之前是 top
button.style.left = "10px"; // 之前是 right
button.style.zIndex = 1000;
button.style.backgroundColor = "#f0f0f0"; // 浅灰色背景
button.style.color = "#000"; // 黑色文本
button.style.border = "1px solid #ddd"; // 浅灰色边框
button.style.padding = "5px 10px"; // 内边距
button.style.borderRadius = "5px"; // 圆角
document.body.appendChild(button);
button.onclick = function () {
const currentlyReading = localStorage.getItem("read") === "true";
const newReadState = !currentlyReading;
localStorage.setItem("read", newReadState.toString());
button.textContent = newReadState ? "停止阅读" : "开始阅读";
if (!newReadState) {
if (scrollInterval !== null) {
clearInterval(scrollInterval);
scrollInterval = null;
}
if (checkScrollTimeout !== null) {
clearTimeout(checkScrollTimeout);
checkScrollTimeout = null;
}
localStorage.removeItem("navigatingToNextTopic");
} else {
// 如果是Linuxdo,就导航到我的帖子
if (BASE_URL == "https://linux.do") {
window.location.href = "https://linux.do/t/topic/13716/900";
} else {
window.location.href = `${BASE_URL}/t/topic/1`;
}
checkScroll();
}
};
//自动点赞按钮
// 在页面上添加一个控制自动点赞的按钮
const toggleAutoLikeButton = document.createElement("button");
toggleAutoLikeButton.textContent = isAutoLikeEnabled()
? "禁用自动点赞"
: "启用自动点赞";
toggleAutoLikeButton.style.position = "fixed";
toggleAutoLikeButton.style.bottom = "50px"; // 之前是 top,且与另一个按钮错开位置
toggleAutoLikeButton.style.left = "10px"; // 之前是 right
toggleAutoLikeButton.style.zIndex = "1000";
toggleAutoLikeButton.style.backgroundColor = "#f0f0f0"; // 浅灰色背景
toggleAutoLikeButton.style.color = "#000"; // 黑色文本
toggleAutoLikeButton.style.border = "1px solid #ddd"; // 浅灰色边框
toggleAutoLikeButton.style.padding = "5px 10px"; // 内边距
toggleAutoLikeButton.style.borderRadius = "5px"; // 圆角
document.body.appendChild(toggleAutoLikeButton);
// 为按钮添加点击事件处理函数
toggleAutoLikeButton.addEventListener("click", () => {
const isEnabled = !isAutoLikeEnabled();
setAutoLikeEnabled(isEnabled);
toggleAutoLikeButton.textContent = isEnabled
? "禁用自动点赞"
: "启用自动点赞";
});
// 判断是否启用自动点赞
function isAutoLikeEnabled() {
// 从localStorage获取autoLikeEnabled的值,如果未设置,默认为"true"
return localStorage.getItem("autoLikeEnabled") !== "false";
}
// 设置自动点赞的启用状态
function setAutoLikeEnabled(enabled) {
localStorage.setItem("autoLikeEnabled", enabled ? "true" : "false");
}
})();
================================================
FILE: index_likeUser.js
================================================
// ==UserScript==
// @name Auto Like Specific User
// @namespace http://tampermonkey.net/
// @version 1.1.2
// @description 自动点赞特定用户,适用于discourse
// @author liuweiqing
// @match https://meta.discourse.org/*
// @match https://linux.do/*
// @match https://meta.appinn.net/*
// @match https://community.openai.com/
// @grant none
// @license MIT
// @icon https://www.google.com/s2/favicons?domain=linux.do
// @downloadURL https://update.greasyfork.org/scripts/489464/Auto%20Read.user.js
// @updateURL https://update.greasyfork.org/scripts/489464/Auto%20Read.meta.js
// @run-at document-end
// ==/UserScript==
(function () {
("use strict");
// 定义可能的基本URL
const possibleBaseURLs = [
"https://meta.discourse.org",
"https://linux.do",
"https://meta.appinn.net",
"https://community.openai.com",
];
const commentLimit = 1000;
const specificUserPostListLimit = 100;
const currentURL = window.location.href;
let specificUser = localStorage.getItem("specificUser") || "14790897";
let likeLimit = parseInt(localStorage.getItem("likeLimit") || 200, 10);
let BASE_URL = possibleBaseURLs.find((url) => currentURL.startsWith(url));
// 环境变量:阅读网址,如果没有找到匹配的URL,则默认为第一个
if (!BASE_URL) {
BASE_URL = possibleBaseURLs[0];
console.log("当前BASE_URL设置为(默认): " + BASE_URL);
} else {
console.log("当前BASE_URL是: " + BASE_URL);
}
// 获取当前时间戳
const currentTime = Date.now();
// 获取存储的时间戳
const defaultTimestamp = new Date("1999-01-01T00:00:00Z").getTime(); //默认值为1999年
const storedTime = parseInt(
localStorage.getItem("clickCounterTimestamp") ||
defaultTimestamp.toString(),
10
);
// 获取当前的点击计数,如果不存在则初始化为0
let clickCounter = parseInt(localStorage.getItem("clickCounter") || "0", 10);
// 检查是否超过12小时(12小时 = 12 * 60 * 60 * 1000 毫秒)
if (currentTime - storedTime > 12 * 60 * 60 * 1000) {
// 超过24小时,清空点击计数器并更新时间戳
clickCounter = 0;
localStorage.setItem("clickCounter", "0");
localStorage.setItem("clickCounterTimestamp", currentTime.toString());
}
console.log(`Initial clickCounter: ${clickCounter}`);
// 入口函数
window.addEventListener("load", () => {
console.log("autoRead", localStorage.getItem("read"));
checkFirstRun();
if (localStorage.getItem("read") === "true") {
console.log("点赞开始");
setTimeout(() => {
likeSpecificPost();
}, 2000);
setTimeout(() => {
openSpecificUserPost();
}, 4000);
}
});
function checkFirstRun() {
if (localStorage.getItem("isFirstRun") === null) {
console.log("脚本第一次运行,执行初始化操作...");
updateInitialData();
localStorage.setItem("isFirstRun", "false");
} else {
console.log("脚本非第一次运行");
}
}
function updateInitialData() {
localStorage.setItem("read", "false"); // 开始时自动滚动关闭
console.log("执行了初始数据更新操作");
}
function getLatestTopic() {
let lastOffset = Number(localStorage.getItem("lastOffset")) || 0;
let specificUserPostList = [];
let isDataSufficient = false;
while (!isDataSufficient) {
// lastOffset += 20;
lastOffset += 1; //对于page来说
// 举例:https://linux.do/user_actions.json?offset=0&username=14790897&filter=5
// const url = `${BASE_URL}/user_actions.json?offset=${lastOffset}&username=${specificUser}&filter=5`;
//举例:https://linux.do/search?q=%4014790897%20in%3Aunseen
const url = `${BASE_URL}/search?q=%40${specificUser}%20in%3Aunseen`; //&page=${lastOffset}
$.ajax({
url: url,
async: false,
headers: {
Accept: "application/json",
},
success: function (result) {
// if (result && result.user_actions && result.user_actions.length > 0) {
// result.user_actions.forEach((action) => {
if (result && result.posts && result.posts.length > 0) {
result.posts.forEach((action) => {
const topicId = action.topic_id;
// const postId = action.post_id;
const postNumber = action.post_number;
specificUserPostList.push({
topic_id: topicId,
// post_id: postId,
post_number: postNumber,
});
});
// 检查是否已获得足够的 Posts
if (specificUserPostList.length >= specificUserPostListLimit) {
isDataSufficient = true;
}
} else {
isDataSufficient = true; // 没有更多内容时停止请求
}
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
console.error(XMLHttpRequest, textStatus, errorThrown);
isDataSufficient = true; // 遇到错误时也停止请求
},
});
}
// 如果列表超出限制,则截断
if (specificUserPostList.length > specificUserPostListLimit) {
specificUserPostList = specificUserPostList.slice(
0,
specificUserPostListLimit
);
}
// 存储 lastOffset 和 specificUserPostList 到 localStorage
localStorage.setItem("lastOffset", lastOffset);
localStorage.setItem(
"specificUserPostList",
JSON.stringify(specificUserPostList)
);
}
function openSpecificUserPost() {
let specificUserPostListStr = localStorage.getItem("specificUserPostList");
let specificUserPostList = specificUserPostListStr
? JSON.parse(specificUserPostListStr)
: [];
// 如果列表为空,则获取最新文章
if (specificUserPostList.length === 0) {
getLatestTopic();
specificUserPostListStr = localStorage.getItem("specificUserPostList");
specificUserPostList = specificUserPostListStr
? JSON.parse(specificUserPostListStr)
: [];
}
// 如果获取到新文章,打开第一个
if (specificUserPostList.length > 0) {
const post = specificUserPostList.shift(); // 获取列表中的第一个对象
localStorage.setItem(
"specificUserPostList",
JSON.stringify(specificUserPostList)
);
window.location.href = `${BASE_URL}/t/topic/${post.topic_id}/${post.post_number}`;
} else {
console.error("未能获取到新的帖子数据。");
}
}
// 检查是否点赞
// const postId = data.post_id;
// const targetId = `discourse-reactions-counter-${postId}-right`;
// const element = document.getElementById(targetId);
function likeSpecificPost() {
const urlParts = window.location.pathname.split("/");
const lastPart = urlParts[urlParts.length - 1]; // 获取最后一部分
let buttons, reactionButton;
console.log("post number:", lastPart);
if (lastPart < 10000) {
buttons = document.querySelectorAll(
"button[aria-label]" //[class*='reply']
);
let targetButton = null;
buttons.forEach((button) => {
const ariaLabel = button.getAttribute("aria-label");
if (ariaLabel && ariaLabel.includes(`#${lastPart}`)) {
targetButton = button;
console.log("找到post number按钮:", targetButton);
return;
}
});
if (targetButton) {
// 找到按钮后,获取其父级元素
const parentElement = targetButton.parentElement;
console.log("父级元素:", parentElement);
reactionButton = parentElement.querySelector(
".discourse-reactions-reaction-button"
);
} else {
console.log(`未找到包含 #${lastPart} 的按钮`);
}
} else {
//大于10000说明是主题帖,选择第一个
reactionButton = document.querySelectorAll(
".discourse-reactions-reaction-button"
)[0];
}
if (!reactionButton) {
console.log("未找到点赞按钮");
return;
}
if (
reactionButton.title !== "点赞此帖子" &&
reactionButton.title !== "Like this post"
) {
console.log("已经点赞过");
return "already liked";
} else if (clickCounter >= likeLimit) {
console.log("已经达到点赞上限");
localStorage.setItem("read", false);
return;
}
triggerClick(reactionButton);
clickCounter++;
console.log(
`Clicked like button ${clickCounter},已点赞用户${specificUser}`
);
localStorage.setItem("clickCounter", clickCounter.toString());
// 如果点击次数达到likeLimit次,则设置点赞变量为false
if (clickCounter === likeLimit) {
console.log(
`Reached ${likeLimit} likes, setting the like variable to false.`
);
localStorage.setItem("read", false);
} else {
console.log("clickCounter:", clickCounter);
}
}
function triggerClick(button) {
const event = new MouseEvent("click", {
bubbles: true,
cancelable: true,
view: window,
});
button.dispatchEvent(event);
}
const button = document.createElement("button");
button.textContent =
localStorage.getItem("read") === "true" ? "停止阅读" : "开始阅读";
button.style.position = "fixed";
button.style.bottom = "20px";
button.style.left = "20px";
button.style.zIndex = 1000;
button.style.backgroundColor = "#e0e0e0";
button.style.color = "#333";
button.style.border = "1px solid #aaa";
button.style.padding = "8px 16px";
button.style.borderRadius = "8px";
document.body.appendChild(button);
button.onclick = function () {
const currentlyReading = localStorage.getItem("read") === "true";
const newReadState = !currentlyReading;
localStorage.setItem("read", newReadState.toString());
button.textContent = newReadState ? "停止阅读" : "开始阅读";
if (newReadState) {
if (BASE_URL == "https://linux.do") {
const maxPostNumber = 600;
const randomPostNumber = Math.floor(Math.random() * maxPostNumber) + 1;
const newUrl = `https://linux.do/t/topic/13716/${randomPostNumber}`;
window.location.href = newUrl;
} else {
window.location.href = `${BASE_URL}/t/topic/1`;
}
}
};
const userInput = document.createElement("input");
userInput.type = "text";
userInput.placeholder = "输入要点赞的用户ID";
userInput.style.position = "fixed";
userInput.style.bottom = "90px";
userInput.style.left = "20px";
userInput.style.zIndex = "1000";
userInput.style.padding = "6px";
userInput.style.border = "1px solid #aaa";
userInput.style.borderRadius = "8px";
userInput.style.backgroundColor = "#e0e0e0";
userInput.style.width = "100px";
userInput.value = localStorage.getItem("specificUser") || "14790897";
document.body.appendChild(userInput);
const saveUserButton = document.createElement("button");
saveUserButton.textContent = "保存用户ID";
saveUserButton.style.position = "fixed";
saveUserButton.style.bottom = "60px";
saveUserButton.style.left = "20px";
saveUserButton.style.zIndex = "1000";
saveUserButton.style.backgroundColor = "#e0e0e0";
saveUserButton.style.color = "#333";
saveUserButton.style.border = "1px solid #aaa";
saveUserButton.style.padding = "8px 16px";
saveUserButton.style.borderRadius = "8px";
document.body.appendChild(saveUserButton);
saveUserButton.onclick = function () {
const newSpecificUser = userInput.value.trim();
if (newSpecificUser) {
localStorage.setItem("specificUser", newSpecificUser);
localStorage.removeItem("specificUserPostList");
localStorage.removeItem("lastOffset");
specificUser = newSpecificUser;
console.log(
`新的specificUser已保存: ${specificUser},specificUserPostList已重置`
);
}
};
const likeLimitInput = document.createElement("input");
likeLimitInput.type = "number";
likeLimitInput.placeholder = "输入点赞数量";
likeLimitInput.style.position = "fixed";
likeLimitInput.style.bottom = "180px";
likeLimitInput.style.left = "20px";
likeLimitInput.style.zIndex = "1000";
likeLimitInput.style.padding = "6px";
likeLimitInput.style.border = "1px solid #aaa";
likeLimitInput.style.borderRadius = "8px";
likeLimitInput.style.backgroundColor = "#e0e0e0";
likeLimitInput.style.width = "100px";
likeLimitInput.value = localStorage.getItem("likeLimit") || 200;
document.body.appendChild(likeLimitInput);
const saveLikeLimitButton = document.createElement("button");
saveLikeLimitButton.textContent = "保存点赞数量";
saveLikeLimitButton.style.position = "fixed";
saveLikeLimitButton.style.bottom = "140px";
saveLikeLimitButton.style.left = "20px";
saveLikeLimitButton.style.zIndex = "1000";
saveLikeLimitButton.style.backgroundColor = "#e0e0e0";
saveLikeLimitButton.style.color = "#333";
saveLikeLimitButton.style.border = "1px solid #aaa";
saveLikeLimitButton.style.padding = "8px 16px";
saveLikeLimitButton.style.borderRadius = "8px";
document.body.appendChild(saveLikeLimitButton);
saveLikeLimitButton.onclick = function () {
const newLikeLimit = parseInt(likeLimitInput.value.trim(), 10);
if (newLikeLimit && newLikeLimit > 0) {
localStorage.setItem("likeLimit", newLikeLimit);
likeLimit = newLikeLimit;
console.log(`新的likeLimit已保存: ${likeLimit}`);
}
};
// 增加清除数据的按钮
const clearDataButton = document.createElement("button");
clearDataButton.textContent = "清除所有数据";
clearDataButton.style.position = "fixed";
clearDataButton.style.bottom = "20px";
clearDataButton.style.left = "140px";
clearDataButton.style.zIndex = "1000";
clearDataButton.style.backgroundColor = "#ff6666"; // 红色背景,提示删除操作
clearDataButton.style.color = "#fff"; // 白色文本
clearDataButton.style.border = "1px solid #ff3333"; // 深红色边框
clearDataButton.style.padding = "8px 16px";
clearDataButton.style.borderRadius = "8px";
document.body.appendChild(clearDataButton);
clearDataButton.onclick = function () {
localStorage.removeItem("lastOffset");
localStorage.removeItem("clickCounter");
localStorage.removeItem("clickCounterTimestamp");
localStorage.removeItem("specificUserPostList");
console.log("所有数据已清除,除了 specificUser 和 specificUserPostList");
};
})();
================================================
FILE: index_likeUser_activity.js
================================================
// ==UserScript==
// @name Auto Like Specific User base on activity
// @namespace http://tampermonkey.net/
// @version 1.1.2
// @description 自动点赞特定用户,适用于discourse
// @author liuweiqing
// @match https://meta.discourse.org/*
// @match https://linux.do/*
// @match https://meta.appinn.net/*
// @match https://community.openai.com/
// @grant none
// @license MIT
// @icon https://www.google.com/s2/favicons?domain=linux.do
// @downloadURL https://update.greasyfork.org/scripts/489464/Auto%20Read.user.js
// @updateURL https://update.greasyfork.org/scripts/489464/Auto%20Read.meta.js
// @run-at document-end
// ==/UserScript==
(function () {
("use strict");
// 定义可能的基本URL
const possibleBaseURLs = [
"https://meta.discourse.org",
"https://linux.do",
"https://meta.appinn.net",
"https://community.openai.com",
];
const commentLimit = 1000;
const specificUserPostListLimit = 100;
const currentURL = window.location.href;
let specificUser = localStorage.getItem("specificUser") || "14790897";
let likeLimit = parseInt(localStorage.getItem("likeLimit") || 200, 10);
let BASE_URL = possibleBaseURLs.find((url) => currentURL.startsWith(url));
// 环境变量:阅读网址,如果没有找到匹配的URL,则默认为第一个
if (!BASE_URL) {
BASE_URL = possibleBaseURLs[0];
console.log("当前BASE_URL设置为(默认): " + BASE_URL);
} else {
console.log("当前BASE_URL是: " + BASE_URL);
}
// 获取当前时间戳
const currentTime = Date.now();
// 获取存储的时间戳
const defaultTimestamp = new Date("1999-01-01T00:00:00Z").getTime(); //默认值为1999年
const storedTime = parseInt(
localStorage.getItem("clickCounterTimestamp") ||
defaultTimestamp.toString(),
10
);
// 获取当前的点击计数,如果不存在则初始化为0
let clickCounter = parseInt(localStorage.getItem("clickCounter") || "0", 10);
// 检查是否超过12小时(12小时 = 12 * 60 * 60 * 1000 毫秒)
if (currentTime - storedTime > 12 * 60 * 60 * 1000) {
// 超过24小时,清空点击计数器并更新时间戳
clickCounter = 0;
localStorage.setItem("clickCounter", "0");
localStorage.setItem("clickCounterTimestamp", currentTime.toString());
}
console.log(`Initial clickCounter: ${clickCounter}`);
// 入口函数
window.addEventListener("load", () => {
console.log("autoRead", localStorage.getItem("read"));
checkFirstRun();
if (localStorage.getItem("read") === "true") {
console.log("点赞开始");
setTimeout(() => {
likeSpecificPost();
}, 2000);
setTimeout(() => {
openSpecificUserPost();
}, 4000);
}
});
function checkFirstRun() {
if (localStorage.getItem("isFirstRun") === null) {
console.log("脚本第一次运行,执行初始化操作...");
updateInitialData();
localStorage.setItem("isFirstRun", "false");
} else {
console.log("脚本非第一次运行");
}
}
function updateInitialData() {
localStorage.setItem("read", "false"); // 开始时自动滚动关闭
console.log("执行了初始数据更新操作");
}
function getLatestTopic() {
let lastOffset = Number(localStorage.getItem("lastOffset")) || 0;
let specificUserPostList = [];
let isDataSufficient = false;
while (!isDataSufficient) {
lastOffset += 20;
// lastOffset += 1; //对于page来说
// 举例:https://linux.do/user_actions.json?offset=0&username=14790897&filter=5
const url = `${BASE_URL}/user_actions.json?offset=${lastOffset}&username=${specificUser}&filter=5`;
//举例:https://linux.do/search?q=%4014790897%20in%3Aunseen
// const url = `${BASE_URL}/search?q=%40${specificUser}%20in%3Aunseen`; //&page=${lastOffset}
$.ajax({
url: url,
async: false,
headers: {
Accept: "application/json",
},
success: function (result) {
if (result && result.user_actions && result.user_actions.length > 0) {
result.user_actions.forEach((action) => {
// if (result && result.posts && result.posts.length > 0) {
// result.posts.forEach((action) => {
const topicId = action.topic_id;
// const postId = action.post_id;
const postNumber = action.post_number;
specificUserPostList.push({
topic_id: topicId,
// post_id: postId,
post_number: postNumber,
});
});
// 检查是否已获得足够的 Posts
if (specificUserPostList.length >= specificUserPostListLimit) {
isDataSufficient = true;
}
} else {
isDataSufficient = true; // 没有更多内容时停止请求
}
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
console.error(XMLHttpRequest, textStatus, errorThrown);
isDataSufficient = true; // 遇到错误时也停止请求
},
});
}
// 如果列表超出限制,则截断
if (specificUserPostList.length > specificUserPostListLimit) {
specificUserPostList = specificUserPostList.slice(
0,
specificUserPostListLimit
);
}
// 存储 lastOffset 和 specificUserPostList 到 localStorage
localStorage.setItem("lastOffset", lastOffset);
localStorage.setItem(
"specificUserPostList",
JSON.stringify(specificUserPostList)
);
}
function openSpecificUserPost() {
let specificUserPostListStr = localStorage.getItem("specificUserPostList");
let specificUserPostList = specificUserPostListStr
? JSON.parse(specificUserPostListStr)
: [];
// 如果列表为空,则获取最新文章
if (specificUserPostList.length === 0) {
getLatestTopic();
specificUserPostListStr = localStorage.getItem("specificUserPostList");
specificUserPostList = specificUserPostListStr
? JSON.parse(specificUserPostListStr)
: [];
}
// 如果获取到新文章,打开第一个
if (specificUserPostList.length > 0) {
const post = specificUserPostList.shift(); // 获取列表中的第一个对象
localStorage.setItem(
"specificUserPostList",
JSON.stringify(specificUserPostList)
);
window.location.href = `${BASE_URL}/t/topic/${post.topic_id}/${post.post_number}`;
} else {
console.error("未能获取到新的帖子数据。");
}
}
// 检查是否点赞
// const postId = data.post_id;
// const targetId = `discourse-reactions-counter-${postId}-right`;
// const element = document.getElementById(targetId);
function likeSpecificPost() {
const urlParts = window.location.pathname.split("/");
const lastPart = urlParts[urlParts.length - 1]; // 获取最后一部分
let buttons, reactionButton;
console.log("post number:", lastPart);
if (lastPart < 10000) {
buttons = document.querySelectorAll(
"button[aria-label]" //[class*='reply']
);
let targetButton = null;
buttons.forEach((button) => {
const ariaLabel = button.getAttribute("aria-label");
if (ariaLabel && ariaLabel.includes(`#${lastPart}`)) {
targetButton = button;
console.log("找到post number按钮:", targetButton);
return;
}
});
if (targetButton) {
// 找到按钮后,获取其父级元素
const parentElement = targetButton.parentElement;
console.log("父级元素:", parentElement);
reactionButton = parentElement.querySelector(
".discourse-reactions-reaction-button"
);
} else {
console.log(`未找到包含 #${lastPart} 的按钮`);
}
} else {
//大于10000说明是主题帖,选择第一个
reactionButton = document.querySelectorAll(
".discourse-reactions-reaction-button"
)[0];
}
if (
reactionButton.title !== "点赞此帖子" &&
reactionButton.title !== "Like this post"
) {
console.log("已经点赞过");
return "already liked";
} else if (clickCounter >= likeLimit) {
console.log("已经达到点赞上限");
localStorage.setItem("read", false);
return;
}
triggerClick(reactionButton);
clickCounter++;
console.log(
`Clicked like button ${clickCounter},已点赞用户${specificUser}`
);
localStorage.setItem("clickCounter", clickCounter.toString());
// 如果点击次数达到likeLimit次,则设置点赞变量为false
if (clickCounter === likeLimit) {
console.log(
`Reached ${likeLimit} likes, setting the like variable to false.`
);
localStorage.setItem("read", false);
} else {
console.log("clickCounter:", clickCounter);
}
}
function triggerClick(button) {
const event = new MouseEvent("click", {
bubbles: true,
cancelable: true,
view: window,
});
button.dispatchEvent(event);
}
const button = document.createElement("button");
button.textContent =
localStorage.getItem("read") === "true" ? "停止阅读" : "开始阅读";
button.style.position = "fixed";
button.style.bottom = "20px";
button.style.left = "20px";
button.style.zIndex = 1000;
button.style.backgroundColor = "#e0e0e0";
button.style.color = "#333";
button.style.border = "1px solid #aaa";
button.style.padding = "8px 16px";
button.style.borderRadius = "8px";
document.body.appendChild(button);
button.onclick = function () {
const currentlyReading = localStorage.getItem("read") === "true";
const newReadState = !currentlyReading;
localStorage.setItem("read", newReadState.toString());
button.textContent = newReadState ? "停止阅读" : "开始阅读";
if (newReadState) {
if (BASE_URL == "https://linux.do") {
const maxPostNumber = 600;
const randomPostNumber = Math.floor(Math.random() * maxPostNumber) + 1;
const newUrl = `https://linux.do/t/topic/13716/${randomPostNumber}`;
window.location.href = newUrl;
} else {
window.location.href = `${BASE_URL}/t/topic/1`;
}
}
};
const userInput = document.createElement("input");
userInput.type = "text";
userInput.placeholder = "输入要点赞的用户ID";
userInput.style.position = "fixed";
userInput.style.bottom = "90px";
userInput.style.left = "20px";
userInput.style.zIndex = "1000";
userInput.style.padding = "6px";
userInput.style.border = "1px solid #aaa";
userInput.style.borderRadius = "8px";
userInput.style.backgroundColor = "#e0e0e0";
userInput.style.width = "100px";
userInput.value = localStorage.getItem("specificUser") || "14790897";
document.body.appendChild(userInput);
const saveUserButton = document.createElement("button");
saveUserButton.textContent = "保存用户ID";
saveUserButton.style.position = "fixed";
saveUserButton.style.bottom = "60px";
saveUserButton.style.left = "20px";
saveUserButton.style.zIndex = "1000";
saveUserButton.style.backgroundColor = "#e0e0e0";
saveUserButton.style.color = "#333";
saveUserButton.style.border = "1px solid #aaa";
saveUserButton.style.padding = "8px 16px";
saveUserButton.style.borderRadius = "8px";
document.body.appendChild(saveUserButton);
saveUserButton.onclick = function () {
const newSpecificUser = userInput.value.trim();
if (newSpecificUser) {
localStorage.setItem("specificUser", newSpecificUser);
localStorage.removeItem("specificUserPostList");
localStorage.removeItem("lastOffset");
specificUser = newSpecificUser;
console.log(
`新的specificUser已保存: ${specificUser},specificUserPostList已重置`
);
}
};
const likeLimitInput = document.createElement("input");
likeLimitInput.type = "number";
likeLimitInput.placeholder = "输入点赞数量";
likeLimitInput.style.position = "fixed";
likeLimitInput.style.bottom = "180px";
likeLimitInput.style.left = "20px";
likeLimitInput.style.zIndex = "1000";
likeLimitInput.style.padding = "6px";
likeLimitInput.style.border = "1px solid #aaa";
likeLimitInput.style.borderRadius = "8px";
likeLimitInput.style.backgroundColor = "#e0e0e0";
likeLimitInput.style.width = "100px";
likeLimitInput.value = localStorage.getItem("likeLimit") || 200;
document.body.appendChild(likeLimitInput);
const saveLikeLimitButton = document.createElement("button");
saveLikeLimitButton.textContent = "保存点赞数量";
saveLikeLimitButton.style.position = "fixed";
saveLikeLimitButton.style.bottom = "140px";
saveLikeLimitButton.style.left = "20px";
saveLikeLimitButton.style.zIndex = "1000";
saveLikeLimitButton.style.backgroundColor = "#e0e0e0";
saveLikeLimitButton.style.color = "#333";
saveLikeLimitButton.style.border = "1px solid #aaa";
saveLikeLimitButton.style.padding = "8px 16px";
saveLikeLimitButton.style.borderRadius = "8px";
document.body.appendChild(saveLikeLimitButton);
saveLikeLimitButton.onclick = function () {
const newLikeLimit = parseInt(likeLimitInput.value.trim(), 10);
if (newLikeLimit && newLikeLimit > 0) {
localStorage.setItem("likeLimit", newLikeLimit);
likeLimit = newLikeLimit;
console.log(`新的likeLimit已保存: ${likeLimit}`);
}
};
// 增加清除数据的按钮
const clearDataButton = document.createElement("button");
clearDataButton.textContent = "清除所有数据";
clearDataButton.style.position = "fixed";
clearDataButton.style.bottom = "20px";
clearDataButton.style.left = "140px";
clearDataButton.style.zIndex = "1000";
clearDataButton.style.backgroundColor = "#ff6666"; // 红色背景,提示删除操作
clearDataButton.style.color = "#fff"; // 白色文本
clearDataButton.style.border = "1px solid #ff3333"; // 深红色边框
clearDataButton.style.padding = "8px 16px";
clearDataButton.style.borderRadius = "8px";
document.body.appendChild(clearDataButton);
clearDataButton.onclick = function () {
localStorage.removeItem("lastOffset");
localStorage.removeItem("clickCounter");
localStorage.removeItem("clickCounterTimestamp");
localStorage.removeItem("specificUserPostList");
console.log("所有数据已清除,除了 specificUser 和 specificUserPostList");
};
})();
================================================
FILE: index_likeUser_random.js
================================================
// 那个activity和普通的是不能通用的
// // ==UserScript==
// // @name Auto Like Specific User base on activity
// // @namespace http://tampermonkey.net/
// // @version 1.1.2
// // @description 自动点赞特定用户,适用于discourse
// // @author liuweiqing
// // @match https://meta.discourse.org/*
// // @match https://linux.do/*
// // @match https://meta.appinn.net/*
// // @match https://community.openai.com/
// // @grant none
// // @license MIT
// // @icon https://www.google.com/s2/favicons?domain=linux.do
// // @downloadURL https://update.greasyfork.org/scripts/489464/Auto%20Read.user.js
// // @updateURL https://update.greasyfork.org/scripts/489464/Auto%20Read.meta.js
// // @run-at document-end
// // ==/UserScript==
// (function () {
// ("use strict");
// // 定义可能的基本URL
// const possibleBaseURLs = [
// "https://linux.do",
// "https://meta.discourse.org",
// "https://meta.appinn.net",
// "https://community.openai.com",
// ];
// const commentLimit = 1000;
// const specificUserPostListLimit = 100;
// const currentURL = window.location.href;
// let specificUser = localStorage.getItem("specificUser") || "14790897";
// let likeLimit = parseInt(localStorage.getItem("likeLimit") || 200, 10);
// let BASE_URL = possibleBaseURLs.find((url) => currentURL.startsWith(url));
// // 环境变量:阅读网址,如果没有找到匹配的URL,则默认为第一个
// if (!BASE_URL) {
// BASE_URL = possibleBaseURLs[0];
// console.log("当前BASE_URL设置为(默认): " + BASE_URL);
// } else {
// console.log("当前BASE_URL是: " + BASE_URL);
// }
// // 获取当前时间戳
// const currentTime = Date.now();
// // 获取存储的时间戳
// const defaultTimestamp = new Date("1999-01-01T00:00:00Z").getTime(); //默认值为1999年
// const storedTime = parseInt(
// localStorage.getItem("clickCounterTimestamp") ||
// defaultTimestamp.toString(),
// 10
// );
// // 获取当前的点击计数,如果不存在则初始化为0
// let clickCounter = parseInt(localStorage.getItem("clickCounter") || "0", 10);
// // 检查是否超过12小时(12小时 = 12 * 60 * 60 * 1000 毫秒)
// if (currentTime - storedTime > 12 * 60 * 60 * 1000) {
// // 超过24小时,清空点击计数器并更新时间戳
// clickCounter = 0;
// localStorage.setItem("clickCounter", "0");
// localStorage.setItem("clickCounterTimestamp", currentTime.toString());
// }
// console.log(`Initial clickCounter: ${clickCounter}`);
// // 入口函数
// window.addEventListener("load", () => {
// console.log("autoRead", localStorage.getItem("read"));
// checkFirstRun();
// if (localStorage.getItem("read") === "true") {
// console.log("点赞开始");
// setTimeout(() => {
// likeSpecificPost();
// }, 2000);
// setTimeout(() => {
// openSpecificUserPost();
// }, 4000);
// }
// });
// function checkFirstRun() {
// if (localStorage.getItem("isFirstRun") === null) {
// console.log("脚本第一次运行,执行初始化操作...");
// updateInitialData();
// localStorage.setItem("isFirstRun", "false");
// } else {
// console.log("脚本非第一次运行");
// }
// }
// function updateInitialData() {
// localStorage.setItem("read", "false"); // 开始时自动滚动关闭
// console.log("执行了初始数据更新操作");
// }
// async function getLatestTopic() {
// let lastOffset = Number(localStorage.getItem("lastOffset")) || 0;
// const specificUserPostList = [];
// let isDataSufficient = false;
// while (!isDataSufficient) {
// const randomChoice = Math.random() < 0.5; // 生成一个随机数,50% 概率为 true
// let url;
// if (randomChoice) {
// // 使用第一个链接
// url = `${BASE_URL}/user_actions.json?offset=${lastOffset}&username=${specificUser}&filter=5`;
// console.log("使用第一个链接:", url);
// } else {
// // 使用第二个链接
// url = `${BASE_URL}/search?q=%40${specificUser}%20in%3Aunseen`; // &page=${lastOffset}
// console.log("使用第二个链接:", url);
// }
// try {
// const response = await fetch(url, {
// headers: {
// Accept: "application/json",
// },
// });
// if (!response.ok) {
// throw new Error(`请求失败,状态码: ${response.status}`);
// }
// const result = await response.json();
// if (result && result.user_actions && result.user_actions.length > 0) {
// result.user_actions.forEach((action) => {
// const topicId = action.topic_id;
// const postNumber = action.post_number;
// specificUserPostList.push({
// topic_id: topicId,
// post_number: postNumber,
// });
// });
// // 检查是否已获得足够的 Posts
// if (specificUserPostList.length >= specificUserPostListLimit) {
// isDataSufficient = true;
// }
// } else {
// isDataSufficient = true; // 没有更多内容时停止请求
// }
// } catch (error) {
// console.error("请求出错:", error);
// isDataSufficient = true; // 遇到错误时也停止请求
// }
// }
// // 根据返回的数据长度调整 lastOffset
// lastOffset += result.user_actions.length;
// // 如果列表超出限制,则截断
// if (specificUserPostList.length > specificUserPostListLimit) {
// specificUserPostList = specificUserPostList.slice(
// 0,
// specificUserPostListLimit
// );
// }
// // 存储 lastOffset 和 specificUserPostList 到 localStorage
// localStorage.setItem("lastOffset", lastOffset);
// localStorage.setItem(
// "specificUserPostList",
// JSON.stringify(specificUserPostList)
// );
// }
// function openSpecificUserPost() {
// let specificUserPostListStr = localStorage.getItem("specificUserPostList");
// let specificUserPostList = specificUserPostListStr
// ? JSON.parse(specificUserPostListStr)
// : [];
// // 如果列表为空,则获取最新文章
// if (specificUserPostList.length === 0) {
// getLatestTopic();
// specificUserPostListStr = localStorage.getItem("specificUserPostList");
// specificUserPostList = specificUserPostListStr
// ? JSON.parse(specificUserPostListStr)
// : [];
// }
// // 如果获取到新文章,打开第一个
// if (specificUserPostList.length > 0) {
// const post = specificUserPostList.shift(); // 获取列表中的第一个对象
// localStorage.setItem(
// "specificUserPostList",
// JSON.stringify(specificUserPostList)
// );
// window.location.href = `${BASE_URL}/t/topic/${post.topic_id}/${post.post_number}`;
// } else {
// console.error("未能获取到新的帖子数据。");
// }
// }
// // 检查是否点赞
// // const postId = data.post_id;
// // const targetId = `discourse-reactions-counter-${postId}-right`;
// // const element = document.getElementById(targetId);
// function likeSpecificPost() {
// const urlParts = window.location.pathname.split("/");
// const lastPart = urlParts[urlParts.length - 1]; // 获取最后一部分
// let buttons, reactionButton;
// console.log("post number:", lastPart);
// if (lastPart < 10000) {
// buttons = document.querySelectorAll(
// "button[aria-label]" //[class*='reply']
// );
// let targetButton = null;
// buttons.forEach((button) => {
// const ariaLabel = button.getAttribute("aria-label");
// if (ariaLabel && ariaLabel.includes(`#${lastPart}`)) {
// targetButton = button;
// console.log("找到post number按钮:", targetButton);
// return;
// }
// });
// if (targetButton) {
// // 找到按钮后,获取其父级元素
// const parentElement = targetButton.parentElement;
// console.log("父级元素:", parentElement);
// reactionButton = parentElement.querySelector(
// ".discourse-reactions-reaction-button"
// );
// } else {
// console.log(`未找到包含 #${lastPart} 的按钮`);
// }
// } else {
// //大于10000说明是主题帖,选择第一个
// reactionButton = document.querySelectorAll(
// ".discourse-reactions-reaction-button"
// )[0];
// }
// if (
// reactionButton.title !== "点赞此帖子" &&
// reactionButton.title !== "Like this post"
// ) {
// console.log("已经点赞过");
// return "already liked";
// } else if (clickCounter >= likeLimit) {
// console.log("已经达到点赞上限");
// localStorage.setItem("read", false);
// return;
// }
// triggerClick(reactionButton);
// clickCounter++;
// console.log(
// `Clicked like button ${clickCounter},已点赞用户${specificUser}`
// );
// localStorage.setItem("clickCounter", clickCounter.toString());
// // 如果点击次数达到likeLimit次,则设置点赞变量为false
// if (clickCounter === likeLimit) {
// console.log(
// `Reached ${likeLimit} likes, setting the like variable to false.`
// );
// localStorage.setItem("read", false);
// } else {
// console.log("clickCounter:", clickCounter);
// }
// }
// function triggerClick(button) {
// const event = new MouseEvent("click", {
// bubbles: true,
// cancelable: true,
// view: window,
// });
// button.dispatchEvent(event);
// }
// const button = document.createElement("button");
// button.textContent =
// localStorage.getItem("read") === "true" ? "停止阅读" : "开始阅读";
// button.style.position = "fixed";
// button.style.bottom = "20px";
// button.style.left = "20px";
// button.style.zIndex = 1000;
// button.style.backgroundColor = "#e0e0e0";
// button.style.color = "#333";
// button.style.border = "1px solid #aaa";
// button.style.padding = "8px 16px";
// button.style.borderRadius = "8px";
// document.body.appendChild(button);
// button.onclick = function () {
// const currentlyReading = localStorage.getItem("read") === "true";
// const newReadState = !currentlyReading;
// localStorage.setItem("read", newReadState.toString());
// button.textContent = newReadState ? "停止阅读" : "开始阅读";
// if (newReadState) {
// if (BASE_URL == "https://linux.do") {
// const maxPostNumber = 600;
// const randomPostNumber = Math.floor(Math.random() * maxPostNumber) + 1;
// const newUrl = `https://linux.do/t/topic/13716/${randomPostNumber}`;
// window.location.href = newUrl;
// } else {
// window.location.href = `${BASE_URL}/t/topic/1`;
// }
// }
// };
// const userInput = document.createElement("input");
// userInput.type = "text";
// userInput.placeholder = "输入要点赞的用户ID";
// userInput.style.position = "fixed";
// userInput.style.bottom = "90px";
// userInput.style.left = "20px";
// userInput.style.zIndex = "1000";
// userInput.style.padding = "6px";
// userInput.style.border = "1px solid #aaa";
// userInput.style.borderRadius = "8px";
// userInput.style.backgroundColor = "#e0e0e0";
// userInput.style.width = "100px";
// userInput.value = localStorage.getItem("specificUser") || "14790897";
// document.body.appendChild(userInput);
// const saveUserButton = document.createElement("button");
// saveUserButton.textContent = "保存用户ID";
// saveUserButton.style.position = "fixed";
// saveUserButton.style.bottom = "60px";
// saveUserButton.style.left = "20px";
// saveUserButton.style.zIndex = "1000";
// saveUserButton.style.backgroundColor = "#e0e0e0";
// saveUserButton.style.color = "#333";
// saveUserButton.style.border = "1px solid #aaa";
// saveUserButton.style.padding = "8px 16px";
// saveUserButton.style.borderRadius = "8px";
// document.body.appendChild(saveUserButton);
// saveUserButton.onclick = function () {
// const newSpecificUser = userInput.value.trim();
// if (newSpecificUser) {
// localStorage.setItem("specificUser", newSpecificUser);
// localStorage.removeItem("specificUserPostList");
// localStorage.removeItem("lastOffset");
// specificUser = newSpecificUser;
// console.log(
// `新的specificUser已保存: ${specificUser},specificUserPostList已重置`
// );
// }
// };
// const likeLimitInput = document.createElement("input");
// likeLimitInput.type = "number";
// likeLimitInput.placeholder = "输入点赞数量";
// likeLimitInput.style.position = "fixed";
// likeLimitInput.style.bottom = "180px";
// likeLimitInput.style.left = "20px";
// l
gitextract_34ilwb11/ ├── .dockerignore ├── .gitattributes ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report_template.md │ │ ├── config.yml │ │ └── feature_request_template.md │ ├── PULL_REQUEST_TEMPLATE.md │ ├── deploy-to-hf-spaces.yml │ └── workflows/ │ ├── IssueManagementAutomation.yml │ ├── bad-pr.yml │ ├── cron_bypassCF.yaml │ ├── cron_bypassCF_idc.yaml │ ├── cron_bypassCF_likeUser.yaml │ ├── cron_read.yaml │ ├── db_test.yaml │ ├── docker.yml │ ├── release-please.yml │ ├── sync.yml │ ├── update-cron.yml │ ├── validate-issue-template.yml │ └── windows_cron_bypassCF.yaml ├── .gitignore ├── CHANGELOG.md ├── Dockerfile ├── Dockerfile-like-user ├── GITHUB_SECRETS_GUIDE.md ├── LICENSE ├── PROXY_GUIDE.md ├── README.md ├── README_en.md ├── bypasscf.js ├── bypasscf_likeUser_not_use.js ├── bypasscf_playwright.mjs ├── cron.log ├── cron.sh ├── debug_db.js ├── debug_rss.js ├── docker-compose-like-user.yml ├── docker-compose.yml ├── external.js ├── index.js ├── index_likeUser.js ├── index_likeUser_activity.js ├── index_likeUser_random.js ├── index_passage_list_old_not_use.js ├── invite_codecow.js ├── package.json ├── parsed_rss_data.json ├── pass_cf.py ├── postgresql/ │ └── root.crt ├── pteer.js ├── src/ │ ├── browser_retry.js │ ├── db.js │ ├── db.test.js │ ├── format_for_telegram.js │ ├── parse_rss.js │ ├── proxy_config.js │ └── topic_data.js ├── telegram_message.txt ├── test_linuxdo.js ├── test_multi_db.js ├── test_proxy.js ├── test_topic_data.js └── 随笔.md
SYMBOL INDEX (109 symbols across 22 files)
FILE: bypasscf.js
function maskUsername (line 97) | function maskUsername(username) {
function tgSendWithRetry (line 140) | async function tgSendWithRetry(id, message, maxRetries = 3) {
function sendToTelegram (line 159) | async function sendToTelegram(message) {
function sendToTelegramGroup (line 174) | async function sendToTelegramGroup(message) {
function delayClick (line 214) | function delayClick(time) {
function parseCookieString (line 297) | function parseCookieString(cookieStr, domain) {
function launchBrowserForUser (line 310) | async function launchBrowserForUser(username, password, cookie = null) {
function login (line 584) | async function login(page, username, password, retryCount = 3) {
function navigatePage (line 690) | async function navigatePage(url, page, browser) {
function takeScreenshots (line 722) | async function takeScreenshots(page) {
constant HEALTH_PORT (line 750) | const HEALTH_PORT = process.env.HEALTH_PORT || 7860;
FILE: bypasscf_likeUser_not_use.js
function sendToTelegram (line 64) | function sendToTelegram(message) {
function delayClick (line 78) | function delayClick(time) {
function launchBrowserForUser (line 149) | async function launchBrowserForUser(username, password) {
function login (line 286) | async function login(page, username, password, retryCount = 3) {
function navigatePage (line 385) | async function navigatePage(url, page, browser) {
function takeScreenshots (line 410) | async function takeScreenshots(page) {
constant HEALTH_PORT (line 438) | const HEALTH_PORT = process.env.HEALTH_PORT || 7860;
FILE: bypasscf_playwright.mjs
function launchBrowserForUser (line 149) | async function launchBrowserForUser(username, password, instanceDelay) {
function resolveBusinessScript (line 221) | function resolveBusinessScript() {
function navigatePage (line 232) | async function navigatePage(url, page) {
function login (line 246) | async function login(page, username, password, retry = 3) {
constant HEALTH_PORT (line 278) | const HEALTH_PORT = process.env.HEALTH_PORT || 7860;
FILE: debug_db.js
function debugDatabase (line 22) | async function debugDatabase() {
FILE: debug_rss.js
function debugRssAndGuids (line 14) | async function debugRssAndGuids() {
FILE: external.js
function checkFirstRun (line 45) | function checkFirstRun() {
function updateInitialData (line 60) | function updateInitialData() {
function scrollToBottomSlowly (line 70) | function scrollToBottomSlowly(
function navigateToNextTopic (line 97) | function navigateToNextTopic() {
function checkScroll (line 117) | function checkScroll() {
function searchLinkClick (line 173) | function searchLinkClick() {
function triggerClick (line 255) | function triggerClick(button) {
function autoLike (line 264) | function autoLike() {
function isAutoLikeEnabled (line 374) | function isAutoLikeEnabled() {
function setAutoLikeEnabled (line 380) | function setAutoLikeEnabled(enabled) {
FILE: index.js
function checkFirstRun (line 51) | function checkFirstRun() {
function updateInitialData (line 62) | function updateInitialData() {
function scrollToBottomSlowly (line 72) | function scrollToBottomSlowly(distancePerStep = 20, delayPerStep = 50) {
function getLatestTopic (line 81) | function getLatestTopic() {
function openNewTopic (line 131) | function openNewTopic() {
function checkScroll (line 155) | function checkScroll() {
function triggerClick (line 212) | function triggerClick(button) {
function autoLike (line 221) | function autoLike() {
function isAutoLikeEnabled (line 340) | function isAutoLikeEnabled() {
function setAutoLikeEnabled (line 346) | function setAutoLikeEnabled(enabled) {
FILE: index_likeUser.js
function checkFirstRun (line 77) | function checkFirstRun() {
function updateInitialData (line 87) | function updateInitialData() {
function getLatestTopic (line 92) | function getLatestTopic() {
function openSpecificUserPost (line 156) | function openSpecificUserPost() {
function likeSpecificPost (line 191) | function likeSpecificPost() {
function triggerClick (line 259) | function triggerClick(button) {
FILE: index_likeUser_activity.js
function checkFirstRun (line 77) | function checkFirstRun() {
function updateInitialData (line 87) | function updateInitialData() {
function getLatestTopic (line 92) | function getLatestTopic() {
function openSpecificUserPost (line 156) | function openSpecificUserPost() {
function likeSpecificPost (line 191) | function likeSpecificPost() {
function triggerClick (line 255) | function triggerClick(button) {
FILE: index_passage_list_old_not_use.js
function checkFirstRun (line 45) | function checkFirstRun() {
function updateInitialData (line 60) | function updateInitialData() {
function scrollToBottomSlowly (line 70) | function scrollToBottomSlowly(
function navigateToNextTopic (line 97) | function navigateToNextTopic() {
function checkScroll (line 117) | function checkScroll() {
function searchLinkClick (line 173) | function searchLinkClick() {
function triggerClick (line 235) | function triggerClick(button) {
function autoLike (line 244) | function autoLike() {
function isAutoLikeEnabled (line 354) | function isAutoLikeEnabled() {
function setAutoLikeEnabled (line 360) | function setAutoLikeEnabled(enabled) {
FILE: invite_codecow.js
function getExpiresAt (line 63) | async function getExpiresAt() {
function fetchInvite (line 81) | async function fetchInvite() {
function fetchInvites (line 112) | async function fetchInvites() {
function uploadInvites (line 142) | async function uploadInvites(inviteLinks) {
FILE: pass_cf.py
class SessionManager (line 15) | class SessionManager:
method __init__ (line 16) | def __init__(self, refresh_interval=3600): # 默认每小时刷新
method _create_session (line 23) | def _create_session(self):
method get_session (line 29) | def get_session(self):
function heartbeat_generator (line 52) | def heartbeat_generator(response_queue, stop_event):
function proxy (line 60) | def proxy(path):
function handle_normal_request (line 115) | def handle_normal_request(method, target_url, headers, cookies):
function handle_sse_request (line 180) | def handle_sse_request(method, target_url, headers, cookies):
FILE: pteer.js
function delayClick (line 27) | function delayClick(time) {
function launchBrowserForUser (line 60) | async function launchBrowserForUser(username, password) {
function login (line 216) | async function login(page, username, password) {
function bypassDetection (line 255) | async function bypassDetection(page) {
FILE: src/db.js
function formatErrorInfo (line 12) | function formatErrorInfo(error) {
function initMySQL (line 70) | async function initMySQL() {
function initMongoDB (line 94) | async function initMongoDB() {
function getAllDatabases (line 124) | async function getAllDatabases() {
function savePosts (line 138) | async function savePosts(posts) {
function isGuidExists (line 317) | async function isGuidExists(guid) {
function testAllConnections (line 385) | async function testAllConnections() {
function getAllDatabaseStats (line 421) | async function getAllDatabaseStats() {
function closeAllConnections (line 496) | async function closeAllConnections() {
function saveTopicData (line 528) | async function saveTopicData(topicData) {
FILE: src/db.test.js
function runDbTest (line 3) | async function runDbTest() {
FILE: src/format_for_telegram.js
function stripHtml (line 7) | function stripHtml(html) {
FILE: src/parse_rss.js
function cleanDescription (line 5) | function cleanDescription(desc) {
function parseRss (line 22) | async function parseRss(xmlData) {
FILE: src/proxy_config.js
function parseProxyUrl (line 19) | function parseProxyUrl(proxyUrl) {
function getProxyConfig (line 48) | function getProxyConfig() {
function getPuppeteerProxyArgs (line 87) | function getPuppeteerProxyArgs(proxyConfig) {
function getPlaywrightProxyConfig (line 106) | function getPlaywrightProxyConfig(proxyConfig) {
function testProxyConnection (line 126) | async function testProxyConnection(proxyConfig) {
function getCurrentIP (line 169) | async function getCurrentIP() {
FILE: src/topic_data.js
function extractTopicIdFromUrl (line 5) | function extractTopicIdFromUrl(url) {
function fetchTopicJson (line 11) | async function fetchTopicJson(page, topicId) {
function processAndSaveTopicData (line 56) | async function processAndSaveTopicData(page, url) {
FILE: test_multi_db.js
function testMultiDatabase (line 10) | async function testMultiDatabase() {
FILE: test_proxy.js
function main (line 18) | async function main() {
FILE: test_topic_data.js
function testTopicData (line 14) | async function testTopicData() {
Condensed preview — 63 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (347K chars).
[
{
"path": ".dockerignore",
"chars": 5,
"preview": ".env\n"
},
{
"path": ".gitattributes",
"chars": 66,
"preview": "# Auto detect text files and perform LF normalization\n* text=auto\n"
},
{
"path": ".github/ISSUE_TEMPLATE/bug_report_template.md",
"chars": 339,
"preview": "---\nname: \"🐛 Bug Report\"\nabout: 报告一个功能或错误的问题。\ntitle: \"[Bug] 请简要描述问题\"\nlabels: bug\nassignees: ''\n---\n\n<!-- ⚠️ 请确保填写所有必须字段以"
},
{
"path": ".github/ISSUE_TEMPLATE/config.yml",
"chars": 145,
"preview": "blank_issues_enabled: false\ncontact_links:\n - name: 📖 项目文档\n url: https://github.com/14790897/auto-read-liunxdo#readm"
},
{
"path": ".github/ISSUE_TEMPLATE/feature_request_template.md",
"chars": 214,
"preview": "---\nname: \"✨ Feature Request\"\nabout: 提出一个功能请求。\ntitle: \"[Feature] 请简要描述功能请求\"\nlabels: enhancement\nassignees: ''\n---\n\n### 背"
},
{
"path": ".github/PULL_REQUEST_TEMPLATE.md",
"chars": 544,
"preview": "<!--\n Thanks for creating a Pull Request! -->\n\n<!--\n Choose one of the following by uncommenting it:\n-->\n\n<!-- This is"
},
{
"path": ".github/deploy-to-hf-spaces.yml",
"chars": 1885,
"preview": "name: Deploy to HuggingFace Spaces\n\non:\n push:\n branches:\n - dev\n - main\n paths-ignore:\n - '.githu"
},
{
"path": ".github/workflows/IssueManagementAutomation.yml",
"chars": 713,
"preview": "name: IssueManagementAutomation(勿动)\n\non:\n # schedule:\n # - cron: \"0 1-9/3 * * *\" #\"0 18 * * *\" \"0 */6 * * *\"\n wo"
},
{
"path": ".github/workflows/bad-pr.yml",
"chars": 1349,
"preview": "name: Cleanup bad PR\n\non:\n pull_request_target:\n types: [opened, reopened]\n\npermissions:\n contents: read\n\njobs:\n c"
},
{
"path": ".github/workflows/cron_bypassCF.yaml",
"chars": 2385,
"preview": "name: readLike(自动阅读随机点赞)\n# GitHub.secrets优先级最高,即使没有设置对应的变量,它也会读取,这时变量为空值,导致报错,.env读取的变量无法覆盖这个值,使用了${PASSWORD_ESCAPED//\\#"
},
{
"path": ".github/workflows/cron_bypassCF_idc.yaml",
"chars": 2435,
"preview": "name: readLike(需手动取消schedule注释)(自动阅读idc随机点赞)\n#只是固定了WEBSITE变量为idcflare.com\n\n# GitHub.secrets优先级最高,即使没有设置对应的变量,它也会读取,这时变量为"
},
{
"path": ".github/workflows/cron_bypassCF_likeUser.yaml",
"chars": 3546,
"preview": "name: likeUser (点赞特定用户)\n# GitHub.secrets优先级最高,即使没有设置对应的变量,它也会读取,这时变量为空值,导致报错,.env读取的变量无法覆盖这个值,使用了${PASSWORD_ESCAPED//\\#/"
},
{
"path": ".github/workflows/cron_read.yaml",
"chars": 831,
"preview": "name: read cron (勿动)\n\non:\n # schedule:\n # # 每天 UTC 时间 18:00 运行\n # - cron: \"0 18 * * *\"\n workflow_dispatch: # 添加这"
},
{
"path": ".github/workflows/db_test.yaml",
"chars": 941,
"preview": "name: db-test\non:\n push:\n branches:\n - '**'\n workflow_dispatch:\njobs:\n db-test:\n if: github.repository == "
},
{
"path": ".github/workflows/docker.yml",
"chars": 1000,
"preview": "name: AutoRead Docker Image Push (勿动)\n\non:\n push:\n branches:\n - main\n paths-ignore:\n - '.github/workflo"
},
{
"path": ".github/workflows/release-please.yml",
"chars": 431,
"preview": "name: Release Please(勿动)\non:\n push:\n branches:\n - main\n paths-ignore:\n - '.github/workflows/cron_*.yaml"
},
{
"path": ".github/workflows/sync.yml",
"chars": 1535,
"preview": "name: Upstream Sync\n\npermissions:\n contents: write\n\non:\n schedule:\n - cron: '0 0 * * *' # every day\n workflow_disp"
},
{
"path": ".github/workflows/update-cron.yml",
"chars": 1950,
"preview": "name: Update Workflow Cron\n\non:\n # schedule:\n # - cron: '0 16 * * *' \n workflow_dispatch: # 允许手动触发\n# push:\n# "
},
{
"path": ".github/workflows/validate-issue-template.yml",
"chars": 4185,
"preview": "name: Issue Template Validator\n\non:\n issues:\n types: [opened, edited]\n\npermissions:\n issues: write\n\njobs:\n validat"
},
{
"path": ".github/workflows/windows_cron_bypassCF.yaml",
"chars": 1996,
"preview": "name: readLike 小众论坛(勿动)\n# GitHub.secrets优先级最高,即使没有设置对应的变量,它也会读取,这时变量为空值,导致报错,.env读取的变量无法覆盖这个值,使用了${PASSWORD_ESCAPED//\\#/"
},
{
"path": ".gitignore",
"chars": 42,
"preview": "node_modules/\n.env.local*\nscreenshots\n.env"
},
{
"path": "CHANGELOG.md",
"chars": 29828,
"preview": "# Changelog\n\n### [1.15.1](https://www.github.com/14790897/auto-read-liunxdo/compare/v1.15.0...v1.15.1) (2026-02-11)\n\n\n##"
},
{
"path": "Dockerfile",
"chars": 1798,
"preview": "FROM node:22-slim\n\nWORKDIR /app\n\nCOPY package*.json ./\n\nRUN apt update && apt install -y \\\n cron \\\n wget \\\n gnu"
},
{
"path": "Dockerfile-like-user",
"chars": 1081,
"preview": "# 使用官方 Node.js 作为父镜像\nFROM node:22-slim\n\n# 设置工作目录\nWORKDIR /app\n\n# 复制 package.json 和 package-lock.json (如果存在)\nCOPY package"
},
{
"path": "GITHUB_SECRETS_GUIDE.md",
"chars": 1670,
"preview": "\n# ============================= 环境变量文档 =============================\n# \n# 在 GitHub 仓库的 Settings -> Secrets and variable"
},
{
"path": "LICENSE",
"chars": 1067,
"preview": "MIT License\n\nCopyright (c) 2024 liuweiqing\n\nPermission is hereby granted, free of charge, to any person obtaining a copy"
},
{
"path": "PROXY_GUIDE.md",
"chars": 2918,
"preview": "# 🚀 自动阅读 Linux.do 代理配置指南\n\n## 📖 概述\n\n本项目支持多种代理协议,帮助您绕过网络限制,提高访问稳定性。\n\n## 🛠️ 支持的代理类型\n\n- **HTTP 代理**: 最常见的代理类型\n- **HTTPS 代理**"
},
{
"path": "README.md",
"chars": 5867,
"preview": "[英文文档](./README_en.md)\n\n## 注意事项\n\n1. 不显示脚本运行日志,只有登录结果\n2. 阅读量统计有延迟,建议看点赞记录\n\n## 彩蛋\n\nhttps://t.me/linuxdoSQL\n每天随机抓取帖子发布在此频道\n"
},
{
"path": "README_en.md",
"chars": 2804,
"preview": " [中文文档](./README_zh.md)\n\n## Method 1: Tampermonkey Script\n\nThe Tampermonkey script can be accessed in the `index_passage"
},
{
"path": "bypasscf.js",
"chars": 27871,
"preview": "import fs from \"fs\";\nimport path from \"path\";\nimport puppeteer from \"puppeteer-extra\";\nimport StealthPlugin from \"puppet"
},
{
"path": "bypasscf_likeUser_not_use.js",
"chars": 16288,
"preview": "import fs from \"fs\";\nimport path from \"path\";\nimport puppeteer from \"puppeteer-extra\";\nimport StealthPlugin from \"puppet"
},
{
"path": "bypasscf_playwright.mjs",
"chars": 10349,
"preview": "/*\n Auto Read App (Playwright 版本)\n ---------------------------------\n 将原 Puppeteer + puppeteer-extra + puppeteer-real"
},
{
"path": "cron.log",
"chars": 0,
"preview": ""
},
{
"path": "cron.sh",
"chars": 476,
"preview": "#!/bin/bash\n#设置为中文\nexport LANG=zh_CN.UTF-8\nexport LC_ALL=zh_CN.UTF-8\n# 获取当前工作目录\nWORKDIR=$(dirname $(readlink -f $0))\n\n# "
},
{
"path": "debug_db.js",
"chars": 1624,
"preview": "import { Pool } from 'pg';\nimport fs from 'fs';\nimport dotenv from 'dotenv';\n\ndotenv.config();\nif (fs.existsSync(\".env.l"
},
{
"path": "debug_rss.js",
"chars": 1724,
"preview": "import { parseRss } from './src/parse_rss.js';\nimport { isGuidExists } from './src/db.js';\nimport fs from 'fs';\nimport d"
},
{
"path": "docker-compose-like-user.yml",
"chars": 193,
"preview": "services:\n autolikeuser:\n image: 14790897/auto-like-user:latest\n container_name: auto-like-user\n env_file:\n "
},
{
"path": "docker-compose.yml",
"chars": 303,
"preview": "services:\n autoread:\n image: 14790897/auto-read:latest\n container_name: auto-read\n env_file:\n - path: ./."
},
{
"path": "external.js",
"chars": 11646,
"preview": "// ==UserScript==\n// @name Auto Read\n// @namespace http://tampermonkey.net/\n// @version 1.3.1\n// @descri"
},
{
"path": "index.js",
"chars": 10937,
"preview": "// ==UserScript==\n// @name Auto Read\n// @namespace http://tampermonkey.net/\n// @version 1.4.6\n// @descri"
},
{
"path": "index_likeUser.js",
"chars": 13644,
"preview": "// ==UserScript==\n// @name Auto Like Specific User\n// @namespace http://tampermonkey.net/\n// @version 1."
},
{
"path": "index_likeUser_activity.js",
"chars": 13588,
"preview": "// ==UserScript==\n// @name Auto Like Specific User base on activity\n// @namespace http://tampermonkey.net/\n//"
},
{
"path": "index_likeUser_random.js",
"chars": 14493,
"preview": "// 那个activity和普通的是不能通用的\n// // ==UserScript==\n// // @name Auto Like Specific User base on activity\n// // @namespa"
},
{
"path": "index_passage_list_old_not_use.js",
"chars": 11080,
"preview": "// ==UserScript==\n// @name Auto Read\n// @namespace http://tampermonkey.net/\n// @version 1.3.2\n// @descri"
},
{
"path": "invite_codecow.js",
"chars": 5456,
"preview": "// ==UserScript==\n// @name 自动上传邀请码\n// @namespace https://linux.do\n// @version 0.0.8\n// @description 直接上"
},
{
"path": "package.json",
"chars": 676,
"preview": "{\n \"type\": \"module\",\n \"dependencies\": {\n \"@playwright/test\": \"^1.56.1\",\n \"dotenv\": \"^16.4.5\",\n \"express\": \"^4"
},
{
"path": "parsed_rss_data.json",
"chars": 13231,
"preview": "{\n \"channelTitle\": \"长期在线回收iPhone\",\n \"channelLink\": \"https://linux.do/t/topic/525305\",\n \"channelDescription\": \"各位佬有二手i"
},
{
"path": "pass_cf.py",
"chars": 9807,
"preview": "# 参考:https://linux.do/t/topic/817360\n#!/usr/bin/python3\nfrom flask import Flask, request, Response, stream_with_context\n"
},
{
"path": "postgresql/root.crt",
"chars": 2728,
"preview": "-----BEGIN CERTIFICATE-----\nMIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw\nTzELMAkGA1UEBhMCVVMxKTAnBgN"
},
{
"path": "pteer.js",
"chars": 8888,
"preview": "import fs from \"fs\";\nimport path from \"path\";\nimport puppeteer from \"puppeteer-extra\";\nimport StealthPlugin from \"puppet"
},
{
"path": "src/browser_retry.js",
"chars": 0,
"preview": ""
},
{
"path": "src/db.js",
"chars": 20183,
"preview": "// 多数据库工具 (PostgreSQL + MongoDB + MySQL)\nimport fs from \"fs\";\n\nimport pkg from \"pg\";\nconst { Pool } = pkg;\nimport { Mong"
},
{
"path": "src/db.test.js",
"chars": 797,
"preview": "import { savePosts, isGuidExists } from \"./db.js\";\n\nasync function runDbTest() {\n const testGuid = \"test-guid-\" + Date."
},
{
"path": "src/format_for_telegram.js",
"chars": 2101,
"preview": "import fs from 'fs';\n\nconst inputFilePath = 'parsed_rss_data.json';\nconst outputFilePath = 'telegram_message.txt';\n\n// F"
},
{
"path": "src/parse_rss.js",
"chars": 2994,
"preview": "import xml2js from \"xml2js\";\nimport { savePosts, isGuidExists } from \"./db.js\";\n\n// 生成适合Telegram的文本内容,去掉“阅读完整话题”和“Read f"
},
{
"path": "src/proxy_config.js",
"chars": 4771,
"preview": "/**\n * 代理配置模块\n * 支持HTTP、HTTPS、SOCKS代理\n */\n\n// 代理配置类型枚举\nexport const ProxyTypes = {\n HTTP: 'http',\n HTTPS: 'https', \n "
},
{
"path": "src/topic_data.js",
"chars": 1845,
"preview": "// 话题数据获取和保存模块\nimport { saveTopicData } from './db.js';\n\n// 从话题页面提取话题ID\nexport function extractTopicIdFromUrl(url) {\n c"
},
{
"path": "telegram_message.txt",
"chars": 3142,
"preview": "*New Posts from \"长期在线回收iPhone\"*\nhttps://linux.do/t/topic/525305\n\nChannel Description:\n各位佬有二手iPhone想出手的,可以来我这里问问价。 专业二手iP"
},
{
"path": "test_linuxdo.js",
"chars": 872,
"preview": "import { connect } from \"puppeteer-real-browser\";\n\nwhile (true) {\n console.log(\"Start of test.js\");\n const { page, bro"
},
{
"path": "test_multi_db.js",
"chars": 2215,
"preview": "// 多数据库功能测试脚本 (PostgreSQL + MongoDB + MySQL)\nimport {\n testAllConnections,\n getAllDatabaseStats,\n savePosts,\n isGuid"
},
{
"path": "test_proxy.js",
"chars": 2761,
"preview": "#!/usr/bin/env node\n/**\n * 代理测试工具\n * 用于测试代理服务器的连通性和配置正确性\n */\n\nimport dotenv from \"dotenv\";\nimport {\n getProxyConfig,\n "
},
{
"path": "test_topic_data.js",
"chars": 1329,
"preview": "import { extractTopicIdFromUrl, processAndSaveTopicData } from './src/topic_data.js';\nimport fs from 'fs';\nimport dotenv"
},
{
"path": "随笔.md",
"chars": 1644,
"preview": "# TimeoutError: Navigation timeout of 30000 ms exceeded 为什么puppeteer经常出现这个错误\n\n\n在使用 Puppeteer 进行网页自动化或爬虫开发时,`TimeoutErr"
}
]
About this extraction
This page contains the full source code of the 14790897/auto-read-liunxdo GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 63 files (286.4 KB), approximately 91.7k tokens, and a symbol index with 109 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.