Spaces:
Paused
Paused
github-actions[bot] commited on
Commit ·
cc9afac
0
Parent(s):
GitHub deploy: cdd1251aafcc7267d5c51954d23cb4565eba35a0
Browse files- .dockerignore +1 -0
- .gitattributes +3 -0
- .github/ISSUE_TEMPLATE/bug_report_template.md +20 -0
- .github/ISSUE_TEMPLATE/feature_request_template.md +16 -0
- .github/workflows/IssueManagementAutomation.yml +26 -0
- .github/workflows/cron_bypassCF.yaml +66 -0
- .github/workflows/cron_bypassCF_likeUser.yaml +67 -0
- .github/workflows/cron_read.yaml +34 -0
- .github/workflows/deploy-to-hf-spaces.yml +60 -0
- .github/workflows/docker.yml +36 -0
- .github/workflows/release-please.yml +16 -0
- .github/workflows/sync.yml +40 -0
- .github/workflows/windows_cron_bypassCF.yaml +65 -0
- .gitignore +4 -0
- CHANGELOG.md +265 -0
- Dockerfile +67 -0
- Dockerfile-like-user +65 -0
- LICENSE +21 -0
- README.md +218 -0
- README_en.md +90 -0
- Thumbs.db +0 -0
- bypasscf.js +536 -0
- bypasscf_likeUser.js +524 -0
- cron.log +0 -0
- cron.sh +22 -0
- docker-compose-like-user.yml +8 -0
- docker-compose.yml +8 -0
- example.png +0 -0
- external.js +383 -0
- image.png +0 -0
- image2.png +0 -0
- index.js +337 -0
- index_likeUser.js +395 -0
- index_likeUser_activity.js +395 -0
- index_passage_list_old_not_use.js +363 -0
- invite_codecow.js +184 -0
- package-lock.json +0 -0
- package.json +14 -0
- pnpm-lock.yaml +0 -0
- pteer.js +286 -0
- test_linuxdo.js +32 -0
- 随笔.md +57 -0
.dockerignore
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
.env
|
.gitattributes
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Auto detect text files and perform LF normalization
|
| 2 |
+
* text=auto
|
| 3 |
+
*.ttf filter=lfs diff=lfs merge=lfs -text
|
.github/ISSUE_TEMPLATE/bug_report_template.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
name: "🐛 Bug Report"
|
| 3 |
+
about: 报告一个功能或错误的问题。
|
| 4 |
+
title: "[Bug] 请简要描述问题"
|
| 5 |
+
labels: bug
|
| 6 |
+
assignees: ''
|
| 7 |
+
---
|
| 8 |
+
|
| 9 |
+
<!-- ⚠️ 请确保填写所有必须字段以便更快获得支持 -->
|
| 10 |
+
|
| 11 |
+
### 问题描述
|
| 12 |
+
<!-- 清楚简洁地描述您遇到的问题 -->
|
| 13 |
+
|
| 14 |
+
### 重现步骤
|
| 15 |
+
1. ...
|
| 16 |
+
2. ...
|
| 17 |
+
3. ...
|
| 18 |
+
|
| 19 |
+
### 附加信息(必须给出日志)
|
| 20 |
+
<!-- 如果有任何截图或者补充信息,请附上 -->
|
.github/ISSUE_TEMPLATE/feature_request_template.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
name: "✨ Feature Request"
|
| 3 |
+
about: 提出一个功能请求。
|
| 4 |
+
title: "[Feature] 请简要描述功能请求"
|
| 5 |
+
labels: enhancement
|
| 6 |
+
assignees: ''
|
| 7 |
+
---
|
| 8 |
+
|
| 9 |
+
### 背景
|
| 10 |
+
<!-- 为什么需要此功能?它解决了什么问题? -->
|
| 11 |
+
|
| 12 |
+
### 功能描述
|
| 13 |
+
<!-- 详细描述你所希望的功能 -->
|
| 14 |
+
|
| 15 |
+
### 实现建议
|
| 16 |
+
<!-- 如果有实现建议,请详细说明 -->
|
.github/workflows/IssueManagementAutomation.yml
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: IssueManagementAutomation(勿动)
|
| 2 |
+
|
| 3 |
+
on:
|
| 4 |
+
schedule:
|
| 5 |
+
- cron: "0 1-9/3 * * *" #"0 18 * * *" "0 */6 * * *"
|
| 6 |
+
workflow_dispatch:
|
| 7 |
+
push:
|
| 8 |
+
branches:
|
| 9 |
+
- main
|
| 10 |
+
issues:
|
| 11 |
+
types: [opened]
|
| 12 |
+
|
| 13 |
+
permissions:
|
| 14 |
+
issues: write
|
| 15 |
+
contents: read
|
| 16 |
+
|
| 17 |
+
jobs:
|
| 18 |
+
manage-issues:
|
| 19 |
+
runs-on: ubuntu-latest
|
| 20 |
+
steps:
|
| 21 |
+
- name: auto lock baipiao
|
| 22 |
+
uses: 14790897/auto-lock-baipiao@v0.1.7
|
| 23 |
+
with:
|
| 24 |
+
gh_token: ${{ secrets.GITHUB_TOKEN }}
|
| 25 |
+
gh_repo: ${{ github.repository }}
|
| 26 |
+
issue_labels: ${{ secrets.ISSUE_LABELS }}
|
.github/workflows/cron_bypassCF.yaml
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: readLike(自动阅读随机点赞)
|
| 2 |
+
# GitHub.secrets优先级最高,即使没有设置对应的变量,它也会读取,这时变量为空值,导致报错,.env读取的变量无法覆盖这个值,使用了${PASSWORD_ESCAPED//\#/\\#}来对#转义,需要两个\,但是我直接在env文件使用这种方法是不行的,GitHub action是有效
|
| 3 |
+
on:
|
| 4 |
+
schedule:
|
| 5 |
+
# 每天 UTC 时间 19:40 运行
|
| 6 |
+
- cron: "40 19 * * *"
|
| 7 |
+
workflow_dispatch: # 允许手动触发
|
| 8 |
+
|
| 9 |
+
jobs:
|
| 10 |
+
build:
|
| 11 |
+
runs-on: ubuntu-latest
|
| 12 |
+
timeout-minutes: 35 # 设置作业超时时间为20分钟
|
| 13 |
+
|
| 14 |
+
strategy:
|
| 15 |
+
matrix:
|
| 16 |
+
node-version: [20.x]
|
| 17 |
+
env:
|
| 18 |
+
# 在作业级别设置环境变量
|
| 19 |
+
USERNAMES: ${{ secrets.USERNAMES }}
|
| 20 |
+
PASSWORDS: ${{ secrets.PASSWORDS }}
|
| 21 |
+
RUN_TIME_LIMIT_MINUTES: ${{secrets.RUN_TIME_LIMIT_MINUTES}}
|
| 22 |
+
TELEGRAM_BOT_TOKEN: ${{secrets.TELEGRAM_BOT_TOKEN}}
|
| 23 |
+
TELEGRAM_CHAT_ID: ${{secrets.TELEGRAM_CHAT_ID}}
|
| 24 |
+
steps:
|
| 25 |
+
- uses: actions/checkout@v3 # 检出仓库
|
| 26 |
+
|
| 27 |
+
- name: Use Node.js ${{ matrix.node-version }}
|
| 28 |
+
uses: actions/setup-node@v3
|
| 29 |
+
with:
|
| 30 |
+
node-version: ${{ matrix.node-version }}
|
| 31 |
+
|
| 32 |
+
- name: Install dependencies
|
| 33 |
+
run: |
|
| 34 |
+
npm install
|
| 35 |
+
sudo apt install -y xvfb
|
| 36 |
+
#github action设置的action环境变量会完全替换掉env文件的读取,所以需要在action里手动进行加载env文件
|
| 37 |
+
- name: Load environment variables
|
| 38 |
+
run: |
|
| 39 |
+
echo "Debug: Checking if .env file exists..."
|
| 40 |
+
|
| 41 |
+
# 检查 .env 文件是否存在
|
| 42 |
+
if [ -f .env ]; then
|
| 43 |
+
echo ".env file found. Loading environment variables from .env file"
|
| 44 |
+
|
| 45 |
+
# 加载 .env 文件中的默认值
|
| 46 |
+
set -a
|
| 47 |
+
source .env
|
| 48 |
+
set +a
|
| 49 |
+
|
| 50 |
+
echo "Loaded .env variables:"
|
| 51 |
+
else
|
| 52 |
+
echo ".env file not found. Skipping loading."
|
| 53 |
+
fi
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
if [ -n "${{ secrets.WEBSITE }}" ] && [ ! -z "${{ secrets.WEBSITE }}" ]; then
|
| 57 |
+
echo "Using GitHub Secret for WEBSITE"
|
| 58 |
+
echo "WEBSITE=${{ secrets.WEBSITE }}" >> $GITHUB_ENV
|
| 59 |
+
else
|
| 60 |
+
echo "WEBSITE=${WEBSITE}" >> $GITHUB_ENV
|
| 61 |
+
fi
|
| 62 |
+
|
| 63 |
+
shell: bash
|
| 64 |
+
|
| 65 |
+
- name: Run a script
|
| 66 |
+
run: node bypasscf.js --USERNAMES "$USERNAMES" --PASSWORDS "$PASSWORDS" --WEBSITE "$WEBSITE"
|
.github/workflows/cron_bypassCF_likeUser.yaml
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: likeUser (点赞特定用户)
|
| 2 |
+
# GitHub.secrets优先级最高,即使没有设置对应的变量,它也会读取,这时变量为空值,导致报错,.env读取的变量无法覆盖这个值,使用了${PASSWORD_ESCAPED//\#/\\#}来对#转义,需要两个\,但是我直接在env文件使用这种方法是不行的,GitHub action是有效
|
| 3 |
+
on:
|
| 4 |
+
schedule:
|
| 5 |
+
# 每天 UTC 时间 18:00 运行
|
| 6 |
+
- cron: "0 18 * * *"
|
| 7 |
+
workflow_dispatch: # 允许手动触发
|
| 8 |
+
|
| 9 |
+
jobs:
|
| 10 |
+
build:
|
| 11 |
+
runs-on: ubuntu-latest
|
| 12 |
+
timeout-minutes: 35 # 设置作业超时时间为20分钟
|
| 13 |
+
|
| 14 |
+
strategy:
|
| 15 |
+
matrix:
|
| 16 |
+
node-version: [20.x]
|
| 17 |
+
env:
|
| 18 |
+
# 在作业级别设置环境变量
|
| 19 |
+
USERNAMES: ${{ secrets.USERNAMES }}
|
| 20 |
+
PASSWORDS: ${{ secrets.PASSWORDS }}
|
| 21 |
+
RUN_TIME_LIMIT_MINUTES: ${{secrets.RUN_TIME_LIMIT_MINUTES}}
|
| 22 |
+
TELEGRAM_BOT_TOKEN: ${{secrets.TELEGRAM_BOT_TOKEN}}
|
| 23 |
+
TELEGRAM_CHAT_ID: ${{secrets.TELEGRAM_CHAT_ID}}
|
| 24 |
+
SPECIFIC_USER: ${{secrets.SPECIFIC_USER}}
|
| 25 |
+
steps:
|
| 26 |
+
- uses: actions/checkout@v3 # 检出仓库
|
| 27 |
+
|
| 28 |
+
- name: Use Node.js ${{ matrix.node-version }}
|
| 29 |
+
uses: actions/setup-node@v3
|
| 30 |
+
with:
|
| 31 |
+
node-version: ${{ matrix.node-version }}
|
| 32 |
+
|
| 33 |
+
- name: Install dependencies
|
| 34 |
+
run: |
|
| 35 |
+
npm install
|
| 36 |
+
sudo apt install -y xvfb
|
| 37 |
+
#github action设置的action环境变量会完全替换掉env文件的读取,所以需要在action里手动进行加载env文件
|
| 38 |
+
- name: Load environment variables
|
| 39 |
+
run: |
|
| 40 |
+
echo "Debug: Checking if .env file exists..."
|
| 41 |
+
|
| 42 |
+
# 检查 .env 文件是否存在
|
| 43 |
+
if [ -f .env ]; then
|
| 44 |
+
echo ".env file found. Loading environment variables from .env file"
|
| 45 |
+
|
| 46 |
+
# 加载 .env 文件中的默认值
|
| 47 |
+
set -a
|
| 48 |
+
source .env
|
| 49 |
+
set +a
|
| 50 |
+
|
| 51 |
+
echo "Loaded .env variables:"
|
| 52 |
+
else
|
| 53 |
+
echo ".env file not found. Skipping loading."
|
| 54 |
+
fi
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
if [ -n "${{ secrets.WEBSITE }}" ] && [ ! -z "${{ secrets.WEBSITE }}" ]; then
|
| 58 |
+
echo "Using GitHub Secret for WEBSITE"
|
| 59 |
+
echo "WEBSITE=${{ secrets.WEBSITE }}" >> $GITHUB_ENV
|
| 60 |
+
else
|
| 61 |
+
echo "WEBSITE=${WEBSITE}" >> $GITHUB_ENV
|
| 62 |
+
fi
|
| 63 |
+
|
| 64 |
+
shell: bash
|
| 65 |
+
|
| 66 |
+
- name: Run a script
|
| 67 |
+
run: LIKE_SPECIFIC_USER=true node bypasscf.js --USERNAMES "$USERNAMES" --PASSWORDS "$PASSWORDS" --WEBSITE "$WEBSITE"
|
.github/workflows/cron_read.yaml
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: read cron (勿动)
|
| 2 |
+
|
| 3 |
+
on:
|
| 4 |
+
# schedule:
|
| 5 |
+
# # 每天 UTC 时间 18:00 运行
|
| 6 |
+
# - cron: "0 18 * * *"
|
| 7 |
+
workflow_dispatch: # 添加这行以允许手动触发
|
| 8 |
+
|
| 9 |
+
jobs:
|
| 10 |
+
build:
|
| 11 |
+
runs-on: ubuntu-latest
|
| 12 |
+
timeout-minutes: 20 # 设置作业超时时间为20分钟
|
| 13 |
+
|
| 14 |
+
strategy:
|
| 15 |
+
matrix:
|
| 16 |
+
node-version: [20.x] # 选择你需要的 Node.js 版本
|
| 17 |
+
env:
|
| 18 |
+
# 在作业级别设置环境变量
|
| 19 |
+
USERNAMES: ${{ secrets.USERNAMES }}
|
| 20 |
+
PASSWORDS: ${{ secrets.PASSWORDS }}
|
| 21 |
+
WEBSITE: ${{secrets.WEBSITE}}
|
| 22 |
+
|
| 23 |
+
steps:
|
| 24 |
+
- uses: actions/checkout@v3 # 检出你的仓库
|
| 25 |
+
- name: Use Node.js ${{ matrix.node-version }}
|
| 26 |
+
uses: actions/setup-node@v3
|
| 27 |
+
with:
|
| 28 |
+
node-version: ${{ matrix.node-version }}
|
| 29 |
+
- name: Install dependencies
|
| 30 |
+
run: |
|
| 31 |
+
npm install
|
| 32 |
+
sudo apt install -y xvfb
|
| 33 |
+
- name: Run auto read
|
| 34 |
+
run: node bypasscf.js # 替换为你想运行的脚本的实际名称
|
.github/workflows/deploy-to-hf-spaces.yml
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Deploy to HuggingFace Spaces
|
| 2 |
+
|
| 3 |
+
on:
|
| 4 |
+
push:
|
| 5 |
+
branches:
|
| 6 |
+
- dev
|
| 7 |
+
- main
|
| 8 |
+
workflow_dispatch:
|
| 9 |
+
|
| 10 |
+
jobs:
|
| 11 |
+
check-secret:
|
| 12 |
+
runs-on: ubuntu-latest
|
| 13 |
+
outputs:
|
| 14 |
+
token-set: ${{ steps.check-key.outputs.defined }}
|
| 15 |
+
steps:
|
| 16 |
+
- id: check-key
|
| 17 |
+
env:
|
| 18 |
+
HF_TOKEN: ${{ secrets.HF_TOKEN }}
|
| 19 |
+
if: "${{ env.HF_TOKEN != '' }}"
|
| 20 |
+
run: echo "defined=true" >> $GITHUB_OUTPUT
|
| 21 |
+
|
| 22 |
+
deploy:
|
| 23 |
+
runs-on: ubuntu-latest
|
| 24 |
+
needs: [check-secret]
|
| 25 |
+
if: needs.check-secret.outputs.token-set == 'true'
|
| 26 |
+
env:
|
| 27 |
+
HF_TOKEN: ${{ secrets.HF_TOKEN }}
|
| 28 |
+
HF_REPO: ${{ secrets.HF_REPO }}
|
| 29 |
+
HF_USER: ${{ secrets.HF_USER }}
|
| 30 |
+
steps:
|
| 31 |
+
- name: Checkout repository
|
| 32 |
+
uses: actions/checkout@v4
|
| 33 |
+
|
| 34 |
+
- name: Remove git history
|
| 35 |
+
run: rm -rf .git
|
| 36 |
+
|
| 37 |
+
- name: Prepend YAML front matter to README.md
|
| 38 |
+
run: |
|
| 39 |
+
echo "---" > temp_readme.md
|
| 40 |
+
echo "title: Auto Read" >> temp_readme.md
|
| 41 |
+
echo "emoji: 🐳" >> temp_readme.md
|
| 42 |
+
echo "colorFrom: purple" >> temp_readme.md
|
| 43 |
+
echo "colorTo: gray" >> temp_readme.md
|
| 44 |
+
echo "sdk: docker" >> temp_readme.md
|
| 45 |
+
echo "app_port: 7860" >> temp_readme.md
|
| 46 |
+
echo "---" >> temp_readme.md
|
| 47 |
+
cat README.md >> temp_readme.md
|
| 48 |
+
mv temp_readme.md README.md
|
| 49 |
+
|
| 50 |
+
- name: Configure git
|
| 51 |
+
run: |
|
| 52 |
+
git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
| 53 |
+
git config --global user.name "github-actions[bot]"
|
| 54 |
+
- name: Set up Git and push to Space
|
| 55 |
+
run: |
|
| 56 |
+
git init --initial-branch=main
|
| 57 |
+
git lfs track "*.ttf"
|
| 58 |
+
git add .
|
| 59 |
+
git commit -m "GitHub deploy: ${{ github.sha }}"
|
| 60 |
+
git push --force https://${HF_USER}:${HF_TOKEN}@huggingface.co/spaces/${HF_USER}/${HF_REPO} main
|
.github/workflows/docker.yml
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: AutoRead Docker Image Push (勿动)
|
| 2 |
+
|
| 3 |
+
on:
|
| 4 |
+
push:
|
| 5 |
+
branches:
|
| 6 |
+
- main
|
| 7 |
+
|
| 8 |
+
jobs:
|
| 9 |
+
build-and-push:
|
| 10 |
+
runs-on: ubuntu-latest
|
| 11 |
+
|
| 12 |
+
steps:
|
| 13 |
+
- name: Check Out Repo
|
| 14 |
+
uses: actions/checkout@v2
|
| 15 |
+
|
| 16 |
+
- name: Login to Docker Hub
|
| 17 |
+
uses: docker/login-action@v1
|
| 18 |
+
with:
|
| 19 |
+
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
| 20 |
+
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
|
| 21 |
+
|
| 22 |
+
- name: Build and Push auto-read
|
| 23 |
+
uses: docker/build-push-action@v2
|
| 24 |
+
with:
|
| 25 |
+
context: ./
|
| 26 |
+
file: ./Dockerfile
|
| 27 |
+
push: true
|
| 28 |
+
tags: 14790897/auto-read:latest
|
| 29 |
+
|
| 30 |
+
- name: Build and Push auto-like-user
|
| 31 |
+
uses: docker/build-push-action@v2
|
| 32 |
+
with:
|
| 33 |
+
context: ./
|
| 34 |
+
file: ./Dockerfile-like-user
|
| 35 |
+
push: true
|
| 36 |
+
tags: 14790897/auto-like-user:latest
|
.github/workflows/release-please.yml
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Release Please(勿动)
|
| 2 |
+
on:
|
| 3 |
+
push:
|
| 4 |
+
branches:
|
| 5 |
+
- main
|
| 6 |
+
jobs:
|
| 7 |
+
release-please:
|
| 8 |
+
runs-on: ubuntu-latest
|
| 9 |
+
steps:
|
| 10 |
+
- uses: GoogleCloudPlatform/release-please-action@v2
|
| 11 |
+
with:
|
| 12 |
+
token: ${{ secrets.GH_TOKEN }}
|
| 13 |
+
release-type: node
|
| 14 |
+
package-name: auto-read
|
| 15 |
+
permissions:
|
| 16 |
+
contents: write
|
.github/workflows/sync.yml
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Upstream Sync
|
| 2 |
+
|
| 3 |
+
permissions:
|
| 4 |
+
contents: write
|
| 5 |
+
|
| 6 |
+
on:
|
| 7 |
+
schedule:
|
| 8 |
+
- cron: "0 0 * * *" # every day
|
| 9 |
+
workflow_dispatch:
|
| 10 |
+
|
| 11 |
+
jobs:
|
| 12 |
+
sync_latest_from_upstream:
|
| 13 |
+
name: Sync latest commits from upstream repo
|
| 14 |
+
runs-on: ubuntu-latest
|
| 15 |
+
if: ${{ github.event.repository.fork }}
|
| 16 |
+
|
| 17 |
+
steps:
|
| 18 |
+
# Step 1: run a standard checkout action
|
| 19 |
+
- name: Checkout target repo
|
| 20 |
+
uses: actions/checkout@v3
|
| 21 |
+
|
| 22 |
+
# Step 2: run the sync action
|
| 23 |
+
- name: Sync upstream changes
|
| 24 |
+
id: sync
|
| 25 |
+
uses: aormsby/Fork-Sync-With-Upstream-action@v3.4
|
| 26 |
+
with:
|
| 27 |
+
upstream_sync_repo: 14790897/auto-read-liunxdo
|
| 28 |
+
upstream_sync_branch: main
|
| 29 |
+
target_sync_branch: main
|
| 30 |
+
target_repo_token: ${{ secrets.GITHUB_TOKEN }} # automatically generated, no need to set
|
| 31 |
+
|
| 32 |
+
# Set test_mode true to run tests instead of the true action!!
|
| 33 |
+
test_mode: false
|
| 34 |
+
|
| 35 |
+
- name: Sync check
|
| 36 |
+
if: failure()
|
| 37 |
+
run: |
|
| 38 |
+
echo "[Error] 由于上游仓库的 workflow 文件变更,导致 GitHub 自动暂停了本次自动更新,你需要手动 Sync Fork 一次,详细教程请查看: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"
|
| 39 |
+
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"
|
| 40 |
+
exit 1
|
.github/workflows/windows_cron_bypassCF.yaml
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: readLike windows(自动阅读随机点赞)
|
| 2 |
+
# GitHub.secrets优先级最高,即使没有设置对应的变量,它也会读取,这时变量为空值,导致报错,.env读取的变量无法覆盖这个值,使用了${PASSWORD_ESCAPED//\#/\\#}来对#转义,需要两个\,但是我直接在env文件使用这种方法是不行的,GitHub action是有效
|
| 3 |
+
on:
|
| 4 |
+
# schedule:
|
| 5 |
+
# 每天 UTC 时间 18:40 运行
|
| 6 |
+
# - cron: "40 18 * * *"
|
| 7 |
+
workflow_dispatch: # 允许手动触发
|
| 8 |
+
|
| 9 |
+
jobs:
|
| 10 |
+
build:
|
| 11 |
+
runs-on: windows-latest
|
| 12 |
+
timeout-minutes: 35 # 设置作业超时时间为20分钟
|
| 13 |
+
|
| 14 |
+
strategy:
|
| 15 |
+
matrix:
|
| 16 |
+
node-version: [20.x]
|
| 17 |
+
env:
|
| 18 |
+
# 在作业级别设置环境变量
|
| 19 |
+
USERNAMES: ${{ secrets.USERNAMES }}
|
| 20 |
+
PASSWORDS: ${{ secrets.PASSWORDS }}
|
| 21 |
+
RUN_TIME_LIMIT_MINUTES: ${{secrets.RUN_TIME_LIMIT_MINUTES}}
|
| 22 |
+
TELEGRAM_BOT_TOKEN: ${{secrets.TELEGRAM_BOT_TOKEN}}
|
| 23 |
+
TELEGRAM_CHAT_ID: ${{secrets.TELEGRAM_CHAT_ID}}
|
| 24 |
+
steps:
|
| 25 |
+
- uses: actions/checkout@v3 # 检出仓库
|
| 26 |
+
|
| 27 |
+
- name: Use Node.js ${{ matrix.node-version }}
|
| 28 |
+
uses: actions/setup-node@v3
|
| 29 |
+
with:
|
| 30 |
+
node-version: ${{ matrix.node-version }}
|
| 31 |
+
|
| 32 |
+
- name: Install dependencies
|
| 33 |
+
run: |
|
| 34 |
+
npm install
|
| 35 |
+
#github action设置的action环境变量会完全替换掉env文件的读取,所以需要在action里手动进行加载env文件
|
| 36 |
+
- name: Load environment variables
|
| 37 |
+
run: |
|
| 38 |
+
echo "Debug: Checking if .env file exists..."
|
| 39 |
+
|
| 40 |
+
# 检查 .env 文件是否存在
|
| 41 |
+
if [ -f .env ]; then
|
| 42 |
+
echo ".env file found. Loading environment variables from .env file"
|
| 43 |
+
|
| 44 |
+
# 加载 .env 文件中的默认值
|
| 45 |
+
set -a
|
| 46 |
+
source .env
|
| 47 |
+
set +a
|
| 48 |
+
|
| 49 |
+
echo "Loaded .env variables:"
|
| 50 |
+
else
|
| 51 |
+
echo ".env file not found. Skipping loading."
|
| 52 |
+
fi
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
if [ -n "${{ secrets.WEBSITE }}" ] && [ ! -z "${{ secrets.WEBSITE }}" ]; then
|
| 56 |
+
echo "Using GitHub Secret for WEBSITE"
|
| 57 |
+
echo "WEBSITE=${{ secrets.WEBSITE }}" >> $GITHUB_ENV
|
| 58 |
+
else
|
| 59 |
+
echo "WEBSITE=${WEBSITE}" >> $GITHUB_ENV
|
| 60 |
+
fi
|
| 61 |
+
|
| 62 |
+
shell: bash
|
| 63 |
+
|
| 64 |
+
- name: Run a script
|
| 65 |
+
run: node bypasscf.js --USERNAMES "$USERNAMES" --PASSWORDS "$PASSWORDS" --WEBSITE "$WEBSITE"
|
.gitignore
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
node_modules/
|
| 2 |
+
.env.local*
|
| 3 |
+
screenshots
|
| 4 |
+
.env
|
CHANGELOG.md
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Changelog
|
| 2 |
+
|
| 3 |
+
## [1.9.0](https://www.github.com/14790897/auto-read-liunxdo/compare/v1.8.2...v1.9.0) (2025-02-12)
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
### Features
|
| 7 |
+
|
| 8 |
+
* 支持禁用自动点赞 ([f6f5e26](https://www.github.com/14790897/auto-read-liunxdo/commit/f6f5e2692eb6974117f0720c0be0623105c656b4))
|
| 9 |
+
|
| 10 |
+
### [1.8.2](https://www.github.com/14790897/auto-read-liunxdo/compare/v1.8.1...v1.8.2) (2025-02-12)
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
### Bug Fixes
|
| 14 |
+
|
| 15 |
+
* 存在无法点击登录按钮的情况,原因未知,在等待跳转前,加入按钮点击 ([1d67b5e](https://www.github.com/14790897/auto-read-liunxdo/commit/1d67b5e0fa531148ceff3c8c3f083d253a98db75))
|
| 16 |
+
|
| 17 |
+
### [1.8.1](https://www.github.com/14790897/auto-read-liunxdo/compare/v1.8.0...v1.8.1) (2025-01-15)
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
### Bug Fixes
|
| 21 |
+
|
| 22 |
+
* 展示信息 ([27ffc94](https://www.github.com/14790897/auto-read-liunxdo/commit/27ffc94610e88df9d06e847461cddb5faff27b6b))
|
| 23 |
+
|
| 24 |
+
## [1.8.0](https://www.github.com/14790897/auto-read-liunxdo/compare/v1.7.0...v1.8.0) (2025-01-14)
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
### Features
|
| 28 |
+
|
| 29 |
+
* 登录失败重试三次 ([2fdee4e](https://www.github.com/14790897/auto-read-liunxdo/commit/2fdee4e6f23f16ae565594a35008101596400b36))
|
| 30 |
+
* 超时和密码错误逻辑分开 ([c2f61e3](https://www.github.com/14790897/auto-read-liunxdo/commit/c2f61e35f16b8fb055aa3b612c2b3c1de85194e8))
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
### Bug Fixes
|
| 34 |
+
|
| 35 |
+
* error ([cff54ac](https://www.github.com/14790897/auto-read-liunxdo/commit/cff54ac99fe95466b7e7b5fd2f5356193465fcec))
|
| 36 |
+
* error ([a106c34](https://www.github.com/14790897/auto-read-liunxdo/commit/a106c34866588a0e401c5c3e8abe0619422e349c))
|
| 37 |
+
* error ([e689db0](https://www.github.com/14790897/auto-read-liunxdo/commit/e689db064937e9527d6208f009c5f85b2a2ae1ce))
|
| 38 |
+
* xvfb包 ([da5549a](https://www.github.com/14790897/auto-read-liunxdo/commit/da5549abfb71f0651ea51dc502b039c5375ef094))
|
| 39 |
+
* 未使用await ([2505906](https://www.github.com/14790897/auto-read-liunxdo/commit/2505906771302de2a7f1f49abd5f7e9b75421f53))
|
| 40 |
+
* 检测密码错误提示 ([22d3766](https://www.github.com/14790897/auto-read-liunxdo/commit/22d3766248481d1290b83e7e3c0eddd34def4573))
|
| 41 |
+
* 登录失败throw error ([2880de3](https://www.github.com/14790897/auto-read-liunxdo/commit/2880de320a1829b6cb070ab8f1f647c2e2e35356))
|
| 42 |
+
|
| 43 |
+
## [1.7.0](https://www.github.com/14790897/auto-read-liunxdo/compare/v1.6.0...v1.7.0) (2024-11-29)
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
### Features
|
| 47 |
+
|
| 48 |
+
* 健康探针 ([0768f30](https://www.github.com/14790897/auto-read-liunxdo/commit/0768f303e6c4a4074f3528970f5329c53d55ae07))
|
| 49 |
+
* 加上首页 ([b561fde](https://www.github.com/14790897/auto-read-liunxdo/commit/b561fde351ad031f4e0e1396aa4683ea0572ab1a))
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
### Bug Fixes
|
| 53 |
+
|
| 54 |
+
* 尝试改进dockerfile ([533a5c9](https://www.github.com/14790897/auto-read-liunxdo/commit/533a5c9d21c35c1b3c8d2d3d2c46a0dedd070493))
|
| 55 |
+
* 暴露端口问题 ([78a1aca](https://www.github.com/14790897/auto-read-liunxdo/commit/78a1aca637909f65909eadd85b45a36704646020))
|
| 56 |
+
|
| 57 |
+
## [1.6.0](https://www.github.com/14790897/auto-read-liunxdo/compare/v1.5.0...v1.6.0) (2024-10-10)
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
### Features
|
| 61 |
+
|
| 62 |
+
* last_read_post_number完善自动阅读跳转 ([f872df4](https://www.github.com/14790897/auto-read-liunxdo/commit/f872df46d04c5726c023a6f1a3d464bf21607cc1))
|
| 63 |
+
* 先点赞再阅读 ([a9f016b](https://www.github.com/14790897/auto-read-liunxdo/commit/a9f016be196074d75f549ed408892ed0809e6095))
|
| 64 |
+
* 增加登录成功通知 ([7d4c96d](https://www.github.com/14790897/auto-read-liunxdo/commit/7d4c96dd78f7d5332a8d9d36729f46d0746c83ef))
|
| 65 |
+
* 自动点赞的docker ([d4b7195](https://www.github.com/14790897/auto-read-liunxdo/commit/d4b7195047ca12f4991583d93bd1a514d56a960a))
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
### Bug Fixes
|
| 69 |
+
|
| 70 |
+
* action不能指定点赞用户 ([4fb63a3](https://www.github.com/14790897/auto-read-liunxdo/commit/4fb63a3b035f01f05316c3b37b4cc1c305e1b933))
|
| 71 |
+
* docker读取.env.local变量 ([c5a61bf](https://www.github.com/14790897/auto-read-liunxdo/commit/c5a61bf5ac38cd05fbde5204976d52541f3c6dfb))
|
| 72 |
+
* 启动间隔时间修复 ([3d32bba](https://www.github.com/14790897/auto-read-liunxdo/commit/3d32bba8c471750b7e0e9b508eaca9e49f258c52))
|
| 73 |
+
|
| 74 |
+
## [1.5.0](https://www.github.com/14790897/auto-read-liunxdo/compare/v1.4.0...v1.5.0) (2024-09-12)
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
### Features
|
| 78 |
+
|
| 79 |
+
* bypasscf使用更好的脚本 ([0fd5340](https://www.github.com/14790897/auto-read-liunxdo/commit/0fd534051629d85f9a7f31d106abf42ca4743576))
|
| 80 |
+
* 优化dockercompose 使得它可以直接读取env文件 ([08a9a4f](https://www.github.com/14790897/auto-read-liunxdo/commit/08a9a4fb044350d72b939531b9398fc6467cdcff))
|
| 81 |
+
* 使用api获得文章列表 ([ccca920](https://www.github.com/14790897/auto-read-liunxdo/commit/ccca9208b28f155af58c2513e2a0040b26b77905))
|
| 82 |
+
* 使用search获得不重复的post ([046e1e6](https://www.github.com/14790897/auto-read-liunxdo/commit/046e1e6026a6484ccfab4fea134f7893782c1dd5))
|
| 83 |
+
* 可以设置结束时间,避免action报错,默认15分钟 ([8f19e92](https://www.github.com/14790897/auto-read-liunxdo/commit/8f19e92adb2ce422787be6a4bf0abece9c385b4d))
|
| 84 |
+
* 多账号分批处理 ([26e516f](https://www.github.com/14790897/auto-read-liunxdo/commit/26e516fbb451769383a31804739a80786ff5c563))
|
| 85 |
+
* 多账号分批处理 ([a5002fb](https://www.github.com/14790897/auto-read-liunxdo/commit/a5002fb9bb104013dd5094f0a32ae37d67107e03))
|
| 86 |
+
* 多账号分批处理 ([500c34e](https://www.github.com/14790897/auto-read-liunxdo/commit/500c34e840c3781f6ce3236e22b5d7a2649639de))
|
| 87 |
+
* 点赞特定用户cron ([214b3b9](https://www.github.com/14790897/auto-read-liunxdo/commit/214b3b9507d74df9a30da8df3db0fe1459d3853f))
|
| 88 |
+
* 电报机器人消息推送 ([43f1f42](https://www.github.com/14790897/auto-read-liunxdo/commit/43f1f425a94589267bc716e144549088359c0ff4))
|
| 89 |
+
* 自动点赞特定用户 ([e2340d4](https://www.github.com/14790897/auto-read-liunxdo/commit/e2340d4433fdd8b4a05af782e89dcd6285f9b9ef))
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
### Bug Fixes
|
| 93 |
+
|
| 94 |
+
* cron中变量的默认值 ([10499e2](https://www.github.com/14790897/auto-read-liunxdo/commit/10499e2aa7fb02075ee7925cbeb2125a59a2d922))
|
| 95 |
+
* cron中变量的默认值 ([9d0302a](https://www.github.com/14790897/auto-read-liunxdo/commit/9d0302a9098267ea587456a7f607e547cd004d86))
|
| 96 |
+
* cron中变量的默认值 ([09fd9bd](https://www.github.com/14790897/auto-read-liunxdo/commit/09fd9bd7a0f0e2b047d3c02bd7f4d93df22503dd))
|
| 97 |
+
* cron中变量的默认值 ([c1c07ca](https://www.github.com/14790897/auto-read-liunxdo/commit/c1c07cae1bd109f8785907b35ebc63b28dce6e93))
|
| 98 |
+
* cron中变量的默认值 ([6bc8bce](https://www.github.com/14790897/auto-read-liunxdo/commit/6bc8bce2a42bbb414450454f9bd36c5a07da8fc5))
|
| 99 |
+
* cron中变量的默认值 ([b2227b6](https://www.github.com/14790897/auto-read-liunxdo/commit/b2227b60a0fd3af4e5abe0dbe19b85bc42ba855c))
|
| 100 |
+
* cron中变量的默认值 ([b9b72fc](https://www.github.com/14790897/auto-read-liunxdo/commit/b9b72fce05be1046b7f917d9820ba3d9260ab476))
|
| 101 |
+
* cron中变量的默认值 ([4cc640a](https://www.github.com/14790897/auto-read-liunxdo/commit/4cc640a7c15f30c0f6455a2e6343fd4681960a67))
|
| 102 |
+
* cron中变量的默认值 ([3b044c1](https://www.github.com/14790897/auto-read-liunxdo/commit/3b044c1cbc57cb043e53a0de15287cbd35f0fce1))
|
| 103 |
+
* cron中变量的默认值 ([72af582](https://www.github.com/14790897/auto-read-liunxdo/commit/72af5821a782b3eb6174372d89dbff5a78656ecf))
|
| 104 |
+
* cron中变量的默认值 ([8fedb7b](https://www.github.com/14790897/auto-read-liunxdo/commit/8fedb7be3930f02fa2b89a49a35484e0f8cfd273))
|
| 105 |
+
* env.local后才能读取环境变量,page.evaluate变量必须从外部显示的传入, 因为在浏览器上下文它是读取不了的 ([f57d512](https://www.github.com/14790897/auto-read-liunxdo/commit/f57d5128dae5e3cffc9928589cf9c427e84d648c))
|
| 106 |
+
* env写错了 ([19d3b64](https://www.github.com/14790897/auto-read-liunxdo/commit/19d3b644445bdd26bb4a3e5a5ebc480ed085cbee))
|
| 107 |
+
* run-at document-end可以修复有时候脚本不运行的问题 ([a0c35f2](https://www.github.com/14790897/auto-read-liunxdo/commit/a0c35f26a2fb187950d4a220ed096fd419e59c88))
|
| 108 |
+
* throw error导致无法运行 ([67811e3](https://www.github.com/14790897/auto-read-liunxdo/commit/67811e35394bf02ef1af6850dffd6b888c4091ae))
|
| 109 |
+
* 保存用户的时候需要清除 localStorage.removeItem("lastOffset"); ([edb72ac](https://www.github.com/14790897/auto-read-liunxdo/commit/edb72ac66ac6bcd864781d2c85d7795bc15881d9))
|
| 110 |
+
* 其实不需要 !topic.unseen ([d2a0ab3](https://www.github.com/14790897/auto-read-liunxdo/commit/d2a0ab3342fcd26498fab9c0e6ebac815b4c353c))
|
| 111 |
+
* 只有一个账号会立刻停止的问题 ([672ebee](https://www.github.com/14790897/auto-read-liunxdo/commit/672ebee00c954cd41661751a35a08868eb3d239d))
|
| 112 |
+
* 密码转义 ([e5802ab](https://www.github.com/14790897/auto-read-liunxdo/commit/e5802abbf770c5a65cfaee3515529d75558b8068))
|
| 113 |
+
* 直接使用油猴脚本 ([d429ca9](https://www.github.com/14790897/auto-read-liunxdo/commit/d429ca931cf8bddfbd14788a451e0c6d2cf05313))
|
| 114 |
+
* 返回 ([9f5d398](https://www.github.com/14790897/auto-read-liunxdo/commit/9f5d39814940c88628bdd648b7766143734f0201))
|
| 115 |
+
* 通过在主进程直接设置localstorage变量,避免单独设置 ([99b6725](https://www.github.com/14790897/auto-read-liunxdo/commit/99b67252ba7536a75708b6eb19956ace04a71122))
|
| 116 |
+
* 重置用户的时候需要清空post列表 ([5980c9a](https://www.github.com/14790897/auto-read-liunxdo/commit/5980c9a3b205af32fbceedc157400330eb77f3b0))
|
| 117 |
+
|
| 118 |
+
## [1.4.0](https://www.github.com/14790897/auto-read-liunxdo/compare/v1.3.0...v1.4.0) (2024-08-08)
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
### Features
|
| 122 |
+
|
| 123 |
+
* action中如果secrets未定义则使用env文件 ([cc2812f](https://www.github.com/14790897/auto-read-liunxdo/commit/cc2812f6a1bdf43bc03c676a963b00ce8271f732))
|
| 124 |
+
* 增加了对小众软件论坛的支持(https://linux.do/t/topic/169209/166) ([598913c](https://www.github.com/14790897/auto-read-liunxdo/commit/598913c09b9bc9b880fe9f974c3da490acb6ca55))
|
| 125 |
+
|
| 126 |
+
|
| 127 |
+
### Bug Fixes
|
| 128 |
+
|
| 129 |
+
* action 中secret读取特殊字符处理 ([5457abc](https://www.github.com/14790897/auto-read-liunxdo/commit/5457abce09c0b26a54ef6b67b0563b49ca567e97))
|
| 130 |
+
* docker-compose.yml命令错误 ([ec3cedc](https://www.github.com/14790897/auto-read-liunxdo/commit/ec3cedc83895cbf8dc759770c1203fa718b52dfd))
|
| 131 |
+
* docker-compose.yml命令错误 ([2b4d73d](https://www.github.com/14790897/auto-read-liunxdo/commit/2b4d73de2becd56f8a1c4d7ecc8aff71de619225))
|
| 132 |
+
* docker-compose.yml命令错误 ([2d8a099](https://www.github.com/14790897/auto-read-liunxdo/commit/2d8a099990d986741189129dfb67ec8e8869325e))
|
| 133 |
+
* docker-compose.yml命令错误(https://linux.do/t/topic/169209/158) ([b83b09c](https://www.github.com/14790897/auto-read-liunxdo/commit/b83b09cbc0b96b846d78d8aa1ef242e4429bac9b))
|
| 134 |
+
* docker命令执行的代码 ([aa36a2b](https://www.github.com/14790897/auto-read-liunxdo/commit/aa36a2b754e2591c65dfdd9314e8676aeba60b2b))
|
| 135 |
+
* loginbutton作用域问题 ([1f626aa](https://www.github.com/14790897/auto-read-liunxdo/commit/1f626aa8cd8299086f91a4019779500cbd9abbfb))
|
| 136 |
+
* Windows需要等待cf的完成 ([bdcbeaf](https://www.github.com/14790897/auto-read-liunxdo/commit/bdcbeaff2403123b74a0e031c28560b16265798b))
|
| 137 |
+
* 似乎不需要特殊处理 ([dc96005](https://www.github.com/14790897/auto-read-liunxdo/commit/dc960051002d88f27e5fd5ccde5a22d6be511250))
|
| 138 |
+
* 增加navigation超时时长 ([7c92ff0](https://www.github.com/14790897/auto-read-liunxdo/commit/7c92ff0b3d58753571674f133eaf3bd88d9c75de))
|
| 139 |
+
* 增加点赞间隔 ([dc472be](https://www.github.com/14790897/auto-read-liunxdo/commit/dc472be03be350e2a69d7adc25ae628f1193f241))
|
| 140 |
+
* 增加点赞间隔,避免频繁429 ([706198d](https://www.github.com/14790897/auto-read-liunxdo/commit/706198d1359157da83bb839827278a6f0b61c01c))
|
| 141 |
+
* 还是要找button ([ba801c9](https://www.github.com/14790897/auto-read-liunxdo/commit/ba801c9cc82b6ba5825fc79111c5d8d319c50cf3))
|
| 142 |
+
|
| 143 |
+
## [1.3.0](https://www.github.com/14790897/auto-read-liunxdo/compare/v1.2.1...v1.3.0) (2024-08-04)
|
| 144 |
+
|
| 145 |
+
|
| 146 |
+
### Features
|
| 147 |
+
|
| 148 |
+
* bypasscf可以绕过cf了 ([197e04f](https://www.github.com/14790897/auto-read-liunxdo/commit/197e04f1b67164ccabdb8e8347039c2ceb51d8e7))
|
| 149 |
+
* 截图记录功能 ([2e98654](https://www.github.com/14790897/auto-read-liunxdo/commit/2e986540f9170ef345b8d2a3e8df7b4b7a8a00c2))
|
| 150 |
+
* 新的cron ([5e005eb](https://www.github.com/14790897/auto-read-liunxdo/commit/5e005eb4591f193c3b45ba4029e4363c7356f62e))
|
| 151 |
+
|
| 152 |
+
|
| 153 |
+
### Bug Fixes
|
| 154 |
+
|
| 155 |
+
* action大小写问题 ([aeeb918](https://www.github.com/14790897/auto-read-liunxdo/commit/aeeb918fe199003b66279aa1e182496ed4b4d683))
|
| 156 |
+
* auto加双引号 ([b38c22e](https://www.github.com/14790897/auto-read-liunxdo/commit/b38c22ee52165cecdae63c296434f564af7f95f0))
|
| 157 |
+
* docker compose环境变量配置 ([b67d946](https://www.github.com/14790897/auto-read-liunxdo/commit/b67d94633920a9c8f2c5eaf6a8a08590d443cf7c))
|
| 158 |
+
* docker compose环境变量配置 ([a7f19bb](https://www.github.com/14790897/auto-read-liunxdo/commit/a7f19bbd3d139e5ede1402127fed13ce5bfea9f1))
|
| 159 |
+
* es6 dirname不存在 ([b5f02b4](https://www.github.com/14790897/auto-read-liunxdo/commit/b5f02b44c4b2c1d02d1ac83d1bcfe8d2f2e55096))
|
| 160 |
+
* Windows有头,Linux无头 ([5a39ded](https://www.github.com/14790897/auto-read-liunxdo/commit/5a39ded0ca6afc3790d891d70928cc7a863c5e01))
|
| 161 |
+
* 使用{ waitUntil: "domcontentloaded" }避免超时错误 ([8c3e38a](https://www.github.com/14790897/auto-read-liunxdo/commit/8c3e38a73efbc51c02191f0bb71350ff4486d9f5))
|
| 162 |
+
|
| 163 |
+
### [1.2.1](https://www.github.com/14790897/auto-read-liunxdo/compare/v1.2.0...v1.2.1) (2024-05-20)
|
| 164 |
+
|
| 165 |
+
|
| 166 |
+
### Bug Fixes
|
| 167 |
+
|
| 168 |
+
* env注释 ([22b118f](https://www.github.com/14790897/auto-read-liunxdo/commit/22b118f5c18f24471feb48890bcbc32f905037a0))
|
| 169 |
+
* 使用domcontentloaded等待页面跳转 ([611290c](https://www.github.com/14790897/auto-read-liunxdo/commit/611290c4c64aa7d75736156dae61674284d67499))
|
| 170 |
+
* 点赞每日重置,修复只能启动一次自动点赞 ([15529df](https://www.github.com/14790897/auto-read-liunxdo/commit/15529df487d307cd421008cb9477e369cda6075a))
|
| 171 |
+
* 错误处理 ([c1ad79f](https://www.github.com/14790897/auto-read-liunxdo/commit/c1ad79ff26d3a806b0e9ae450b4d36a4f8e134c1))
|
| 172 |
+
|
| 173 |
+
## [1.2.0](https://www.github.com/14790897/auto-read-liunxdo/compare/v1.1.0...v1.2.0) (2024-05-07)
|
| 174 |
+
|
| 175 |
+
|
| 176 |
+
### Features
|
| 177 |
+
|
| 178 |
+
* 增加local变量的读取方便调试 ([08c5631](https://www.github.com/14790897/auto-read-liunxdo/commit/08c563143d7fd1ae868e90a950cbd51ea46fe279))
|
| 179 |
+
|
| 180 |
+
|
| 181 |
+
### Bug Fixes
|
| 182 |
+
|
| 183 |
+
* headless改为true ([77bee42](https://www.github.com/14790897/auto-read-liunxdo/commit/77bee42accad682cc9ed4e680b1d529d7274d015))
|
| 184 |
+
|
| 185 |
+
## [1.1.0](https://www.github.com/14790897/auto-read-liunxdo/compare/v1.0.0...v1.1.0) (2024-04-30)
|
| 186 |
+
|
| 187 |
+
|
| 188 |
+
### Features
|
| 189 |
+
|
| 190 |
+
* cron的docker ([3f96acc](https://www.github.com/14790897/auto-read-liunxdo/commit/3f96accce19c263263ad953f3c55f422a0c16c37))
|
| 191 |
+
* docker 定时运行 ([963a2ed](https://www.github.com/14790897/auto-read-liunxdo/commit/963a2edf466cf113908493e1144f23c1ea9c95c6))
|
| 192 |
+
* docker输出日志 ([a0a13e6](https://www.github.com/14790897/auto-read-liunxdo/commit/a0a13e6f09d6226f3811cb82425852f4283f48f5))
|
| 193 |
+
* 环境变量可以配置阅读网站 ([fc1e52b](https://www.github.com/14790897/auto-read-liunxdo/commit/fc1e52b20fe8ad17a3c5215a3732da5d91a49d12))
|
| 194 |
+
* 适用于meta.discourse ([ada117a](https://www.github.com/14790897/auto-read-liunxdo/commit/ada117af31053029fcec28292844175db6b5d6a6))
|
| 195 |
+
|
| 196 |
+
|
| 197 |
+
### Bug Fixes
|
| 198 |
+
|
| 199 |
+
* cron ([13e2181](https://www.github.com/14790897/auto-read-liunxdo/commit/13e21815c8b4f081cef4c76d113781451e738030))
|
| 200 |
+
* cron ([3986f1d](https://www.github.com/14790897/auto-read-liunxdo/commit/3986f1d22c2c6be1ff0e373fad5862dcf2f3b4e5))
|
| 201 |
+
* cron ([d428a63](https://www.github.com/14790897/auto-read-liunxdo/commit/d428a6357f2c705c6b288188909fa5f62f35ab5a))
|
| 202 |
+
* cron ([82376ca](https://www.github.com/14790897/auto-read-liunxdo/commit/82376ca52bbd374ce8596b4583bc3e09e30d741c))
|
| 203 |
+
* cron ([b3f17c7](https://www.github.com/14790897/auto-read-liunxdo/commit/b3f17c75fbc83b2f4be005e6de8d683a4d93ad70))
|
| 204 |
+
* cron ([feec05e](https://www.github.com/14790897/auto-read-liunxdo/commit/feec05e10149f7314894b3d6284285f2aaa564d5))
|
| 205 |
+
* cron ([1ba6a4d](https://www.github.com/14790897/auto-read-liunxdo/commit/1ba6a4d7862f6d9c46b464be107b8ac7aaed66b3))
|
| 206 |
+
* cron ([bdd3db0](https://www.github.com/14790897/auto-read-liunxdo/commit/bdd3db0d9e0bf3109fbe2af3fee3731c9fd8478f))
|
| 207 |
+
* cron ([51d9566](https://www.github.com/14790897/auto-read-liunxdo/commit/51d95663b21121328ddd257e87c529fd807b7155))
|
| 208 |
+
* cron ([51326fe](https://www.github.com/14790897/auto-read-liunxdo/commit/51326fec312ada029e29fb8fe7b46996ae17d3a6))
|
| 209 |
+
* cron ([69b1801](https://www.github.com/14790897/auto-read-liunxdo/commit/69b1801b11af87685761927488937ac18592b42c))
|
| 210 |
+
* cron bug ([e749db7](https://www.github.com/14790897/auto-read-liunxdo/commit/e749db7d50586082e8efae2e3e3cd8b8a1f3dd56))
|
| 211 |
+
* cron docker ([a572a72](https://www.github.com/14790897/auto-read-liunxdo/commit/a572a720be95a8d1496699fd2203cace0cc53041))
|
| 212 |
+
* cron添加执行权限 ([c57da15](https://www.github.com/14790897/auto-read-liunxdo/commit/c57da15f3bd2e5f119a8f145adfb9c97d1a624e1))
|
| 213 |
+
* docker ([effbaa1](https://www.github.com/14790897/auto-read-liunxdo/commit/effbaa1b6982b0dd90494c0ddc9726481a824e73))
|
| 214 |
+
* docker 环境变量配置 ([b651584](https://www.github.com/14790897/auto-read-liunxdo/commit/b651584caa5fed554f9b95707d87e0465e3ed698))
|
| 215 |
+
* remove button in pteer ([edc8ef0](https://www.github.com/14790897/auto-read-liunxdo/commit/edc8ef04eb4a9034d46194722864d00f32aaadf1))
|
| 216 |
+
* workdir ([6083de8](https://www.github.com/14790897/auto-read-liunxdo/commit/6083de8418e1f2f8f34937f73716d30a40b673bd))
|
| 217 |
+
* 权限 ([4c33ce9](https://www.github.com/14790897/auto-read-liunxdo/commit/4c33ce93f29013f88611ed29d4177d2ed935fe98))
|
| 218 |
+
|
| 219 |
+
## 1.0.0 (2024-04-05)
|
| 220 |
+
|
| 221 |
+
|
| 222 |
+
### Features
|
| 223 |
+
|
| 224 |
+
* puppeteer ([8952911](https://www.github.com/14790897/auto-read-liunxdo/commit/895291148807dc669c10a9e0481cb9a024c57577))
|
| 225 |
+
* 再加一个链接 ([60cc6b0](https://www.github.com/14790897/auto-read-liunxdo/commit/60cc6b03fe884ca700b8645f646801f8d7ef088e))
|
| 226 |
+
* 增加脚本图标 ([61e2d35](https://www.github.com/14790897/auto-read-liunxdo/commit/61e2d354ce5b8e7c54f65233ffb2f0d89e7534fe))
|
| 227 |
+
* 多浏览器间隔启动 ([0647c0b](https://www.github.com/14790897/auto-read-liunxdo/commit/0647c0b721972db19451ba73a53dbe4a6831e52a))
|
| 228 |
+
* 多账户功能 ([c922667](https://www.github.com/14790897/auto-read-liunxdo/commit/c9226675ca22c826e09959989154cd91309d027a))
|
| 229 |
+
* 完善登录等待逻辑 ([6134567](https://www.github.com/14790897/auto-read-liunxdo/commit/6134567566ef695cb33244e52c08d4a0e0b1f8a7))
|
| 230 |
+
* 找按钮逻辑优化 ([4e392a1](https://www.github.com/14790897/auto-read-liunxdo/commit/4e392a125b6ecc7e994fa91ff67dd64cc3e01eeb))
|
| 231 |
+
* 收集报错 ([3b12c3b](https://www.github.com/14790897/auto-read-liunxdo/commit/3b12c3bcef358df0e7b12ccd5263ace8c9fc4eb7))
|
| 232 |
+
* 更好的寻找未读过的文章,但可能有问题 ([5e1e9ce](https://www.github.com/14790897/auto-read-liunxdo/commit/5e1e9ce390e886259f7e92a19a7b02201e1e1f74))
|
| 233 |
+
* 检查avatarImg判断登录状况 ([19efb2f](https://www.github.com/14790897/auto-read-liunxdo/commit/19efb2f74918e1e4dc8b72992f2e06a4d1d217eb))
|
| 234 |
+
* **点赞:** 防止已赞过的被取消 ([fcc2b40](https://www.github.com/14790897/auto-read-liunxdo/commit/fcc2b40c70c8475f83be4af2b4a7c5be601373bb))
|
| 235 |
+
* 自动点赞 ([a9dd8d7](https://www.github.com/14790897/auto-read-liunxdo/commit/a9dd8d74d5bcbcd9836ff0fd5df3c5014188c5a8))
|
| 236 |
+
* 自动点赞按钮 ([843f61f](https://www.github.com/14790897/auto-read-liunxdo/commit/843f61fe5178d7a6c4ae968a5aef2457efbda238))
|
| 237 |
+
* 设置正常的请求头 ([3eb58de](https://www.github.com/14790897/auto-read-liunxdo/commit/3eb58dec6e069182a852408c2900dff1b5f7fe83))
|
| 238 |
+
* 设置点赞上限 ([15a6ba9](https://www.github.com/14790897/auto-read-liunxdo/commit/15a6ba9cf5bccbea6ff33a5c0655e58b30e44854))
|
| 239 |
+
|
| 240 |
+
|
| 241 |
+
### Bug Fixes
|
| 242 |
+
|
| 243 |
+
* headless ([184461e](https://www.github.com/14790897/auto-read-liunxdo/commit/184461e27b62d0e57e0da4679b56b75e3f3a6535))
|
| 244 |
+
* localstorage无法访问 ([f2c1e9f](https://www.github.com/14790897/auto-read-liunxdo/commit/f2c1e9ff9ca27bd6d48296d3a3a0931b6184fba0))
|
| 245 |
+
* 不要刷新就启动 ([670992a](https://www.github.com/14790897/auto-read-liunxdo/commit/670992a91c031387c555682b0327cc782d309dbf))
|
| 246 |
+
* 修复略过已点赞按钮的逻辑错误 ([8e3089c](https://www.github.com/14790897/auto-read-liunxdo/commit/8e3089c7339fde603c26b67b0fcbb5fdc0138b3d))
|
| 247 |
+
* 修改等待元素 ([57ad719](https://www.github.com/14790897/auto-read-liunxdo/commit/57ad7190b0221181d746e88f1d83838b46a58dca))
|
| 248 |
+
* 去掉link限制,延迟2秒执行 ([98b0c93](https://www.github.com/14790897/auto-read-liunxdo/commit/98b0c936a359040ea5f5f68ed26dc02b72784c25))
|
| 249 |
+
* 去掉new ([f4d8c27](https://www.github.com/14790897/auto-read-liunxdo/commit/f4d8c270c20536bb60877183e9757e8069778dcb))
|
| 250 |
+
* 去除unread,因为可能没有文章 ([531d5b0](https://www.github.com/14790897/auto-read-liunxdo/commit/531d5b0923f4c676ff31fc1e6d5cdf43bc907443))
|
| 251 |
+
* 去除监听request ([32d4637](https://www.github.com/14790897/auto-read-liunxdo/commit/32d4637e79f78169f8f11f5970490a9052168b4d))
|
| 252 |
+
* 增加元素等待时间 ([e574e50](https://www.github.com/14790897/auto-read-liunxdo/commit/e574e509bfb676b43a5cb35bf34225ed6f7b5747))
|
| 253 |
+
* 增加等待时间 ([e439eee](https://www.github.com/14790897/auto-read-liunxdo/commit/e439eee13a631856fe8d524d1e7ab79eb2d618cd))
|
| 254 |
+
* 按钮位置移到到左下角 ([afd3394](https://www.github.com/14790897/auto-read-liunxdo/commit/afd33947af7bc86422857ad1452cb692a83707ca))
|
| 255 |
+
* 改变帖子位置 ([37c52ee](https://www.github.com/14790897/auto-read-liunxdo/commit/37c52eeee9296197334e0d929fd2249b8ef9adee))
|
| 256 |
+
* 改变帖子位置 ([0bd3aed](https://www.github.com/14790897/auto-read-liunxdo/commit/0bd3aede6a937c623d687e2edf59089511efa7e0))
|
| 257 |
+
* 暗色模式下看不清的问题 ([1616eb3](https://www.github.com/14790897/auto-read-liunxdo/commit/1616eb33b9432ee1636ee124acfd1860d4940669))
|
| 258 |
+
* 更新名字 ([3024a4c](https://www.github.com/14790897/auto-read-liunxdo/commit/3024a4c0b9ef9a691ef96b24c0e0943956e4b90d))
|
| 259 |
+
* 点赞429 ([a0d809c](https://www.github.com/14790897/auto-read-liunxdo/commit/a0d809ce4faeeb98c49f611eb78d384dc195b1e4))
|
| 260 |
+
* 点赞跳过加上英文title判断 ([36e05fb](https://www.github.com/14790897/auto-read-liunxdo/commit/36e05fb33ad9d507faae042e05a6a7821937c432))
|
| 261 |
+
* 环境变量名字错误 ([a3f8f1e](https://www.github.com/14790897/auto-read-liunxdo/commit/a3f8f1e1c123ff813c5443acb5a0f512493dc58f))
|
| 262 |
+
* 调整了浏览的速度 ([e85425f](https://www.github.com/14790897/auto-read-liunxdo/commit/e85425f3138c94a603793a1111dfabeb1c22e3c5))
|
| 263 |
+
* 阅读位置 ([2a3d1a3](https://www.github.com/14790897/auto-read-liunxdo/commit/2a3d1a3a25537cf9bacea3e21b2df646650fb67f))
|
| 264 |
+
* 页面刷新之后保持之前的状态 ([d1817b8](https://www.github.com/14790897/auto-read-liunxdo/commit/d1817b81fb9085bad392675422c1e56f5e01ce90))
|
| 265 |
+
* 默认不启动自动点赞 ([be0ca10](https://www.github.com/14790897/auto-read-liunxdo/commit/be0ca10aecb6ec1bcfb61af19e97b2536bfa1ad8))
|
Dockerfile
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM node:22-slim
|
| 2 |
+
|
| 3 |
+
WORKDIR /app
|
| 4 |
+
|
| 5 |
+
COPY package*.json ./
|
| 6 |
+
|
| 7 |
+
RUN apt update && apt install -y \
|
| 8 |
+
cron \
|
| 9 |
+
wget \
|
| 10 |
+
gnupg2 \
|
| 11 |
+
ca-certificates \
|
| 12 |
+
fonts-liberation \
|
| 13 |
+
libappindicator3-1 \
|
| 14 |
+
libasound2 \
|
| 15 |
+
libatk-bridge2.0-0 \
|
| 16 |
+
libatk1.0-0 \
|
| 17 |
+
libc6 \
|
| 18 |
+
libcairo2 \
|
| 19 |
+
libcups2 \
|
| 20 |
+
libdbus-1-3 \
|
| 21 |
+
libexpat1 \
|
| 22 |
+
libfontconfig1 \
|
| 23 |
+
libgbm1 \
|
| 24 |
+
libgcc1 \
|
| 25 |
+
libgdk-pixbuf2.0-0 \
|
| 26 |
+
libglib2.0-0 \
|
| 27 |
+
libgtk-3-0 \
|
| 28 |
+
libnspr4 \
|
| 29 |
+
libnss3 \
|
| 30 |
+
libpango-1.0-0 \
|
| 31 |
+
libpangocairo-1.0-0 \
|
| 32 |
+
libstdc++6 \
|
| 33 |
+
libx11-6 \
|
| 34 |
+
libx11-xcb1 \
|
| 35 |
+
libxcb1 \
|
| 36 |
+
libxcomposite1 \
|
| 37 |
+
libxcursor1 \
|
| 38 |
+
libxdamage1 \
|
| 39 |
+
libxext6 \
|
| 40 |
+
libxfixes3 \
|
| 41 |
+
libxi6 \
|
| 42 |
+
libxrandr2 \
|
| 43 |
+
libxrender1 \
|
| 44 |
+
libxss1 \
|
| 45 |
+
libxtst6 \
|
| 46 |
+
lsb-release \
|
| 47 |
+
xdg-utils \
|
| 48 |
+
xvfb && \
|
| 49 |
+
rm -rf /var/lib/apt/lists/*
|
| 50 |
+
|
| 51 |
+
RUN wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | gpg --dearmor -o /usr/share/keyrings/google-linux-signing-key.gpg && \
|
| 52 |
+
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 && \
|
| 53 |
+
apt update && apt install -y google-chrome-stable && \
|
| 54 |
+
rm -rf /var/lib/apt/lists/*
|
| 55 |
+
|
| 56 |
+
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true \
|
| 57 |
+
PUPPETEER_EXECUTABLE_PATH=/usr/bin/google-chrome
|
| 58 |
+
|
| 59 |
+
ENV TZ=Asia/Shanghai
|
| 60 |
+
|
| 61 |
+
RUN npm install
|
| 62 |
+
|
| 63 |
+
COPY . .
|
| 64 |
+
|
| 65 |
+
RUN chmod -R 777 /app
|
| 66 |
+
|
| 67 |
+
CMD ["node", "/app/bypasscf.js"]
|
Dockerfile-like-user
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 使用官方 Node.js 作为父镜像
|
| 2 |
+
FROM node:22-slim
|
| 3 |
+
|
| 4 |
+
# 设置工作目录
|
| 5 |
+
WORKDIR /app
|
| 6 |
+
|
| 7 |
+
# 复制 package.json 和 package-lock.json (如果存在)
|
| 8 |
+
COPY package*.json ./
|
| 9 |
+
|
| 10 |
+
# 安装 Puppeteer 依赖
|
| 11 |
+
RUN apt update && apt install -y \
|
| 12 |
+
cron\
|
| 13 |
+
wget \
|
| 14 |
+
ca-certificates \
|
| 15 |
+
fonts-liberation \
|
| 16 |
+
libappindicator3-1 \
|
| 17 |
+
libasound2 \
|
| 18 |
+
libatk-bridge2.0-0 \
|
| 19 |
+
libatk1.0-0 \
|
| 20 |
+
libc6 \
|
| 21 |
+
libcairo2 \
|
| 22 |
+
libcups2 \
|
| 23 |
+
libdbus-1-3 \
|
| 24 |
+
libexpat1 \
|
| 25 |
+
libfontconfig1 \
|
| 26 |
+
libgbm1 \
|
| 27 |
+
libgcc1 \
|
| 28 |
+
libgdk-pixbuf2.0-0 \
|
| 29 |
+
libglib2.0-0 \
|
| 30 |
+
libgtk-3-0 \
|
| 31 |
+
libnspr4 \
|
| 32 |
+
libnss3 \
|
| 33 |
+
libpango-1.0-0 \
|
| 34 |
+
libpangocairo-1.0-0 \
|
| 35 |
+
libstdc++6 \
|
| 36 |
+
libx11-6 \
|
| 37 |
+
libx11-xcb1 \
|
| 38 |
+
libxcb1 \
|
| 39 |
+
libxcomposite1 \
|
| 40 |
+
libxcursor1 \
|
| 41 |
+
libxdamage1 \
|
| 42 |
+
libxext6 \
|
| 43 |
+
libxfixes3 \
|
| 44 |
+
libxi6 \
|
| 45 |
+
libxrandr2 \
|
| 46 |
+
libxrender1 \
|
| 47 |
+
libxss1 \
|
| 48 |
+
libxtst6 \
|
| 49 |
+
lsb-release \
|
| 50 |
+
xdg-utils \
|
| 51 |
+
--no-install-recommends \
|
| 52 |
+
xvfb \
|
| 53 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 54 |
+
|
| 55 |
+
#时区为中国
|
| 56 |
+
ENV TZ=Asia/Shanghai
|
| 57 |
+
|
| 58 |
+
# 安装 Node.js 依赖
|
| 59 |
+
RUN npm install
|
| 60 |
+
|
| 61 |
+
# 将根目录复制到容器中
|
| 62 |
+
COPY . .
|
| 63 |
+
|
| 64 |
+
# 设置容器启动时运行的命令
|
| 65 |
+
CMD ["node", "/app/bypasscf_likeUser.js"]
|
LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2024 liuweiqing
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
+
SOFTWARE.
|
README.md
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: Auto Read
|
| 3 |
+
emoji: 🐳
|
| 4 |
+
colorFrom: purple
|
| 5 |
+
colorTo: gray
|
| 6 |
+
sdk: docker
|
| 7 |
+
app_port: 7860
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
[英文文档](./README_en.md)
|
| 11 |
+
###
|
| 12 |
+
新的依赖不能显示脚本运行日志了
|
| 13 |
+
|
| 14 |
+
## 使用方法一:油猴脚本(火狐不兼容,谷歌可以用)
|
| 15 |
+
|
| 16 |
+
油猴脚本代码在 index 开头的文件 中,建议在使用前将浏览器页面缩小,这样子可以一次滚动更多页面,读更多的回复
|
| 17 |
+
油猴脚本安装地址:
|
| 18 |
+
|
| 19 |
+
1. https://greasyfork.org/en/scripts/489464-auto-read 自动阅读随机点赞
|
| 20 |
+
2. https://greasyfork.org/en/scripts/506371-auto-like-specific-user 基于搜索到的帖子自动点赞特定用户
|
| 21 |
+
3. https://greasyfork.org/zh-CN/scripts/506567-auto-like-specific-user-base-on-activity 基于用户的活动自动点赞特定用户
|
| 22 |
+
|
| 23 |
+
## 使用方法二:本地运行(Windows 默认有头浏览器,Linux 默认无头浏览器)
|
| 24 |
+
|
| 25 |
+
### 1.设置环境变量
|
| 26 |
+
|
| 27 |
+
.env 里面设置用户名 密码 以及其它 env 里面指明的信息
|
| 28 |
+
|
| 29 |
+
### 2.运行
|
| 30 |
+
|
| 31 |
+
#### Windows
|
| 32 |
+
|
| 33 |
+
```sh
|
| 34 |
+
npm install
|
| 35 |
+
# 自动阅读随机点赞
|
| 36 |
+
node .\bypasscf.js
|
| 37 |
+
# 自动点赞特定用户
|
| 38 |
+
## windows
|
| 39 |
+
set LIKE_SPECIFIC_USER=true && node .\bypasscf.js
|
| 40 |
+
## powershell
|
| 41 |
+
$env:LIKE_SPECIFIC_USER = "true"
|
| 42 |
+
node .\bypasscf.js
|
| 43 |
+
## linux
|
| 44 |
+
LIKE_SPECIFIC_USER=true node ./bypasscf.js
|
| 45 |
+
```
|
| 46 |
+
|
| 47 |
+
#### Linux 额外安装以下包,运行命令相同
|
| 48 |
+
|
| 49 |
+
```sh
|
| 50 |
+
sudo apt update
|
| 51 |
+
wget -qO- https://deb.nodesource.com/setup_20.x | sudo -E bash - #安装node的最新源
|
| 52 |
+
sudo apt install nodejs -y
|
| 53 |
+
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
|
| 54 |
+
sudo snap install chromium
|
| 55 |
+
|
| 56 |
+
```
|
| 57 |
+
|
| 58 |
+
```sh
|
| 59 |
+
npm install
|
| 60 |
+
# 自动阅读随机点赞
|
| 61 |
+
node .\bypasscf.js
|
| 62 |
+
# 自动点赞特定用户
|
| 63 |
+
node .\bypasscf_likeUser.js
|
| 64 |
+
```
|
| 65 |
+
|
| 66 |
+
## 使用方法三:GitHub Action 每天 4 点阅读
|
| 67 |
+
|
| 68 |
+
#### 说明: 每天运行,每次二十分钟(可自行修改启动时间和持续时间,代码.github\workflows\cron_bypassCF.yaml 和 .github\workflows\cron_bypassCF_likeUser.yaml)
|
| 69 |
+
|
| 70 |
+
### 1. fork 仓库
|
| 71 |
+
|
| 72 |
+
### 2.设置环境变量
|
| 73 |
+
|
| 74 |
+
在 GitHub action 的 secrets 设置用户名密码(变量名参考.env 中给出的),这里无法读取.env 变量
|
| 75 |
+

|
| 76 |
+
|
| 77 |
+
### 3.启动 workflow
|
| 78 |
+
|
| 79 |
+
教程:https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web?tab=readme-ov-file#enable-automatic-updates
|
| 80 |
+
|
| 81 |
+
## 使用方法四:docker 运行
|
| 82 |
+
|
| 83 |
+
### 1.立刻执行
|
| 84 |
+
|
| 85 |
+
克隆仓库,在`docker-compose.yml`里面设置环境变量,然后运行
|
| 86 |
+
|
| 87 |
+
```sh
|
| 88 |
+
# 自动阅读随机点赞
|
| 89 |
+
docker-compose up -d
|
| 90 |
+
# 自动点赞特定用户
|
| 91 |
+
docker-compose -f docker-compose-like-user.yml up -d
|
| 92 |
+
```
|
| 93 |
+
|
| 94 |
+
查看日志
|
| 95 |
+
|
| 96 |
+
```sh
|
| 97 |
+
docker-compose logs -f
|
| 98 |
+
```
|
| 99 |
+
|
| 100 |
+
### 2.定时运行
|
| 101 |
+
|
| 102 |
+
```sh
|
| 103 |
+
chmod +x cron.sh
|
| 104 |
+
|
| 105 |
+
crontab -e
|
| 106 |
+
```
|
| 107 |
+
|
| 108 |
+
手动添加以下内容(功能是每天六点执行)
|
| 109 |
+
|
| 110 |
+
```sh
|
| 111 |
+
0 6 * * * /root/auto-read-liunxdo/cron.sh # 注意这是示例目录,要改为所在仓库目录的cron.sh(使用pwd查看所在目录)
|
| 112 |
+
```
|
| 113 |
+
|
| 114 |
+
## 如何增加基于 discourse 的其它网站的支持?
|
| 115 |
+
|
| 116 |
+
1. 修改 index_passage_list 中的// @match ,根据其它示例网站,填写新的 url,此外在脚本开头的 possibleBaseURLs 中也添加 url
|
| 117 |
+
2. 服务器运行时,还需要修改.env 下的 WEBSITE 变量为对应的网址(如果网址是不存在原先脚本的,需要修改 external.js 中对应的部分,重新构建镜像)
|
| 118 |
+
3. 小众软件论坛只能在 Windows 下运行,所以需要使用定制版 action: [.github\workflows\windows_cron_bypassCF.yaml](https://github.com/14790897/auto-read-liunxdo/blob/main/.github/workflows/windows_cron_bypassCF.yaml)
|
| 119 |
+
|
| 120 |
+
#### 随笔
|
| 121 |
+
|
| 122 |
+
开发中遇到的问题:
|
| 123 |
+
问:TimeoutError: Navigation timeout of 30000 ms exceeded 为什么 puppeteer 经常出现这个错误?
|
| 124 |
+
答:linux 使用{waitUntil: 'domcontentloaded'}后,情况大大好转,但还是有时出现,Windows 未曾出现此问题 [见文章分析](随笔.md) 目前发现存在不点击登录按钮导致超时,已解决(原因未知)
|
| 125 |
+
|
| 126 |
+
这个也可能是因为登陆太频繁导致的,太快的登陆太多的账号
|
| 127 |
+
|
| 128 |
+
更少见的情况其实是密码错误,还有账户未激活
|
| 129 |
+
|
| 130 |
+
#### 待做
|
| 131 |
+
|
| 132 |
+
1. TimeoutError 时候可以捕获错误然后关掉当前浏览器重新再开一次(已经实现刷新页面重新登录但是效果不好)
|
| 133 |
+
2. 自动阅读脚本可以加一个阅读速度选项(快,慢,始终),因为有用户反应读的太快了(应该是他们屏幕太小)
|
| 134 |
+
3. https://github.com/14790897/auto-read-liunxdo/issues/67
|
| 135 |
+
|
| 136 |
+
## 感谢
|
| 137 |
+
|
| 138 |
+
https://linux.do/t/topic/106471
|
| 139 |
+
|
| 140 |
+
#### 使用 index_likeUser 点赞记录
|
| 141 |
+
|
| 142 |
+
9.2 handsome
|
| 143 |
+
9.3 lwyt
|
| 144 |
+
9.4 hindex
|
| 145 |
+
9.5 endercat
|
| 146 |
+
9.6 mrliushaopu
|
| 147 |
+
9.6 MonsterKing
|
| 148 |
+
9.7 zhiyang
|
| 149 |
+
9.8 xibalama
|
| 150 |
+
9.9 seeyourface LangYnn
|
| 151 |
+
9.10 YYWD
|
| 152 |
+
9.11 zhong_little
|
| 153 |
+
9.12 LangYnn
|
| 154 |
+
9.13 YYWD
|
| 155 |
+
9.14 wii
|
| 156 |
+
9.15 RunningSnail
|
| 157 |
+
9.16 ll0, mojihua,ywxh
|
| 158 |
+
9.17 GlycoProtein
|
| 159 |
+
9.18 Clarke.L Vyvx
|
| 160 |
+
9.19 azrael
|
| 161 |
+
9.20 Philippa shenchong
|
| 162 |
+
9.21lllyz hwang
|
| 163 |
+
9.22 include Unique
|
| 164 |
+
9.24 taobug
|
| 165 |
+
9.25 CoolMan
|
| 166 |
+
9.26 Madara jonty
|
| 167 |
+
9.27 jonty(不小心点了两次)
|
| 168 |
+
9.29 haoc louis miku8miku
|
| 169 |
+
9.30 horrZzz zxcv
|
| 170 |
+
10.1 bbb
|
| 171 |
+
10.2 zyzcom
|
| 172 |
+
10.4 jeff0319 Game0526 LeoMeng
|
| 173 |
+
10.5 kobe1 pangbaibai
|
| 174 |
+
10.6 xfgb lentikr
|
| 175 |
+
10.7 PlayMcBKuwu Tim88
|
| 176 |
+
10.10 elfmaid
|
| 177 |
+
10.11 yu_sheng orxvan l444736 time-wanderer
|
| 178 |
+
10.14 time-wanderer OrangeQiu
|
| 179 |
+
Timmy_0
|
| 180 |
+
SINOPEC
|
| 181 |
+
onePiece HelShiJiasi delph1s
|
| 182 |
+
|
| 183 |
+
<!--
|
| 184 |
+
代码:
|
| 185 |
+
https://github.com/14790897/auto-read-liunxdo
|
| 186 |
+
## 手动运行
|
| 187 |
+
|
| 188 |
+
### 1.设置环境变量
|
| 189 |
+
|
| 190 |
+
.env 里面设置用户名 密码
|
| 191 |
+
|
| 192 |
+
### 2.运行
|
| 193 |
+
|
| 194 |
+
|
| 195 |
+
```sh
|
| 196 |
+
|
| 197 |
+
npm install
|
| 198 |
+
|
| 199 |
+
node .\bypasscf.js
|
| 200 |
+
|
| 201 |
+
```
|
| 202 |
+
## GitHub Action 每天 阅读
|
| 203 |
+
|
| 204 |
+
(可自行修改启动时间和持续时间,代码.github\workflows\cron_bypassCF.yaml)
|
| 205 |
+
|
| 206 |
+
### 1. fork 仓库
|
| 207 |
+
|
| 208 |
+
### 2.设置环境变量
|
| 209 |
+
|
| 210 |
+
在 GitHub action 的 secrets 设置用户名密码(变量名参考.env 中给出的)(.env 里面设置用户名密码在这里无效)
|
| 211 |
+

|
| 212 |
+
|
| 213 |
+
### 3.启动 workflow
|
| 214 |
+
|
| 215 |
+
教程:https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web?tab=readme-ov-file#enable-automatic-updates
|
| 216 |
+
|
| 217 |
+
## 演示视频
|
| 218 |
+
<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> -->
|
README_en.md
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[中文文档](./README_zh.md)
|
| 2 |
+
|
| 3 |
+
## Method 1: Tampermonkey Script
|
| 4 |
+
|
| 5 |
+
The Tampermonkey script can be accessed in the `index_passage_list`. You can find and install the script from Greasy Fork:
|
| 6 |
+
|
| 7 |
+

|
| 8 |
+
[Tampermonkey Script: Auto Read](https://greasyfork.org/en/scripts/489464-auto-read)
|
| 9 |
+
|
| 10 |
+
## Method 2: Headless Execution with Puppeteer
|
| 11 |
+
|
| 12 |
+
### 1. Setting Environment Variables
|
| 13 |
+
|
| 14 |
+
Set your username and password in the `.env` file.
|
| 15 |
+
|
| 16 |
+
### 2. Execution
|
| 17 |
+
|
| 18 |
+
#### For Windows
|
| 19 |
+
|
| 20 |
+
Run the following commands:
|
| 21 |
+
|
| 22 |
+
```sh
|
| 23 |
+
npm install
|
| 24 |
+
node .\pteer.js
|
| 25 |
+
```
|
| 26 |
+
|
| 27 |
+
#### For Linux (additional packages needed)
|
| 28 |
+
|
| 29 |
+
Install the required packages and run the same commands as for Windows:
|
| 30 |
+
|
| 31 |
+
```sh
|
| 32 |
+
sudo apt-get update
|
| 33 |
+
sudo apt install nodejs npm -y
|
| 34 |
+
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
|
| 35 |
+
```
|
| 36 |
+
|
| 37 |
+
## Method 3: GitHub Actions for Daily Reading at Midnight
|
| 38 |
+
|
| 39 |
+
Modify the start time and duration as needed. The code is located in `.github/workflows/cron_read.yaml`.
|
| 40 |
+
|
| 41 |
+
### 1. Fork the Repository
|
| 42 |
+
|
| 43 |
+
### 2. Set Environment Variables
|
| 44 |
+
|
| 45 |
+
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.
|
| 46 |
+

|
| 47 |
+
|
| 48 |
+
### 3. Start the Workflow
|
| 49 |
+
|
| 50 |
+
Tutorial: [Enable Automatic Updates](https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web?tab=readme-ov-file#enable-automatic-updates)
|
| 51 |
+
|
| 52 |
+
## Method 4: Docker Execution
|
| 53 |
+
|
| 54 |
+
### 1. Immediate Execution
|
| 55 |
+
|
| 56 |
+
Clone the repository, set environment variables in `docker-compose.yml`, and run:
|
| 57 |
+
|
| 58 |
+
```sh
|
| 59 |
+
docker-compose up -d
|
| 60 |
+
```
|
| 61 |
+
|
| 62 |
+
To view logs:
|
| 63 |
+
|
| 64 |
+
```sh
|
| 65 |
+
docker-compose logs -f
|
| 66 |
+
```
|
| 67 |
+
|
| 68 |
+
### 2. Scheduled Execution
|
| 69 |
+
|
| 70 |
+
Set permissions and edit the crontab:
|
| 71 |
+
|
| 72 |
+
```sh
|
| 73 |
+
chmod +x cron.sh
|
| 74 |
+
crontab -e
|
| 75 |
+
```
|
| 76 |
+
|
| 77 |
+
Manually add the following entry (to execute daily at 6 AM, adjust the directory as needed):
|
| 78 |
+
|
| 79 |
+
```sh
|
| 80 |
+
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)
|
| 81 |
+
```
|
| 82 |
+
|
| 83 |
+
#### Additional Information
|
| 84 |
+
|
| 85 |
+
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:
|
| 86 |
+
|
| 87 |
+
```sh
|
| 88 |
+
localStorage.setItem("read", "true"); // Initially disables auto-scroll
|
| 89 |
+
localStorage.setItem("autoLikeEnabled", "true"); // Auto-liking is enabled by default
|
| 90 |
+
```
|
Thumbs.db
ADDED
|
Binary file (10.8 kB). View file
|
|
|
bypasscf.js
ADDED
|
@@ -0,0 +1,536 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import fs from "fs";
|
| 2 |
+
import path from "path";
|
| 3 |
+
import puppeteer from "puppeteer-extra";
|
| 4 |
+
import StealthPlugin from "puppeteer-extra-plugin-stealth";
|
| 5 |
+
import dotenv from "dotenv";
|
| 6 |
+
import { fileURLToPath } from "url";
|
| 7 |
+
import { dirname, join } from "path";
|
| 8 |
+
import TelegramBot from "node-telegram-bot-api";
|
| 9 |
+
|
| 10 |
+
dotenv.config();
|
| 11 |
+
|
| 12 |
+
// 截图保存的文件夹
|
| 13 |
+
// const screenshotDir = "screenshots";
|
| 14 |
+
// if (!fs.existsSync(screenshotDir)) {
|
| 15 |
+
// fs.mkdirSync(screenshotDir);
|
| 16 |
+
// }
|
| 17 |
+
puppeteer.use(StealthPlugin());
|
| 18 |
+
|
| 19 |
+
// Load the default .env file
|
| 20 |
+
if (fs.existsSync(".env.local")) {
|
| 21 |
+
console.log("Using .env.local file to supply config environment variables");
|
| 22 |
+
const envConfig = dotenv.parse(fs.readFileSync(".env.local"));
|
| 23 |
+
for (const k in envConfig) {
|
| 24 |
+
process.env[k] = envConfig[k];
|
| 25 |
+
}
|
| 26 |
+
} else {
|
| 27 |
+
console.log(
|
| 28 |
+
"Using .env file to supply config environment variables, you can create a .env.local file to overwrite defaults, it doesn't upload to git"
|
| 29 |
+
);
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
// 读取以分钟为单位的运行时间限制
|
| 33 |
+
const runTimeLimitMinutes = process.env.RUN_TIME_LIMIT_MINUTES || 20;
|
| 34 |
+
|
| 35 |
+
// 将分钟转换为毫秒
|
| 36 |
+
const runTimeLimitMillis = runTimeLimitMinutes * 60 * 1000;
|
| 37 |
+
|
| 38 |
+
console.log(
|
| 39 |
+
`运行时间限制为:${runTimeLimitMinutes} 分钟 (${runTimeLimitMillis} 毫秒)`
|
| 40 |
+
);
|
| 41 |
+
|
| 42 |
+
// 设置一个定时器,在运行时间到达时终止进程
|
| 43 |
+
const shutdownTimer = setTimeout(() => {
|
| 44 |
+
console.log("时间到,Reached time limit, shutting down the process...");
|
| 45 |
+
process.exit(0); // 退出进程
|
| 46 |
+
}, runTimeLimitMillis);
|
| 47 |
+
|
| 48 |
+
const token = process.env.TELEGRAM_BOT_TOKEN;
|
| 49 |
+
const chatId = process.env.TELEGRAM_CHAT_ID;
|
| 50 |
+
const specificUser = process.env.SPECIFIC_USER || "14790897";
|
| 51 |
+
const maxConcurrentAccounts = 4; // 每批最多同时运行的账号数
|
| 52 |
+
const usernames = process.env.USERNAMES.split(",");
|
| 53 |
+
const passwords = process.env.PASSWORDS.split(",");
|
| 54 |
+
const loginUrl = process.env.WEBSITE || "https://linux.do"; //在GitHub action环境里它不能读取默认环境变量,只能在这里设置默认值
|
| 55 |
+
const delayBetweenInstances = 10000;
|
| 56 |
+
const totalAccounts = usernames.length; // 总的账号数
|
| 57 |
+
const delayBetweenBatches =
|
| 58 |
+
runTimeLimitMillis / Math.ceil(totalAccounts / maxConcurrentAccounts);
|
| 59 |
+
const isLikeSpecificUser = process.env.LIKE_SPECIFIC_USER || "false";
|
| 60 |
+
const isAutoLike = process.env.AUTO_LIKE || "true";
|
| 61 |
+
let bot;
|
| 62 |
+
if (token && chatId) {
|
| 63 |
+
bot = new TelegramBot(token);
|
| 64 |
+
}
|
| 65 |
+
function sendToTelegram(message) {
|
| 66 |
+
if (!bot) return;
|
| 67 |
+
|
| 68 |
+
bot
|
| 69 |
+
.sendMessage(chatId, message)
|
| 70 |
+
.then(() => {
|
| 71 |
+
console.log("Telegram message sent successfully");
|
| 72 |
+
})
|
| 73 |
+
.catch((error) => {
|
| 74 |
+
console.error("Error sending Telegram message:", error);
|
| 75 |
+
});
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
//随机等待时间
|
| 79 |
+
function delayClick(time) {
|
| 80 |
+
return new Promise(function (resolve) {
|
| 81 |
+
setTimeout(resolve, time);
|
| 82 |
+
});
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
(async () => {
|
| 86 |
+
try {
|
| 87 |
+
if (usernames.length !== passwords.length) {
|
| 88 |
+
console.log(usernames.length, usernames, passwords.length, passwords);
|
| 89 |
+
throw new Error("用户名和密码的数量不匹配!");
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
// 并发启动浏览器实例进行登录
|
| 93 |
+
const loginTasks = usernames.map((username, index) => {
|
| 94 |
+
const password = passwords[index];
|
| 95 |
+
const delay = (index % maxConcurrentAccounts) * delayBetweenInstances; // 使得每一组内的浏览器可以分开启动
|
| 96 |
+
return () => {
|
| 97 |
+
// 确保这里返回的是函数
|
| 98 |
+
return new Promise((resolve, reject) => {
|
| 99 |
+
setTimeout(() => {
|
| 100 |
+
launchBrowserForUser(username, password)
|
| 101 |
+
.then(resolve)
|
| 102 |
+
.catch(reject);
|
| 103 |
+
}, delay);
|
| 104 |
+
});
|
| 105 |
+
};
|
| 106 |
+
});
|
| 107 |
+
// 依次执行每个批次的任务
|
| 108 |
+
for (let i = 0; i < totalAccounts; i += maxConcurrentAccounts) {
|
| 109 |
+
console.log(`当前批次:${i + 1} - ${i + maxConcurrentAccounts}`);
|
| 110 |
+
// 执行每批次最多 4 个账号
|
| 111 |
+
const batch = loginTasks
|
| 112 |
+
.slice(i, i + maxConcurrentAccounts)
|
| 113 |
+
.map(async (task) => {
|
| 114 |
+
const { browser } = await task(); // 运行任务并获取浏览器实例
|
| 115 |
+
return browser;
|
| 116 |
+
}); // 等待当前批次的任务完成
|
| 117 |
+
const browsers = await Promise.all(batch); // Task里面的任务本身是没有进行await的, 所以会继续执行下面的代码
|
| 118 |
+
|
| 119 |
+
// 如果还有下一个批次,等待指定的时间,同时,如果总共只有一个账号,也需要继续运行
|
| 120 |
+
if (i + maxConcurrentAccounts < totalAccounts || i === 0) {
|
| 121 |
+
console.log(`等待 ${delayBetweenBatches / 1000} 秒`);
|
| 122 |
+
await new Promise((resolve) =>
|
| 123 |
+
setTimeout(resolve, delayBetweenBatches)
|
| 124 |
+
);
|
| 125 |
+
} else {
|
| 126 |
+
console.log("没有下一个批次,即将结束");
|
| 127 |
+
}
|
| 128 |
+
console.log(
|
| 129 |
+
`批次 ${
|
| 130 |
+
Math.floor(i / maxConcurrentAccounts) + 1
|
| 131 |
+
} 完成,关闭浏览器...,浏览器对象:${browsers}`
|
| 132 |
+
);
|
| 133 |
+
// 关闭所有浏览器实例
|
| 134 |
+
for (const browser of browsers) {
|
| 135 |
+
await browser.close();
|
| 136 |
+
}
|
| 137 |
+
}
|
| 138 |
+
|
| 139 |
+
console.log("所有账号登录操作已完成");
|
| 140 |
+
// 等待所有登录操作完成
|
| 141 |
+
// await Promise.all(loginTasks);
|
| 142 |
+
} catch (error) {
|
| 143 |
+
// 错误处理逻辑
|
| 144 |
+
console.error("发生错误:", error);
|
| 145 |
+
if (token && chatId) {
|
| 146 |
+
sendToTelegram(`${error.message}`);
|
| 147 |
+
}
|
| 148 |
+
}
|
| 149 |
+
})();
|
| 150 |
+
async function launchBrowserForUser(username, password) {
|
| 151 |
+
let browser = null; // 在 try 之外声明 browser 变量
|
| 152 |
+
try {
|
| 153 |
+
console.log("当前用户:", username);
|
| 154 |
+
const browserOptions = {
|
| 155 |
+
headless: "auto",
|
| 156 |
+
args: ["--no-sandbox", "--disable-setuid-sandbox"], // Linux 需要的安全设置
|
| 157 |
+
};
|
| 158 |
+
|
| 159 |
+
// 如果环境变量不是 'dev',则添加代理配置
|
| 160 |
+
// if (process.env.ENVIRONMENT !== "dev") {
|
| 161 |
+
// browserOptions["proxy"] = {
|
| 162 |
+
// host: "38.154.227.167",
|
| 163 |
+
// port: "5868",
|
| 164 |
+
// username: "pqxujuyl",
|
| 165 |
+
// password: "y1nmb5kjbz9t",
|
| 166 |
+
// };
|
| 167 |
+
// }
|
| 168 |
+
|
| 169 |
+
var { connect } = await import("puppeteer-real-browser");
|
| 170 |
+
const { page, browser: newBrowser } = await connect(browserOptions);
|
| 171 |
+
browser = newBrowser; // 将 browser 初始化
|
| 172 |
+
// 启动截图功能
|
| 173 |
+
// takeScreenshots(page);
|
| 174 |
+
//登录操作
|
| 175 |
+
await navigatePage(loginUrl, page, browser);
|
| 176 |
+
await delayClick(8000);
|
| 177 |
+
// 设置额外的 headers
|
| 178 |
+
await page.setExtraHTTPHeaders({
|
| 179 |
+
"accept-language": "en-US,en;q=0.9",
|
| 180 |
+
});
|
| 181 |
+
// 验证 `navigator.webdriver` 属性是否为 undefined
|
| 182 |
+
// const isWebDriverUndefined = await page.evaluate(() => {
|
| 183 |
+
// return `${navigator.webdriver}`;
|
| 184 |
+
// });
|
| 185 |
+
|
| 186 |
+
// console.log("navigator.webdriver is :", isWebDriverUndefined); // 输出应为 false
|
| 187 |
+
page.on("pageerror", (error) => {
|
| 188 |
+
console.error(`Page error: ${error.message}`);
|
| 189 |
+
});
|
| 190 |
+
page.on("error", async (error) => {
|
| 191 |
+
// console.error(`Error: ${error.message}`);
|
| 192 |
+
// 检查是否是 localStorage 的访问权限错误
|
| 193 |
+
if (
|
| 194 |
+
error.message.includes(
|
| 195 |
+
"Failed to read the 'localStorage' property from 'Window'"
|
| 196 |
+
)
|
| 197 |
+
) {
|
| 198 |
+
console.log("Trying to refresh the page to resolve the issue...");
|
| 199 |
+
await page.reload(); // 刷新页面
|
| 200 |
+
// 重新尝试你的操作...
|
| 201 |
+
}
|
| 202 |
+
});
|
| 203 |
+
page.on("console", async (msg) => {
|
| 204 |
+
// console.log("PAGE LOG:", msg.text());
|
| 205 |
+
// 使用一个标志变量来检测是否已经刷新过页面
|
| 206 |
+
if (
|
| 207 |
+
!page._isReloaded &&
|
| 208 |
+
msg.text().includes("the server responded with a status of 429")
|
| 209 |
+
) {
|
| 210 |
+
// 设置标志变量为 true,表示即将刷新页面
|
| 211 |
+
page._isReloaded = true;
|
| 212 |
+
//由于油候脚本它这个时候可能会导航到新的网页,会导致直接执行代码报错,所以使用这个来在每个新网页加载之前来执行
|
| 213 |
+
await page.evaluateOnNewDocument(() => {
|
| 214 |
+
localStorage.setItem("autoLikeEnabled", "false");
|
| 215 |
+
});
|
| 216 |
+
// 等待一段时间,比如 3 秒
|
| 217 |
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
| 218 |
+
console.log("Retrying now...");
|
| 219 |
+
// 尝试刷新页面
|
| 220 |
+
// await page.reload();
|
| 221 |
+
}
|
| 222 |
+
});
|
| 223 |
+
// //登录操作
|
| 224 |
+
console.log("登录操作");
|
| 225 |
+
await login(page, username, password);
|
| 226 |
+
// 查找具有类名 "avatar" 的 img 元素验证登录是否成功
|
| 227 |
+
const avatarImg = await page.$("img.avatar");
|
| 228 |
+
|
| 229 |
+
if (avatarImg) {
|
| 230 |
+
console.log("找到avatarImg,登录成功");
|
| 231 |
+
} else {
|
| 232 |
+
console.log("未找到avatarImg,登录失败");
|
| 233 |
+
throw new Error("登录失败");
|
| 234 |
+
}
|
| 235 |
+
|
| 236 |
+
//真正执行阅读脚本
|
| 237 |
+
let externalScriptPath;
|
| 238 |
+
if (isLikeSpecificUser === "true") {
|
| 239 |
+
externalScriptPath = path.join(
|
| 240 |
+
dirname(fileURLToPath(import.meta.url)),
|
| 241 |
+
"index_likeUser.js"
|
| 242 |
+
);
|
| 243 |
+
} else {
|
| 244 |
+
externalScriptPath = path.join(
|
| 245 |
+
dirname(fileURLToPath(import.meta.url)),
|
| 246 |
+
"index.js"
|
| 247 |
+
);
|
| 248 |
+
}
|
| 249 |
+
const externalScript = fs.readFileSync(externalScriptPath, "utf8");
|
| 250 |
+
|
| 251 |
+
// 在每个新的文档加载时执行外部脚本
|
| 252 |
+
await page.evaluateOnNewDocument(
|
| 253 |
+
(...args) => {
|
| 254 |
+
const [specificUser, scriptToEval, isAutoLike] = args;
|
| 255 |
+
localStorage.setItem("read", true);
|
| 256 |
+
localStorage.setItem("specificUser", specificUser);
|
| 257 |
+
localStorage.setItem("isFirstRun", "false");
|
| 258 |
+
localStorage.setItem("autoLikeEnabled", isAutoLike);
|
| 259 |
+
console.log("当前点赞用户:", specificUser);
|
| 260 |
+
eval(scriptToEval);
|
| 261 |
+
},
|
| 262 |
+
specificUser,
|
| 263 |
+
externalScript,
|
| 264 |
+
isAutoLike
|
| 265 |
+
); //变量必须从外部显示的传入, 因为在浏览器上下文它是读取不了的
|
| 266 |
+
// 添加一个监听器来监听每次页面加载完成的事件
|
| 267 |
+
page.on("load", async () => {
|
| 268 |
+
// await page.evaluate(externalScript); //因为这个是在页面加载好之后执行的,而脚本是在页面加载好时刻来判断是否要执行,由于已经加载好了,脚本就不会起作用
|
| 269 |
+
});
|
| 270 |
+
// 如果是Linuxdo,就导航到我的帖子,但我感觉这里写没什么用,因为外部脚本已经定义好了,不对,这里不会点击按钮,所以不会跳转,需要手动跳转
|
| 271 |
+
if (loginUrl == "https://linux.do") {
|
| 272 |
+
await page.goto("https://linux.do/t/topic/13716/630", {
|
| 273 |
+
waitUntil: "domcontentloaded",
|
| 274 |
+
});
|
| 275 |
+
} else if (loginUrl == "https://meta.appinn.net") {
|
| 276 |
+
await page.goto("https://meta.appinn.net/t/topic/52006", {
|
| 277 |
+
waitUntil: "domcontentloaded",
|
| 278 |
+
});
|
| 279 |
+
} else {
|
| 280 |
+
await page.goto(`${loginUrl}/t/topic/1`, {
|
| 281 |
+
waitUntil: "domcontentloaded",
|
| 282 |
+
});
|
| 283 |
+
}
|
| 284 |
+
if (token && chatId) {
|
| 285 |
+
sendToTelegram(`${username} 登录成功`);
|
| 286 |
+
}
|
| 287 |
+
return { browser };
|
| 288 |
+
} catch (err) {
|
| 289 |
+
// throw new Error(err);
|
| 290 |
+
console.log("Error in launchBrowserForUser:", err);
|
| 291 |
+
if (token && chatId) {
|
| 292 |
+
sendToTelegram(`${err.message}`);
|
| 293 |
+
}
|
| 294 |
+
return { browser }; // 错误时仍然返回 browser
|
| 295 |
+
}
|
| 296 |
+
}
|
| 297 |
+
async function login(page, username, password, retryCount = 3) {
|
| 298 |
+
// 使用XPath查询找到包含"登录"或"login"文本的按钮
|
| 299 |
+
let loginButtonFound = await page.evaluate(() => {
|
| 300 |
+
let loginButton = Array.from(document.querySelectorAll("button")).find(
|
| 301 |
+
(button) =>
|
| 302 |
+
button.textContent.includes("登录") ||
|
| 303 |
+
button.textContent.includes("login")
|
| 304 |
+
); // 注意loginButton 变量在外部作用域中是无法被 page.evaluate 内部的代码直接修改的。page.evaluate 的代码是在浏览器环境中执行的,这意味着它们无法直接影响 Node.js 环境中的变量
|
| 305 |
+
// 如果没有找到,尝试根据类名查找
|
| 306 |
+
if (!loginButton) {
|
| 307 |
+
loginButton = document.querySelector(".login-button");
|
| 308 |
+
}
|
| 309 |
+
if (loginButton) {
|
| 310 |
+
loginButton.click();
|
| 311 |
+
console.log("Login button clicked.");
|
| 312 |
+
return true; // 返回true表示找到了按钮并点击了
|
| 313 |
+
} else {
|
| 314 |
+
console.log("Login button not found.");
|
| 315 |
+
return false; // 返回false表示没有找到按钮
|
| 316 |
+
}
|
| 317 |
+
});
|
| 318 |
+
if (!loginButtonFound) {
|
| 319 |
+
if (loginUrl == "https://meta.appinn.net") {
|
| 320 |
+
await page.goto("https://meta.appinn.net/t/topic/52006", {
|
| 321 |
+
waitUntil: "domcontentloaded",
|
| 322 |
+
});
|
| 323 |
+
await page.click(".discourse-reactions-reaction-button");
|
| 324 |
+
} else {
|
| 325 |
+
await page.goto(`${loginUrl}/t/topic/1`, {
|
| 326 |
+
waitUntil: "domcontentloaded",
|
| 327 |
+
});
|
| 328 |
+
await page.click(".discourse-reactions-reaction-button");
|
| 329 |
+
}
|
| 330 |
+
}
|
| 331 |
+
// 等待用户名输入框加载
|
| 332 |
+
await page.waitForSelector("#login-account-name");
|
| 333 |
+
// 模拟人类在找到输入框后的短暂停顿
|
| 334 |
+
await delayClick(1000); // 延迟500毫秒
|
| 335 |
+
// 清空输入框并输入用户名
|
| 336 |
+
await page.click("#login-account-name", { clickCount: 3 });
|
| 337 |
+
await page.type("#login-account-name", username, {
|
| 338 |
+
delay: 100,
|
| 339 |
+
}); // 输入时在每个按键之间添加额外的延迟
|
| 340 |
+
await delayClick(1000);
|
| 341 |
+
// 等待密码输入框加载
|
| 342 |
+
// await page.waitForSelector("#login-account-password");
|
| 343 |
+
// 模拟人类在输入用户名后的短暂停顿
|
| 344 |
+
// delayClick; // 清空输入框并输入密码
|
| 345 |
+
await page.click("#login-account-password", { clickCount: 3 });
|
| 346 |
+
await page.type("#login-account-password", password, {
|
| 347 |
+
delay: 100,
|
| 348 |
+
});
|
| 349 |
+
|
| 350 |
+
// 模拟人类在输入完成后思考的短暂停顿
|
| 351 |
+
await delayClick(1000);
|
| 352 |
+
|
| 353 |
+
// 假设登录按钮的ID是'login-button',点击登录按钮
|
| 354 |
+
await page.waitForSelector("#login-button");
|
| 355 |
+
await delayClick(1000); // 模拟在点击登录按钮前的短暂停顿
|
| 356 |
+
await page.click("#login-button");
|
| 357 |
+
try {
|
| 358 |
+
await Promise.all([
|
| 359 |
+
page.waitForNavigation({ waitUntil: "domcontentloaded" }), // 等待 页面跳转 DOMContentLoaded 事件
|
| 360 |
+
// 去掉上面一行会报错:Error: Execution context was destroyed, most likely because of a navigation. 可能是因为之后没等页面加载完成就执行了脚本
|
| 361 |
+
page.click("#login-button", { force: true }), // 点击登录按钮触发跳转
|
| 362 |
+
]); //注意如果登录失败,这里会一直等待跳转,导致脚本执行失败 这点四个月之前你就发现了结果今天又遇到(有个用户遇到了https://linux.do/t/topic/169209/82),但是你没有在这个报错你提示我8.5
|
| 363 |
+
} catch (error) {
|
| 364 |
+
const alertError = await page.$(".alert.alert-error");
|
| 365 |
+
if (alertError) {
|
| 366 |
+
const alertText = await page.evaluate((el) => el.innerText, alertError); // 使用 evaluate 获取 innerText
|
| 367 |
+
if (
|
| 368 |
+
alertText.includes("incorrect") ||
|
| 369 |
+
alertText.includes("Incorrect ") ||
|
| 370 |
+
alertText.includes("不正确")
|
| 371 |
+
) {
|
| 372 |
+
throw new Error(
|
| 373 |
+
`非超时错误,请检查用户名密码是否正确,失败用户 ${username}, 错误信息:${alertText}`
|
| 374 |
+
);
|
| 375 |
+
} else {
|
| 376 |
+
throw new Error(
|
| 377 |
+
`非超时错误,也不是密码错误,可能是IP导致,需使用中国美国香港台湾IP,失败用户 ${username},错误信息:${alertText}`
|
| 378 |
+
);
|
| 379 |
+
}
|
| 380 |
+
} else {
|
| 381 |
+
if (retryCount > 0) {
|
| 382 |
+
console.log("Retrying login...");
|
| 383 |
+
await page.reload({ waitUntil: "domcontentloaded" });
|
| 384 |
+
await delayClick(2000); // 增加重试前的延迟
|
| 385 |
+
return await login(page, username, password, retryCount - 1);
|
| 386 |
+
} else {
|
| 387 |
+
throw new Error(
|
| 388 |
+
`Navigation timed out in login.超时了,可能是IP质量问题,失败用户 ${username},
|
| 389 |
+
${error}`
|
| 390 |
+
); //{password}
|
| 391 |
+
}
|
| 392 |
+
}
|
| 393 |
+
}
|
| 394 |
+
await delayClick(1000);
|
| 395 |
+
}
|
| 396 |
+
|
| 397 |
+
async function navigatePage(url, page, browser) {
|
| 398 |
+
await page.goto(url, { waitUntil: "domcontentloaded" }); //如果使用默认的load,linux下页面会一直加载导致无法继续执行
|
| 399 |
+
|
| 400 |
+
const startTime = Date.now(); // 记录开始时间
|
| 401 |
+
let pageTitle = await page.title(); // 获取当前页面标题
|
| 402 |
+
|
| 403 |
+
while (pageTitle.includes("Just a moment")) {
|
| 404 |
+
console.log("The page is under Cloudflare protection. Waiting...");
|
| 405 |
+
|
| 406 |
+
await delayClick(2000); // 每次检查间隔2秒
|
| 407 |
+
|
| 408 |
+
// 重新获取页面标题
|
| 409 |
+
pageTitle = await page.title();
|
| 410 |
+
|
| 411 |
+
// 检查是否超过15秒
|
| 412 |
+
if (Date.now() - startTime > 35000) {
|
| 413 |
+
console.log("Timeout exceeded, aborting actions.");
|
| 414 |
+
await browser.close();
|
| 415 |
+
return; // 超时则退出函数
|
| 416 |
+
}
|
| 417 |
+
}
|
| 418 |
+
console.log("页面标题:", pageTitle);
|
| 419 |
+
}
|
| 420 |
+
|
| 421 |
+
// 每秒截图功能
|
| 422 |
+
async function takeScreenshots(page) {
|
| 423 |
+
let screenshotIndex = 0;
|
| 424 |
+
setInterval(async () => {
|
| 425 |
+
screenshotIndex++;
|
| 426 |
+
const screenshotPath = path.join(
|
| 427 |
+
screenshotDir,
|
| 428 |
+
`screenshot-${screenshotIndex}.png`
|
| 429 |
+
);
|
| 430 |
+
try {
|
| 431 |
+
await page.screenshot({ path: screenshotPath, fullPage: true });
|
| 432 |
+
console.log(`Screenshot saved: ${screenshotPath}`);
|
| 433 |
+
} catch (error) {
|
| 434 |
+
console.error("Error taking screenshot:", error);
|
| 435 |
+
}
|
| 436 |
+
}, 1000);
|
| 437 |
+
// 注册退出时删除文件夹的回调函数
|
| 438 |
+
process.on("exit", () => {
|
| 439 |
+
try {
|
| 440 |
+
fs.rmdirSync(screenshotDir, { recursive: true });
|
| 441 |
+
console.log(`Deleted folder: ${screenshotDir}`);
|
| 442 |
+
} catch (error) {
|
| 443 |
+
console.error(`Error deleting folder ${screenshotDir}:`, error);
|
| 444 |
+
}
|
| 445 |
+
});
|
| 446 |
+
}
|
| 447 |
+
import express from "express";
|
| 448 |
+
|
| 449 |
+
const healthApp = express();
|
| 450 |
+
const HEALTH_PORT = process.env.HEALTH_PORT || 7860;
|
| 451 |
+
|
| 452 |
+
// 健康探针路由
|
| 453 |
+
healthApp.get("/health", (req, res) => {
|
| 454 |
+
const memoryUsage = process.memoryUsage();
|
| 455 |
+
|
| 456 |
+
// 将字节转换为MB
|
| 457 |
+
const memoryUsageMB = {
|
| 458 |
+
rss: `${(memoryUsage.rss / (1024 * 1024)).toFixed(2)} MB`, // 转换为MB并保留两位小数
|
| 459 |
+
heapTotal: `${(memoryUsage.heapTotal / (1024 * 1024)).toFixed(2)} MB`,
|
| 460 |
+
heapUsed: `${(memoryUsage.heapUsed / (1024 * 1024)).toFixed(2)} MB`,
|
| 461 |
+
external: `${(memoryUsage.external / (1024 * 1024)).toFixed(2)} MB`,
|
| 462 |
+
arrayBuffers: `${(memoryUsage.arrayBuffers / (1024 * 1024)).toFixed(2)} MB`,
|
| 463 |
+
};
|
| 464 |
+
|
| 465 |
+
const healthData = {
|
| 466 |
+
status: "OK",
|
| 467 |
+
timestamp: new Date().toISOString(),
|
| 468 |
+
memoryUsage: memoryUsageMB,
|
| 469 |
+
uptime: process.uptime().toFixed(2), // 保留两位小数
|
| 470 |
+
};
|
| 471 |
+
|
| 472 |
+
res.status(200).json(healthData);
|
| 473 |
+
});
|
| 474 |
+
healthApp.get("/", (req, res) => {
|
| 475 |
+
res.send(`
|
| 476 |
+
<html>
|
| 477 |
+
<head>
|
| 478 |
+
<title>Auto Read</title>
|
| 479 |
+
<style>
|
| 480 |
+
body {
|
| 481 |
+
font-family: Arial, sans-serif;
|
| 482 |
+
background-color: #f4f4f4;
|
| 483 |
+
color: #333;
|
| 484 |
+
margin: 0;
|
| 485 |
+
padding: 20px;
|
| 486 |
+
display: flex;
|
| 487 |
+
justify-content: center;
|
| 488 |
+
align-items: center;
|
| 489 |
+
height: 100vh;
|
| 490 |
+
}
|
| 491 |
+
.container {
|
| 492 |
+
background-color: #fff;
|
| 493 |
+
padding: 20px;
|
| 494 |
+
border-radius: 8px;
|
| 495 |
+
box-shadow: 0 0 15px rgba(0, 0, 0, 0.1);
|
| 496 |
+
max-width: 600px;
|
| 497 |
+
text-align: center;
|
| 498 |
+
}
|
| 499 |
+
h1 {
|
| 500 |
+
color: #007bff;
|
| 501 |
+
}
|
| 502 |
+
p {
|
| 503 |
+
font-size: 18px;
|
| 504 |
+
margin: 15px 0;
|
| 505 |
+
}
|
| 506 |
+
a {
|
| 507 |
+
color: #007bff;
|
| 508 |
+
text-decoration: none;
|
| 509 |
+
font-weight: bold;
|
| 510 |
+
}
|
| 511 |
+
a:hover {
|
| 512 |
+
text-decoration: underline;
|
| 513 |
+
}
|
| 514 |
+
footer {
|
| 515 |
+
margin-top: 20px;
|
| 516 |
+
font-size: 14px;
|
| 517 |
+
color: #555;
|
| 518 |
+
}
|
| 519 |
+
</style>
|
| 520 |
+
</head>
|
| 521 |
+
<body>
|
| 522 |
+
<div class="container">
|
| 523 |
+
<h1>Welcome to the Auto Read App</h1>
|
| 524 |
+
<p>You can check the server's health at <a href="/health">/health</a>.</p>
|
| 525 |
+
<p>GitHub: <a href="https://github.com/14790897/auto-read-liunxdo" target="_blank">https://github.com/14790897/auto-read-liunxdo</a></p>
|
| 526 |
+
<footer>© 2024 Auto Read App</footer>
|
| 527 |
+
</div>
|
| 528 |
+
</body>
|
| 529 |
+
</html>
|
| 530 |
+
`);
|
| 531 |
+
});
|
| 532 |
+
healthApp.listen(HEALTH_PORT, () => {
|
| 533 |
+
console.log(
|
| 534 |
+
`Health check endpoint is running at http://localhost:${HEALTH_PORT}/health`
|
| 535 |
+
);
|
| 536 |
+
});
|
bypasscf_likeUser.js
ADDED
|
@@ -0,0 +1,524 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import fs from "fs";
|
| 2 |
+
import path from "path";
|
| 3 |
+
import puppeteer from "puppeteer-extra";
|
| 4 |
+
import StealthPlugin from "puppeteer-extra-plugin-stealth";
|
| 5 |
+
import dotenv from "dotenv";
|
| 6 |
+
import { fileURLToPath } from "url";
|
| 7 |
+
import { dirname, join } from "path";
|
| 8 |
+
import TelegramBot from "node-telegram-bot-api";
|
| 9 |
+
|
| 10 |
+
dotenv.config();
|
| 11 |
+
|
| 12 |
+
// 截图保存的文件夹
|
| 13 |
+
// const screenshotDir = "screenshots";
|
| 14 |
+
// if (!fs.existsSync(screenshotDir)) {
|
| 15 |
+
// fs.mkdirSync(screenshotDir);
|
| 16 |
+
// }
|
| 17 |
+
puppeteer.use(StealthPlugin());
|
| 18 |
+
|
| 19 |
+
// Load the default .env file
|
| 20 |
+
if (fs.existsSync(".env.local")) {
|
| 21 |
+
console.log("Using .env.local file to supply config environment variables");
|
| 22 |
+
const envConfig = dotenv.parse(fs.readFileSync(".env.local"));
|
| 23 |
+
for (const k in envConfig) {
|
| 24 |
+
process.env[k] = envConfig[k];
|
| 25 |
+
}
|
| 26 |
+
} else {
|
| 27 |
+
console.log(
|
| 28 |
+
"Using .env file to supply config environment variables, you can create a .env.local file to overwrite defaults, it doesn't upload to git"
|
| 29 |
+
);
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
// 读取以分钟为单位的运行时间限制
|
| 33 |
+
const runTimeLimitMinutes = process.env.RUN_TIME_LIMIT_MINUTES || 20;
|
| 34 |
+
|
| 35 |
+
// 将分钟转换为毫秒
|
| 36 |
+
const runTimeLimitMillis = runTimeLimitMinutes * 60 * 1000;
|
| 37 |
+
|
| 38 |
+
console.log(
|
| 39 |
+
`运行时间限制为:${runTimeLimitMinutes} 分钟 (${runTimeLimitMillis} 毫秒)`
|
| 40 |
+
);
|
| 41 |
+
|
| 42 |
+
// 设置一个定时器,在运行时间到达时终止进程
|
| 43 |
+
const shutdownTimer = setTimeout(() => {
|
| 44 |
+
console.log("时间到,Reached time limit, shutting down the process...");
|
| 45 |
+
process.exit(0); // 退出进程
|
| 46 |
+
}, runTimeLimitMillis);
|
| 47 |
+
|
| 48 |
+
const token = process.env.TELEGRAM_BOT_TOKEN;
|
| 49 |
+
const chatId = process.env.TELEGRAM_CHAT_ID;
|
| 50 |
+
const specificUser = process.env.SPECIFIC_USER || "14790897";
|
| 51 |
+
const maxConcurrentAccounts = 4; // 每批最多同时运行的账号数
|
| 52 |
+
const usernames = process.env.USERNAMES.split(",");
|
| 53 |
+
const passwords = process.env.PASSWORDS.split(",");
|
| 54 |
+
const loginUrl = process.env.WEBSITE || "https://linux.do"; //在GitHub action环境里它不能读取默认环境变量,只能在这里设置默认值
|
| 55 |
+
const delayBetweenInstances = 10000;
|
| 56 |
+
const totalAccounts = usernames.length; // 总的账号数
|
| 57 |
+
const delayBetweenBatches =
|
| 58 |
+
runTimeLimitMillis / Math.ceil(totalAccounts / maxConcurrentAccounts);
|
| 59 |
+
|
| 60 |
+
let bot;
|
| 61 |
+
if (token && chatId) {
|
| 62 |
+
bot = new TelegramBot(token);
|
| 63 |
+
}
|
| 64 |
+
function sendToTelegram(message) {
|
| 65 |
+
if (!bot) return;
|
| 66 |
+
|
| 67 |
+
bot
|
| 68 |
+
.sendMessage(chatId, message)
|
| 69 |
+
.then(() => {
|
| 70 |
+
console.log("Telegram message sent successfully");
|
| 71 |
+
})
|
| 72 |
+
.catch((error) => {
|
| 73 |
+
console.error("Error sending Telegram message:", error);
|
| 74 |
+
});
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
//随机等待时间
|
| 78 |
+
function delayClick(time) {
|
| 79 |
+
return new Promise(function (resolve) {
|
| 80 |
+
setTimeout(resolve, time);
|
| 81 |
+
});
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
(async () => {
|
| 85 |
+
try {
|
| 86 |
+
if (usernames.length !== passwords.length) {
|
| 87 |
+
console.log(usernames.length, usernames, passwords.length, passwords);
|
| 88 |
+
throw new Error("用户名和密码的数量不匹配!");
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
// 并发启动浏览器实例进行登录
|
| 92 |
+
const loginTasks = usernames.map((username, index) => {
|
| 93 |
+
const password = passwords[index];
|
| 94 |
+
const delay = (index % maxConcurrentAccounts) * delayBetweenInstances; // 使得每一组内的浏览器可以分开启动
|
| 95 |
+
return () => {
|
| 96 |
+
// 确保这里返回的是函数
|
| 97 |
+
return new Promise((resolve, reject) => {
|
| 98 |
+
setTimeout(() => {
|
| 99 |
+
launchBrowserForUser(username, password)
|
| 100 |
+
.then(resolve)
|
| 101 |
+
.catch(reject);
|
| 102 |
+
}, delay);
|
| 103 |
+
});
|
| 104 |
+
};
|
| 105 |
+
});
|
| 106 |
+
// 依次执行每个批次的任务
|
| 107 |
+
for (let i = 0; i < totalAccounts; i += maxConcurrentAccounts) {
|
| 108 |
+
console.log(`当前批次:${i + 1} - ${i + maxConcurrentAccounts}`);
|
| 109 |
+
// 执行每批次最多 4 个账号
|
| 110 |
+
const batch = loginTasks
|
| 111 |
+
.slice(i, i + maxConcurrentAccounts)
|
| 112 |
+
.map(async (task) => {
|
| 113 |
+
const { browser } = await task(); // 运行任务并获取浏览器实例
|
| 114 |
+
return browser;
|
| 115 |
+
}); // 等待当前批次的任务完成
|
| 116 |
+
const browsers = await Promise.all(batch); // Task里面的任务本身是没有进行await的, 所以会继续执行下面的代码
|
| 117 |
+
|
| 118 |
+
// 如果还有下一个批次,等待指定的时间,同时,如果总共只有一个账号,也需要继续运行
|
| 119 |
+
if (i + maxConcurrentAccounts < totalAccounts || i === 0) {
|
| 120 |
+
console.log(`等待 ${delayBetweenBatches / 1000} 秒`);
|
| 121 |
+
await new Promise((resolve) =>
|
| 122 |
+
setTimeout(resolve, delayBetweenBatches)
|
| 123 |
+
);
|
| 124 |
+
} else {
|
| 125 |
+
console.log("没有下一个批次,即将结束");
|
| 126 |
+
}
|
| 127 |
+
console.log(
|
| 128 |
+
`批次 ${
|
| 129 |
+
Math.floor(i / maxConcurrentAccounts) + 1
|
| 130 |
+
} 完成,关闭浏览器...,浏览器对象:${browsers}`
|
| 131 |
+
);
|
| 132 |
+
// 关闭所有浏览器实例
|
| 133 |
+
for (const browser of browsers) {
|
| 134 |
+
await browser.close();
|
| 135 |
+
}
|
| 136 |
+
}
|
| 137 |
+
|
| 138 |
+
console.log("所有账号登录操作已完成");
|
| 139 |
+
// 等待所有登录操作完成
|
| 140 |
+
// await Promise.all(loginTasks);
|
| 141 |
+
} catch (error) {
|
| 142 |
+
// 错误处理逻辑
|
| 143 |
+
console.error("发生错误:", error);
|
| 144 |
+
if (token && chatId) {
|
| 145 |
+
sendToTelegram(`${error.message}`);
|
| 146 |
+
}
|
| 147 |
+
}
|
| 148 |
+
})();
|
| 149 |
+
async function launchBrowserForUser(username, password) {
|
| 150 |
+
let browser = null; // 在 try 之外声明 browser 变量
|
| 151 |
+
try {
|
| 152 |
+
console.log("当前用户:", username);
|
| 153 |
+
const browserOptions = {
|
| 154 |
+
headless: "auto",
|
| 155 |
+
args: ["--no-sandbox", "--disable-setuid-sandbox"], // Linux 需要的安全设置
|
| 156 |
+
};
|
| 157 |
+
|
| 158 |
+
// 如果环境变量不是 'dev',则添加代理配置
|
| 159 |
+
// if (process.env.ENVIRONMENT !== "dev") {
|
| 160 |
+
// browserOptions["proxy"] = {
|
| 161 |
+
// host: "38.154.227.167",
|
| 162 |
+
// port: "5868",
|
| 163 |
+
// username: "pqxujuyl",
|
| 164 |
+
// password: "y1nmb5kjbz9t",
|
| 165 |
+
// };
|
| 166 |
+
// }
|
| 167 |
+
|
| 168 |
+
var { connect } = await import("puppeteer-real-browser");
|
| 169 |
+
const { page, browser: newBrowser } = await connect(browserOptions);
|
| 170 |
+
browser = newBrowser; // 将 browser 初始化
|
| 171 |
+
// 启动截图功能
|
| 172 |
+
// takeScreenshots(page);
|
| 173 |
+
//登录操作
|
| 174 |
+
await navigatePage(loginUrl, page, browser);
|
| 175 |
+
await delayClick(8000);
|
| 176 |
+
// 设置额外的 headers
|
| 177 |
+
await page.setExtraHTTPHeaders({
|
| 178 |
+
"accept-language": "en-US,en;q=0.9",
|
| 179 |
+
});
|
| 180 |
+
// 验证 `navigator.webdriver` 属性是否为 undefined
|
| 181 |
+
// const isWebDriverUndefined = await page.evaluate(() => {
|
| 182 |
+
// return `${navigator.webdriver}`;
|
| 183 |
+
// });
|
| 184 |
+
|
| 185 |
+
// console.log("navigator.webdriver is :", isWebDriverUndefined); // 输出应为 false
|
| 186 |
+
page.on("pageerror", (error) => {
|
| 187 |
+
console.error(`Page error: ${error.message}`);
|
| 188 |
+
});
|
| 189 |
+
page.on("error", async (error) => {
|
| 190 |
+
// console.error(`Error: ${error.message}`);
|
| 191 |
+
// 检查是否是 localStorage 的访问权限错误
|
| 192 |
+
if (
|
| 193 |
+
error.message.includes(
|
| 194 |
+
"Failed to read the 'localStorage' property from 'Window'"
|
| 195 |
+
)
|
| 196 |
+
) {
|
| 197 |
+
console.log("Trying to refresh the page to resolve the issue...");
|
| 198 |
+
await page.reload(); // 刷新页面
|
| 199 |
+
// 重新尝试你的操作...
|
| 200 |
+
}
|
| 201 |
+
});
|
| 202 |
+
page.on("console", async (msg) => {
|
| 203 |
+
// console.log("PAGE LOG:", msg.text());
|
| 204 |
+
// 使用一个标志变量来检测是否已经刷新过页面
|
| 205 |
+
if (
|
| 206 |
+
!page._isReloaded &&
|
| 207 |
+
msg.text().includes("the server responded with a status of 429")
|
| 208 |
+
) {
|
| 209 |
+
// 设置标志变量为 true,表示即将刷新页面
|
| 210 |
+
page._isReloaded = true;
|
| 211 |
+
//由于油候脚本它这个时候可能会导航到新的网页,会导致直接执行代码报错,所以使用这个来在每个新网页加载之前来执行
|
| 212 |
+
await page.evaluateOnNewDocument(() => {
|
| 213 |
+
localStorage.setItem("autoLikeEnabled", "false");
|
| 214 |
+
});
|
| 215 |
+
// 等待一段时间,比如 3 秒
|
| 216 |
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
| 217 |
+
console.log("Retrying now...");
|
| 218 |
+
// 尝试刷新页面
|
| 219 |
+
// await page.reload();
|
| 220 |
+
}
|
| 221 |
+
});
|
| 222 |
+
// //登录操作
|
| 223 |
+
console.log("登录操作");
|
| 224 |
+
await login(page, username, password);
|
| 225 |
+
// 查找具有类名 "avatar" 的 img 元素验证登录是否成功
|
| 226 |
+
const avatarImg = await page.$("img.avatar");
|
| 227 |
+
|
| 228 |
+
if (avatarImg) {
|
| 229 |
+
console.log("找到avatarImg,登录成功");
|
| 230 |
+
} else {
|
| 231 |
+
console.log("未找到avatarImg,登录失败");
|
| 232 |
+
throw new Error("登录失败");
|
| 233 |
+
}
|
| 234 |
+
|
| 235 |
+
//真正执行阅读脚本
|
| 236 |
+
const externalScriptPath = path.join(
|
| 237 |
+
dirname(fileURLToPath(import.meta.url)),
|
| 238 |
+
"index_likeUser.js"
|
| 239 |
+
);
|
| 240 |
+
const externalScript = fs.readFileSync(externalScriptPath, "utf8");
|
| 241 |
+
|
| 242 |
+
// 在每个新的文档加载时执行外部脚本
|
| 243 |
+
await page.evaluateOnNewDocument(
|
| 244 |
+
(...args) => {
|
| 245 |
+
const [specificUser, scriptToEval] = args;
|
| 246 |
+
localStorage.setItem("read", true);
|
| 247 |
+
localStorage.setItem("specificUser", specificUser);
|
| 248 |
+
localStorage.setItem("isFirstRun", "false");
|
| 249 |
+
console.log("当前点赞用户:", specificUser);
|
| 250 |
+
eval(scriptToEval);
|
| 251 |
+
},
|
| 252 |
+
specificUser,
|
| 253 |
+
externalScript
|
| 254 |
+
); //变量必须从外部显示的传入, 因为在浏览器上下文它是读取不了的
|
| 255 |
+
// 添加一个监听器来监听每次页面加载完成的事件
|
| 256 |
+
page.on("load", async () => {
|
| 257 |
+
// await page.evaluate(externalScript); //因为这个是在页面加载好之后执行的,而脚本是在页面加载好时刻来判断是否要执行,由于已经加载好了,脚本就不会起作用
|
| 258 |
+
});
|
| 259 |
+
// 如果是Linuxdo,就导航到我的帖子,但我感觉这里写没什么用,因为外部脚本已经定义好了,不对,这里不会点击按钮,所以不会跳转,需要手动跳转
|
| 260 |
+
if (loginUrl == "https://linux.do") {
|
| 261 |
+
await page.goto("https://linux.do/t/topic/13716/630", {
|
| 262 |
+
waitUntil: "domcontentloaded",
|
| 263 |
+
});
|
| 264 |
+
} else if (loginUrl == "https://meta.appinn.net") {
|
| 265 |
+
await page.goto("https://meta.appinn.net/t/topic/52006", {
|
| 266 |
+
waitUntil: "domcontentloaded",
|
| 267 |
+
});
|
| 268 |
+
} else {
|
| 269 |
+
await page.goto(`${loginUrl}/t/topic/1`, {
|
| 270 |
+
waitUntil: "domcontentloaded",
|
| 271 |
+
});
|
| 272 |
+
}
|
| 273 |
+
if (token && chatId) {
|
| 274 |
+
sendToTelegram(`${username} 登录成功`);
|
| 275 |
+
}
|
| 276 |
+
return { browser };
|
| 277 |
+
} catch (err) {
|
| 278 |
+
// throw new Error(err);
|
| 279 |
+
console.log("Error in launchBrowserForUser:", err);
|
| 280 |
+
if (token && chatId) {
|
| 281 |
+
sendToTelegram(`${err.message}`);
|
| 282 |
+
}
|
| 283 |
+
return { browser }; // 错误时仍然返回 browser
|
| 284 |
+
}
|
| 285 |
+
}
|
| 286 |
+
async function login(page, username, password, retryCount = 3) {
|
| 287 |
+
// 使用XPath查询找到包含"登录"或"login"文本的按钮
|
| 288 |
+
let loginButtonFound = await page.evaluate(() => {
|
| 289 |
+
let loginButton = Array.from(document.querySelectorAll("button")).find(
|
| 290 |
+
(button) =>
|
| 291 |
+
button.textContent.includes("登录") ||
|
| 292 |
+
button.textContent.includes("login")
|
| 293 |
+
); // 注意loginButton 变量在外部作用域中是无法被 page.evaluate 内部的代码直接修改的。page.evaluate 的代码是在浏览器环境中执行的,这意味着它们无法直接影响 Node.js 环境中的变量
|
| 294 |
+
// 如果没有找到,尝试根据类名查找
|
| 295 |
+
if (!loginButton) {
|
| 296 |
+
loginButton = document.querySelector(".login-button");
|
| 297 |
+
}
|
| 298 |
+
if (loginButton) {
|
| 299 |
+
loginButton.click();
|
| 300 |
+
console.log("Login button clicked.");
|
| 301 |
+
return true; // 返回true表示找到了按钮并点击了
|
| 302 |
+
} else {
|
| 303 |
+
console.log("Login button not found.");
|
| 304 |
+
return false; // 返回false表示没有找到按钮
|
| 305 |
+
}
|
| 306 |
+
});
|
| 307 |
+
if (!loginButtonFound) {
|
| 308 |
+
if (loginUrl == "https://meta.appinn.net") {
|
| 309 |
+
await page.goto("https://meta.appinn.net/t/topic/52006", {
|
| 310 |
+
waitUntil: "domcontentloaded",
|
| 311 |
+
});
|
| 312 |
+
await page.click(".discourse-reactions-reaction-button");
|
| 313 |
+
} else {
|
| 314 |
+
await page.goto(`${loginUrl}/t/topic/1`, {
|
| 315 |
+
waitUntil: "domcontentloaded",
|
| 316 |
+
});
|
| 317 |
+
await page.click(".discourse-reactions-reaction-button");
|
| 318 |
+
}
|
| 319 |
+
}
|
| 320 |
+
// 等待用户名输入框加载
|
| 321 |
+
await page.waitForSelector("#login-account-name");
|
| 322 |
+
// 模拟人类在找到输入框后的短暂停顿
|
| 323 |
+
await delayClick(500); // 延迟500毫秒
|
| 324 |
+
// 清空输入框并输入用户名
|
| 325 |
+
await page.click("#login-account-name", { clickCount: 3 });
|
| 326 |
+
await page.type("#login-account-name", username, {
|
| 327 |
+
delay: 100,
|
| 328 |
+
}); // 输入时在每个按键之间添加额外的延迟
|
| 329 |
+
|
| 330 |
+
// 等待密码输入框加载
|
| 331 |
+
await page.waitForSelector("#login-account-password");
|
| 332 |
+
// 模拟人类在输入用户名后的短暂停顿
|
| 333 |
+
// delayClick; // 清空输入框并输入密码
|
| 334 |
+
await page.click("#login-account-password", { clickCount: 3 });
|
| 335 |
+
await page.type("#login-account-password", password, {
|
| 336 |
+
delay: 100,
|
| 337 |
+
});
|
| 338 |
+
|
| 339 |
+
// 模拟人类在输入完成后思考的短暂停顿
|
| 340 |
+
await delayClick(1000);
|
| 341 |
+
|
| 342 |
+
// 假设登录按钮的ID是'login-button',点击登录按钮
|
| 343 |
+
await page.waitForSelector("#login-button");
|
| 344 |
+
await page.click("#login-button");
|
| 345 |
+
await delayClick(500); // 模拟在点击登录按钮前的短暂停顿
|
| 346 |
+
try {
|
| 347 |
+
await Promise.all([
|
| 348 |
+
page.waitForNavigation({ waitUntil: "domcontentloaded" }), // 等待 页面跳转 DOMContentLoaded 事件
|
| 349 |
+
// 去掉上面一行会报错:Error: Execution context was destroyed, most likely because of a navigation. 可能是因为之后没等页面加载完成就执行了脚本
|
| 350 |
+
page.click("#login-button", { force: true }), // 点击登录按钮触发跳转
|
| 351 |
+
]); //注意如果登录失败,这里会一直等待跳转,导致脚本执行失败 这点四个月之前你就发现了结果今天又遇到(有个用户遇到了https://linux.do/t/topic/169209/82),但是你没有在这个报错你提示我8.5
|
| 352 |
+
} catch (error) {
|
| 353 |
+
const alertError = await page.$(".alert.alert-error");
|
| 354 |
+
if (alertError) {
|
| 355 |
+
const alertText = await page.evaluate((el) => el.innerText, alertError); // 使用 evaluate 获取 innerText
|
| 356 |
+
if (
|
| 357 |
+
alertText.includes("incorrect") ||
|
| 358 |
+
alertText.includes("Incorrect ") ||
|
| 359 |
+
alertText.includes("不正确")
|
| 360 |
+
) {
|
| 361 |
+
throw new Error(
|
| 362 |
+
`非超时错误,请检查用户名密码是否正确,失败用户 ${username}, 错误信息:${alertText}`
|
| 363 |
+
);
|
| 364 |
+
} else {
|
| 365 |
+
throw new Error(
|
| 366 |
+
`非超时错误,也不是密码错误,失败用户 ${username},错误信息:${alertText}`
|
| 367 |
+
);
|
| 368 |
+
}
|
| 369 |
+
} else {
|
| 370 |
+
if (retryCount > 0) {
|
| 371 |
+
console.log("Retrying login...");
|
| 372 |
+
await delayClick(2000); // 增加重试前的延迟
|
| 373 |
+
return await login(page, username, password, retryCount - 1);
|
| 374 |
+
} else {
|
| 375 |
+
throw new Error(
|
| 376 |
+
`Navigation timed out in login.超时了,可能是IP质量问题,失败用户 ${username},
|
| 377 |
+
${error}`
|
| 378 |
+
); //{password}
|
| 379 |
+
}
|
| 380 |
+
}
|
| 381 |
+
}
|
| 382 |
+
await delayClick(1000);
|
| 383 |
+
}
|
| 384 |
+
|
| 385 |
+
async function navigatePage(url, page, browser) {
|
| 386 |
+
await page.goto(url, { waitUntil: "domcontentloaded" }); //如果使用默认的load,linux下页面会一���加载导致无法继续执行
|
| 387 |
+
|
| 388 |
+
const startTime = Date.now(); // 记录开始时间
|
| 389 |
+
let pageTitle = await page.title(); // 获取当前页面标题
|
| 390 |
+
|
| 391 |
+
while (pageTitle.includes("Just a moment")) {
|
| 392 |
+
console.log("The page is under Cloudflare protection. Waiting...");
|
| 393 |
+
|
| 394 |
+
await delayClick(2000); // 每次检查间隔2秒
|
| 395 |
+
|
| 396 |
+
// 重新获取页面标题
|
| 397 |
+
pageTitle = await page.title();
|
| 398 |
+
|
| 399 |
+
// 检查是否超过15秒
|
| 400 |
+
if (Date.now() - startTime > 35000) {
|
| 401 |
+
console.log("Timeout exceeded, aborting actions.");
|
| 402 |
+
await browser.close();
|
| 403 |
+
return; // 超时则退出函数
|
| 404 |
+
}
|
| 405 |
+
}
|
| 406 |
+
console.log("页面标题:", pageTitle);
|
| 407 |
+
}
|
| 408 |
+
|
| 409 |
+
// 每秒截图功能
|
| 410 |
+
async function takeScreenshots(page) {
|
| 411 |
+
let screenshotIndex = 0;
|
| 412 |
+
setInterval(async () => {
|
| 413 |
+
screenshotIndex++;
|
| 414 |
+
const screenshotPath = path.join(
|
| 415 |
+
screenshotDir,
|
| 416 |
+
`screenshot-${screenshotIndex}.png`
|
| 417 |
+
);
|
| 418 |
+
try {
|
| 419 |
+
await page.screenshot({ path: screenshotPath, fullPage: true });
|
| 420 |
+
console.log(`Screenshot saved: ${screenshotPath}`);
|
| 421 |
+
} catch (error) {
|
| 422 |
+
console.error("Error taking screenshot:", error);
|
| 423 |
+
}
|
| 424 |
+
}, 1000);
|
| 425 |
+
// 注册退出时删除文件夹的回调函数
|
| 426 |
+
process.on("exit", () => {
|
| 427 |
+
try {
|
| 428 |
+
fs.rmdirSync(screenshotDir, { recursive: true });
|
| 429 |
+
console.log(`Deleted folder: ${screenshotDir}`);
|
| 430 |
+
} catch (error) {
|
| 431 |
+
console.error(`Error deleting folder ${screenshotDir}:`, error);
|
| 432 |
+
}
|
| 433 |
+
});
|
| 434 |
+
}
|
| 435 |
+
import express from "express";
|
| 436 |
+
|
| 437 |
+
const healthApp = express();
|
| 438 |
+
const HEALTH_PORT = process.env.HEALTH_PORT || 7860;
|
| 439 |
+
|
| 440 |
+
// 健康探针路由
|
| 441 |
+
healthApp.get("/health", (req, res) => {
|
| 442 |
+
const memoryUsage = process.memoryUsage();
|
| 443 |
+
|
| 444 |
+
// 将字节转换为MB
|
| 445 |
+
const memoryUsageMB = {
|
| 446 |
+
rss: `${(memoryUsage.rss / (1024 * 1024)).toFixed(2)} MB`, // 转换为MB并保留两位小数
|
| 447 |
+
heapTotal: `${(memoryUsage.heapTotal / (1024 * 1024)).toFixed(2)} MB`,
|
| 448 |
+
heapUsed: `${(memoryUsage.heapUsed / (1024 * 1024)).toFixed(2)} MB`,
|
| 449 |
+
external: `${(memoryUsage.external / (1024 * 1024)).toFixed(2)} MB`,
|
| 450 |
+
arrayBuffers: `${(memoryUsage.arrayBuffers / (1024 * 1024)).toFixed(2)} MB`,
|
| 451 |
+
};
|
| 452 |
+
|
| 453 |
+
const healthData = {
|
| 454 |
+
status: "OK",
|
| 455 |
+
timestamp: new Date().toISOString(),
|
| 456 |
+
memoryUsage: memoryUsageMB,
|
| 457 |
+
uptime: process.uptime().toFixed(2), // 保留两位小数
|
| 458 |
+
};
|
| 459 |
+
|
| 460 |
+
res.status(200).json(healthData);
|
| 461 |
+
});
|
| 462 |
+
healthApp.get("/", (req, res) => {
|
| 463 |
+
res.send(`
|
| 464 |
+
<html>
|
| 465 |
+
<head>
|
| 466 |
+
<title>Auto Read</title>
|
| 467 |
+
<style>
|
| 468 |
+
body {
|
| 469 |
+
font-family: Arial, sans-serif;
|
| 470 |
+
background-color: #f4f4f4;
|
| 471 |
+
color: #333;
|
| 472 |
+
margin: 0;
|
| 473 |
+
padding: 20px;
|
| 474 |
+
display: flex;
|
| 475 |
+
justify-content: center;
|
| 476 |
+
align-items: center;
|
| 477 |
+
height: 100vh;
|
| 478 |
+
}
|
| 479 |
+
.container {
|
| 480 |
+
background-color: #fff;
|
| 481 |
+
padding: 20px;
|
| 482 |
+
border-radius: 8px;
|
| 483 |
+
box-shadow: 0 0 15px rgba(0, 0, 0, 0.1);
|
| 484 |
+
max-width: 600px;
|
| 485 |
+
text-align: center;
|
| 486 |
+
}
|
| 487 |
+
h1 {
|
| 488 |
+
color: #007bff;
|
| 489 |
+
}
|
| 490 |
+
p {
|
| 491 |
+
font-size: 18px;
|
| 492 |
+
margin: 15px 0;
|
| 493 |
+
}
|
| 494 |
+
a {
|
| 495 |
+
color: #007bff;
|
| 496 |
+
text-decoration: none;
|
| 497 |
+
font-weight: bold;
|
| 498 |
+
}
|
| 499 |
+
a:hover {
|
| 500 |
+
text-decoration: underline;
|
| 501 |
+
}
|
| 502 |
+
footer {
|
| 503 |
+
margin-top: 20px;
|
| 504 |
+
font-size: 14px;
|
| 505 |
+
color: #555;
|
| 506 |
+
}
|
| 507 |
+
</style>
|
| 508 |
+
</head>
|
| 509 |
+
<body>
|
| 510 |
+
<div class="container">
|
| 511 |
+
<h1>Welcome to the Auto Read App</h1>
|
| 512 |
+
<p>You can check the server's health at <a href="/health">/health</a>.</p>
|
| 513 |
+
<p>GitHub: <a href="https://github.com/14790897/auto-read-liunxdo" target="_blank">https://github.com/14790897/auto-read-liunxdo</a></p>
|
| 514 |
+
<footer>© 2024 Auto Read App</footer>
|
| 515 |
+
</div>
|
| 516 |
+
</body>
|
| 517 |
+
</html>
|
| 518 |
+
`);
|
| 519 |
+
});
|
| 520 |
+
healthApp.listen(HEALTH_PORT, () => {
|
| 521 |
+
console.log(
|
| 522 |
+
`Health check endpoint is running at http://localhost:${HEALTH_PORT}/health`
|
| 523 |
+
);
|
| 524 |
+
});
|
cron.log
ADDED
|
File without changes
|
cron.sh
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
#设置为中文
|
| 3 |
+
export LANG=zh_CN.UTF-8
|
| 4 |
+
export LC_ALL=zh_CN.UTF-8
|
| 5 |
+
# 获取当前工作目录
|
| 6 |
+
WORKDIR=$(dirname $(readlink -f $0))
|
| 7 |
+
|
| 8 |
+
# 进入工作目录
|
| 9 |
+
cd $WORKDIR
|
| 10 |
+
|
| 11 |
+
# 停止 Docker Compose
|
| 12 |
+
/usr/local/bin/docker-compose down --remove-orphans --volumes
|
| 13 |
+
|
| 14 |
+
# 重新启动 Docker Compose
|
| 15 |
+
/usr/local/bin/docker-compose up -d >> ./cron.log 2>&1
|
| 16 |
+
|
| 17 |
+
# 等待20分钟
|
| 18 |
+
sleep 20m
|
| 19 |
+
/usr/local/bin/docker-compose logs >> ./cron.log 2>&1
|
| 20 |
+
|
| 21 |
+
# 停止 Docker Compose
|
| 22 |
+
/usr/local/bin/docker-compose down --remove-orphans --volumes >> ./cron.log 2>&1
|
docker-compose-like-user.yml
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
services:
|
| 2 |
+
autolikeuser:
|
| 3 |
+
image: 14790897/auto-like-user:latest
|
| 4 |
+
container_name: auto-like-user
|
| 5 |
+
env_file:
|
| 6 |
+
- ./.env
|
| 7 |
+
- ./.env.local
|
| 8 |
+
restart: unless-stopped # 容器退出时重启策略
|
docker-compose.yml
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
services:
|
| 2 |
+
autoread:
|
| 3 |
+
image: 14790897/auto-read:latest
|
| 4 |
+
container_name: auto-read
|
| 5 |
+
env_file:
|
| 6 |
+
- ./.env
|
| 7 |
+
- ./.env.local
|
| 8 |
+
restart: unless-stopped # 容器退出时重启策略
|
example.png
ADDED
|
external.js
ADDED
|
@@ -0,0 +1,383 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// ==UserScript==
|
| 2 |
+
// @name Auto Read
|
| 3 |
+
// @namespace http://tampermonkey.net/
|
| 4 |
+
// @version 1.3.1
|
| 5 |
+
// @description 自动刷linuxdo文章
|
| 6 |
+
// @author liuweiqing
|
| 7 |
+
// @match https://meta.discourse.org/*
|
| 8 |
+
// @match https://linux.do/*
|
| 9 |
+
// @match https://meta.appinn.net/*
|
| 10 |
+
// @match https://community.openai.com/
|
| 11 |
+
// @grant none
|
| 12 |
+
// @license MIT
|
| 13 |
+
// @icon https://www.google.com/s2/favicons?domain=linux.do
|
| 14 |
+
// ==/UserScript==
|
| 15 |
+
|
| 16 |
+
(function () {
|
| 17 |
+
("use strict");
|
| 18 |
+
// 定义可能的基本URL
|
| 19 |
+
const possibleBaseURLs = [
|
| 20 |
+
"https://meta.discourse.org",
|
| 21 |
+
"https://linux.do",
|
| 22 |
+
"https://meta.appinn.net",
|
| 23 |
+
"https://community.openai.com",
|
| 24 |
+
];
|
| 25 |
+
|
| 26 |
+
// 获取当前页面的URL
|
| 27 |
+
const currentURL = window.location.href;
|
| 28 |
+
|
| 29 |
+
// 确定当前页面对应的BASE_URL
|
| 30 |
+
let BASE_URL = possibleBaseURLs.find((url) => currentURL.startsWith(url));
|
| 31 |
+
|
| 32 |
+
// 环境变量:阅读网址,如果没有找到匹配的URL,则默认为第一个
|
| 33 |
+
if (!BASE_URL) {
|
| 34 |
+
BASE_URL = possibleBaseURLs[0];
|
| 35 |
+
console.log("默认BASE_URL设置为: " + BASE_URL);
|
| 36 |
+
} else {
|
| 37 |
+
console.log("当前BASE_URL是: " + BASE_URL);
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
// 以下是脚本的其余部分
|
| 41 |
+
console.log("脚本正在运行在: " + BASE_URL);
|
| 42 |
+
//1.进入网页 https://linux.do/t/topic/数字(1,2,3,4)
|
| 43 |
+
//2.使滚轮均衡的往下移动模拟刷文章
|
| 44 |
+
// 检查是否是第一次运行脚本
|
| 45 |
+
function checkFirstRun() {
|
| 46 |
+
if (localStorage.getItem("isFirstRun") === null) {
|
| 47 |
+
// 是第一次运行,执行初始化操作
|
| 48 |
+
console.log("脚本第一次运行,执行初始化操作...");
|
| 49 |
+
updateInitialData();
|
| 50 |
+
|
| 51 |
+
// 设置 isFirstRun 标记为 false
|
| 52 |
+
localStorage.setItem("isFirstRun", "false");
|
| 53 |
+
} else {
|
| 54 |
+
// 非第一次运行
|
| 55 |
+
console.log("脚本非第一次运行");
|
| 56 |
+
}
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
// 更新初始数据的函数
|
| 60 |
+
function updateInitialData() {
|
| 61 |
+
localStorage.setItem("read", "true"); // 开始时自动滚动关闭
|
| 62 |
+
localStorage.setItem("autoLikeEnabled", "true"); //默认关闭自动点赞
|
| 63 |
+
console.log("执行了初始数据更新操作");
|
| 64 |
+
}
|
| 65 |
+
const delay = 2000; // 滚动检查的间隔(毫秒)
|
| 66 |
+
let scrollInterval = null;
|
| 67 |
+
let checkScrollTimeout = null;
|
| 68 |
+
let autoLikeInterval = null;
|
| 69 |
+
|
| 70 |
+
function scrollToBottomSlowly(
|
| 71 |
+
stopDistance = 9999999999,
|
| 72 |
+
callback = undefined,
|
| 73 |
+
distancePerStep = 20,
|
| 74 |
+
delayPerStep = 50
|
| 75 |
+
) {
|
| 76 |
+
if (scrollInterval !== null) {
|
| 77 |
+
clearInterval(scrollInterval);
|
| 78 |
+
}
|
| 79 |
+
scrollInterval = setInterval(() => {
|
| 80 |
+
if (
|
| 81 |
+
window.innerHeight + window.scrollY >=
|
| 82 |
+
document.body.offsetHeight - 100 ||
|
| 83 |
+
window.innerHeight + window.scrollY >= stopDistance
|
| 84 |
+
) {
|
| 85 |
+
clearInterval(scrollInterval);
|
| 86 |
+
scrollInterval = null;
|
| 87 |
+
if (typeof callback === "function") {
|
| 88 |
+
callback(); // 当滚动结束时调用回调函数
|
| 89 |
+
}
|
| 90 |
+
} else {
|
| 91 |
+
window.scrollBy(0, distancePerStep);
|
| 92 |
+
}
|
| 93 |
+
}, delayPerStep);
|
| 94 |
+
}
|
| 95 |
+
// 功能:跳转到下一个话题
|
| 96 |
+
|
| 97 |
+
function navigateToNextTopic() {
|
| 98 |
+
// 定义包含文章列表的数组
|
| 99 |
+
const urls = [
|
| 100 |
+
`${BASE_URL}/latest`,
|
| 101 |
+
`${BASE_URL}/top`,
|
| 102 |
+
`${BASE_URL}/latest?ascending=false&order=posts`,
|
| 103 |
+
// `${BASE_URL}/unread`, // 示例:如果你想将这个URL启用,只需去掉前面的注释
|
| 104 |
+
];
|
| 105 |
+
|
| 106 |
+
// 生成一个随机索引
|
| 107 |
+
const randomIndex = Math.floor(Math.random() * urls.length);
|
| 108 |
+
|
| 109 |
+
// 根据随机索引选择一个URL
|
| 110 |
+
const nextTopicURL = urls[randomIndex]; // 在跳转之前,标记即将跳转到下一个话题
|
| 111 |
+
localStorage.setItem("navigatingToNextTopic", "true");
|
| 112 |
+
// 尝试导航到下一个话题
|
| 113 |
+
window.location.href = nextTopicURL;
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
// 检查是否已滚动到底部(不断重复执行)
|
| 117 |
+
function checkScroll() {
|
| 118 |
+
if (localStorage.getItem("read")) {
|
| 119 |
+
if (
|
| 120 |
+
window.innerHeight + window.scrollY >=
|
| 121 |
+
document.body.offsetHeight - 100
|
| 122 |
+
) {
|
| 123 |
+
console.log("已滚动到底部");
|
| 124 |
+
navigateToNextTopic();
|
| 125 |
+
} else {
|
| 126 |
+
scrollToBottomSlowly();
|
| 127 |
+
if (checkScrollTimeout !== null) {
|
| 128 |
+
clearTimeout(checkScrollTimeout);
|
| 129 |
+
}
|
| 130 |
+
checkScrollTimeout = setTimeout(checkScroll, delay);
|
| 131 |
+
}
|
| 132 |
+
}
|
| 133 |
+
}
|
| 134 |
+
|
| 135 |
+
// 入口函数
|
| 136 |
+
window.addEventListener("load", () => {
|
| 137 |
+
checkFirstRun();
|
| 138 |
+
console.log(
|
| 139 |
+
"autoRead",
|
| 140 |
+
localStorage.getItem("read"),
|
| 141 |
+
"autoLikeEnabled",
|
| 142 |
+
localStorage.getItem("autoLikeEnabled")
|
| 143 |
+
);
|
| 144 |
+
if (localStorage.getItem("read") === "true") {
|
| 145 |
+
// 检查是否正在导航到下一个话题
|
| 146 |
+
if (localStorage.getItem("navigatingToNextTopic") === "true") {
|
| 147 |
+
console.log("正在导航到下一个话题");
|
| 148 |
+
// 等待一段时间或直到页面完全加载
|
| 149 |
+
// 页面加载完成后,移除标记
|
| 150 |
+
localStorage.removeItem("navigatingToNextTopic");
|
| 151 |
+
// 使用setTimeout延迟执行
|
| 152 |
+
setTimeout(() => {
|
| 153 |
+
// 先随机滚动一段距离然后再查找链接
|
| 154 |
+
scrollToBottomSlowly(
|
| 155 |
+
Math.random() * document.body.offsetHeight * 3,
|
| 156 |
+
searchLinkClick,
|
| 157 |
+
20,
|
| 158 |
+
20
|
| 159 |
+
);
|
| 160 |
+
}, 2000); // 延迟2000毫秒(即2秒)
|
| 161 |
+
} else {
|
| 162 |
+
console.log("执行正常的滚动和检查逻辑");
|
| 163 |
+
// 执行正常的滚动和检查逻辑
|
| 164 |
+
checkScroll();
|
| 165 |
+
if (isAutoLikeEnabled()) {
|
| 166 |
+
//自动点赞
|
| 167 |
+
autoLike();
|
| 168 |
+
}
|
| 169 |
+
}
|
| 170 |
+
}
|
| 171 |
+
});
|
| 172 |
+
// 创建一个控制滚动的按钮
|
| 173 |
+
function searchLinkClick() {
|
| 174 |
+
// 在新页面加载后执行检查
|
| 175 |
+
// 使用CSS属性选择器寻找href属性符合特定格式的<a>标签
|
| 176 |
+
const links = document.querySelectorAll('a[href^="/t/"]');
|
| 177 |
+
// const alreadyReadLinks = JSON.parse(
|
| 178 |
+
// localStorage.getItem("alreadyReadLinks") || "[]"
|
| 179 |
+
// ); // 获取已阅读链接列表
|
| 180 |
+
|
| 181 |
+
// 筛选出未阅读的链接
|
| 182 |
+
const unreadLinks = Array.from(links).filter((link) => {
|
| 183 |
+
// 检查链接是否已经被读过
|
| 184 |
+
// const isAlreadyRead = alreadyReadLinks.includes(link.href);
|
| 185 |
+
// if (isAlreadyRead) {
|
| 186 |
+
// return false; // 如果链接已被读过,直接排除
|
| 187 |
+
// }
|
| 188 |
+
|
| 189 |
+
// 向上遍历DOM树,查找包含'visited'类的父级元素,最多查找三次
|
| 190 |
+
let parent = link.parentElement;
|
| 191 |
+
let times = 0; // 查找次数计数器
|
| 192 |
+
while (parent && times < 3) {
|
| 193 |
+
if (parent.classList.contains("visited")) {
|
| 194 |
+
// 如果找到包含'visited'类的父级元素,中断循环
|
| 195 |
+
return false; // 父级元素包含'visited'类,排除这个链接
|
| 196 |
+
}
|
| 197 |
+
parent = parent.parentElement; // 继续向上查找
|
| 198 |
+
times++; // 增加查找次数
|
| 199 |
+
}
|
| 200 |
+
|
| 201 |
+
// 如果链接未被读过,且在向上查找三次内,其父级元素中没有包含'visited'类,则保留这个链接
|
| 202 |
+
return true;
|
| 203 |
+
});
|
| 204 |
+
|
| 205 |
+
// 如果找到了这样的链接
|
| 206 |
+
if (unreadLinks.length > 0) {
|
| 207 |
+
// 从所有匹配的链接中随机选择一个
|
| 208 |
+
const randomIndex = Math.floor(Math.random() * unreadLinks.length);
|
| 209 |
+
const link = unreadLinks[randomIndex];
|
| 210 |
+
// 打印找到的链接(可选)
|
| 211 |
+
console.log("Found link:", link.href);
|
| 212 |
+
// // 模拟点击该链接
|
| 213 |
+
// setTimeout(() => {
|
| 214 |
+
// link.click();
|
| 215 |
+
// }, delay);
|
| 216 |
+
// 将链接添加到已阅读列表并更新localStorage
|
| 217 |
+
// alreadyReadLinks.push(link.href);
|
| 218 |
+
// localStorage.setItem(
|
| 219 |
+
// "alreadyReadLinks",
|
| 220 |
+
// JSON.stringify(alreadyReadLinks)
|
| 221 |
+
// );
|
| 222 |
+
|
| 223 |
+
// 导航到该链接
|
| 224 |
+
window.location.href = link.href;
|
| 225 |
+
} else {
|
| 226 |
+
// 如果没有找到符合条件的链接,打印消息(可选)
|
| 227 |
+
console.log("No link with the specified format was found.");
|
| 228 |
+
scrollToBottomSlowly(
|
| 229 |
+
Math.random() * document.body.offsetHeight * 3,
|
| 230 |
+
searchLinkClick
|
| 231 |
+
);
|
| 232 |
+
}
|
| 233 |
+
}
|
| 234 |
+
// 获取当前时间戳
|
| 235 |
+
const currentTime = Date.now();
|
| 236 |
+
// 获取存储的时间戳
|
| 237 |
+
const defaultTimestamp = new Date("1999-01-01T00:00:00Z").getTime(); //默认值为1999年
|
| 238 |
+
const storedTime = parseInt(
|
| 239 |
+
localStorage.getItem("clickCounterTimestamp") ||
|
| 240 |
+
defaultTimestamp.toString(),
|
| 241 |
+
10
|
| 242 |
+
);
|
| 243 |
+
|
| 244 |
+
// 获取当前的点击计数,如果不存在则初始化为0
|
| 245 |
+
let clickCounter = parseInt(localStorage.getItem("clickCounter") || "0", 10);
|
| 246 |
+
// 检查是否超过24小时(24小时 = 24 * 60 * 60 * 1000 毫秒)
|
| 247 |
+
if (currentTime - storedTime > 24 * 60 * 60 * 1000) {
|
| 248 |
+
// 超过24小时,清空点击计数器并更新时间戳
|
| 249 |
+
clickCounter = 0;
|
| 250 |
+
localStorage.setItem("clickCounter", "0");
|
| 251 |
+
localStorage.setItem("clickCounterTimestamp", currentTime.toString());
|
| 252 |
+
}
|
| 253 |
+
|
| 254 |
+
console.log(`Initial clickCounter: ${clickCounter}`);
|
| 255 |
+
function triggerClick(button) {
|
| 256 |
+
const event = new MouseEvent("click", {
|
| 257 |
+
bubbles: true,
|
| 258 |
+
cancelable: true,
|
| 259 |
+
view: window,
|
| 260 |
+
});
|
| 261 |
+
button.dispatchEvent(event);
|
| 262 |
+
}
|
| 263 |
+
|
| 264 |
+
function autoLike() {
|
| 265 |
+
console.log(`Initial clickCounter: ${clickCounter}`);
|
| 266 |
+
// 寻找所有的discourse-reactions-reaction-button
|
| 267 |
+
const buttons = document.querySelectorAll(
|
| 268 |
+
".discourse-reactions-reaction-button"
|
| 269 |
+
);
|
| 270 |
+
if (buttons.length === 0) {
|
| 271 |
+
console.error(
|
| 272 |
+
"No buttons found with the selector '.discourse-reactions-reaction-button'"
|
| 273 |
+
);
|
| 274 |
+
return;
|
| 275 |
+
}
|
| 276 |
+
console.log(`Found ${buttons.length} buttons.`); // 调试信息
|
| 277 |
+
|
| 278 |
+
// 逐个点击找到的按钮
|
| 279 |
+
buttons.forEach((button, index) => {
|
| 280 |
+
if (
|
| 281 |
+
(button.title !== "点赞此帖子" && button.title !== "Like this post") ||
|
| 282 |
+
clickCounter >= 50
|
| 283 |
+
) {
|
| 284 |
+
return;
|
| 285 |
+
}
|
| 286 |
+
|
| 287 |
+
// 使用setTimeout来错开每次点击的时间,避免同时触发点击
|
| 288 |
+
autoLikeInterval = setTimeout(() => {
|
| 289 |
+
// 模拟点击
|
| 290 |
+
triggerClick(button); // 使用自定义的触发点击方法
|
| 291 |
+
console.log(`Clicked like button ${index + 1}`);
|
| 292 |
+
clickCounter++; // 更新点击计数器
|
| 293 |
+
// 将新的点击计数存储到localStorage
|
| 294 |
+
localStorage.setItem("clickCounter", clickCounter.toString());
|
| 295 |
+
// 如果点击次数达到50次,则设置点赞变量为false
|
| 296 |
+
if (clickCounter === 50) {
|
| 297 |
+
console.log("Reached 50 likes, setting the like variable to false.");
|
| 298 |
+
localStorage.setItem("autoLikeEnabled", "false"); // 使用localStorage存储点赞变量状态
|
| 299 |
+
} else {
|
| 300 |
+
console.log("clickCounter:", clickCounter);
|
| 301 |
+
}
|
| 302 |
+
}, index * 3000); // 这里的3000毫秒是两次点击之间的间隔,可以根据需要调整
|
| 303 |
+
});
|
| 304 |
+
}
|
| 305 |
+
const button = document.createElement("button");
|
| 306 |
+
// 初始化按钮文本基于当前的阅读状态
|
| 307 |
+
button.textContent =
|
| 308 |
+
localStorage.getItem("read") === "true" ? "停止阅读" : "开始阅读";
|
| 309 |
+
button.style.position = "fixed";
|
| 310 |
+
button.style.bottom = "10px"; // 之前是 top
|
| 311 |
+
button.style.left = "10px"; // 之前是 right
|
| 312 |
+
button.style.zIndex = 1000;
|
| 313 |
+
button.style.backgroundColor = "#f0f0f0"; // 浅灰色背景
|
| 314 |
+
button.style.color = "#000"; // 黑色文本
|
| 315 |
+
button.style.border = "1px solid #ddd"; // 浅灰色边框
|
| 316 |
+
button.style.padding = "5px 10px"; // 内边距
|
| 317 |
+
button.style.borderRadius = "5px"; // 圆角
|
| 318 |
+
// document.body.appendChild(button);
|
| 319 |
+
|
| 320 |
+
button.onclick = function () {
|
| 321 |
+
const currentlyReading = localStorage.getItem("read") === "true";
|
| 322 |
+
const newReadState = !currentlyReading;
|
| 323 |
+
localStorage.setItem("read", newReadState.toString());
|
| 324 |
+
button.textContent = newReadState ? "停止阅读" : "开始阅读";
|
| 325 |
+
if (!newReadState) {
|
| 326 |
+
if (scrollInterval !== null) {
|
| 327 |
+
clearInterval(scrollInterval);
|
| 328 |
+
scrollInterval = null;
|
| 329 |
+
}
|
| 330 |
+
if (checkScrollTimeout !== null) {
|
| 331 |
+
clearTimeout(checkScrollTimeout);
|
| 332 |
+
checkScrollTimeout = null;
|
| 333 |
+
}
|
| 334 |
+
localStorage.removeItem("navigatingToNextTopic");
|
| 335 |
+
} else {
|
| 336 |
+
// 如果是Linuxdo,就导航到我的帖子
|
| 337 |
+
if (BASE_URL == "https://linux.do") {
|
| 338 |
+
window.location.href = "https://linux.do/t/topic/13716/340";
|
| 339 |
+
} else if (BASE_URL == "https://meta.appinn.net") {
|
| 340 |
+
window.location.href = "https://meta.appinn.net/t/topic/52006";
|
| 341 |
+
} else {
|
| 342 |
+
window.location.href = `${BASE_URL}/t/topic/1`;
|
| 343 |
+
}
|
| 344 |
+
checkScroll();
|
| 345 |
+
}
|
| 346 |
+
};
|
| 347 |
+
|
| 348 |
+
//自动点赞按钮
|
| 349 |
+
// 在页面上添加一个控制自动点赞的按钮
|
| 350 |
+
const toggleAutoLikeButton = document.createElement("button");
|
| 351 |
+
toggleAutoLikeButton.textContent = isAutoLikeEnabled()
|
| 352 |
+
? "禁用自动点赞"
|
| 353 |
+
: "启用自动点赞";
|
| 354 |
+
toggleAutoLikeButton.style.position = "fixed";
|
| 355 |
+
toggleAutoLikeButton.style.bottom = "50px"; // 之前是 top,且与另一个按钮错开位置
|
| 356 |
+
toggleAutoLikeButton.style.left = "10px"; // 之前是 right
|
| 357 |
+
toggleAutoLikeButton.style.zIndex = "1000";
|
| 358 |
+
toggleAutoLikeButton.style.backgroundColor = "#f0f0f0"; // 浅灰色背景
|
| 359 |
+
toggleAutoLikeButton.style.color = "#000"; // 黑色文本
|
| 360 |
+
toggleAutoLikeButton.style.border = "1px solid #ddd"; // 浅灰色边框
|
| 361 |
+
toggleAutoLikeButton.style.padding = "5px 10px"; // 内边距
|
| 362 |
+
toggleAutoLikeButton.style.borderRadius = "5px"; // 圆角
|
| 363 |
+
// document.body.appendChild(toggleAutoLikeButton);
|
| 364 |
+
|
| 365 |
+
// 为按钮添加点击事件处理函数
|
| 366 |
+
toggleAutoLikeButton.addEventListener("click", () => {
|
| 367 |
+
const isEnabled = !isAutoLikeEnabled();
|
| 368 |
+
setAutoLikeEnabled(isEnabled);
|
| 369 |
+
toggleAutoLikeButton.textContent = isEnabled
|
| 370 |
+
? "禁用自动点赞"
|
| 371 |
+
: "启用自动点赞";
|
| 372 |
+
});
|
| 373 |
+
// 判断是否启用自动点赞
|
| 374 |
+
function isAutoLikeEnabled() {
|
| 375 |
+
// 从localStorage获取autoLikeEnabled的值,如果未设置,默认为"true"
|
| 376 |
+
return localStorage.getItem("autoLikeEnabled") !== "false";
|
| 377 |
+
}
|
| 378 |
+
|
| 379 |
+
// 设置自动点赞的启用状态
|
| 380 |
+
function setAutoLikeEnabled(enabled) {
|
| 381 |
+
localStorage.setItem("autoLikeEnabled", enabled ? "true" : "false");
|
| 382 |
+
}
|
| 383 |
+
})();
|
image.png
ADDED
|
image2.png
ADDED
|
index.js
ADDED
|
@@ -0,0 +1,337 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// ==UserScript==
|
| 2 |
+
// @name Auto Read
|
| 3 |
+
// @namespace http://tampermonkey.net/
|
| 4 |
+
// @version 1.4.2
|
| 5 |
+
// @description 自动刷linuxdo文章
|
| 6 |
+
// @author liuweiqing
|
| 7 |
+
// @match https://meta.discourse.org/*
|
| 8 |
+
// @match https://linux.do/*
|
| 9 |
+
// @match https://meta.appinn.net/*
|
| 10 |
+
// @match https://community.openai.com/
|
| 11 |
+
// @grant none
|
| 12 |
+
// @license MIT
|
| 13 |
+
// @icon https://www.google.com/s2/favicons?domain=linux.do
|
| 14 |
+
// @downloadURL https://update.greasyfork.org/scripts/489464/Auto%20Read.user.js
|
| 15 |
+
// @updateURL https://update.greasyfork.org/scripts/489464/Auto%20Read.meta.js
|
| 16 |
+
// ==/UserScript==
|
| 17 |
+
|
| 18 |
+
(function () {
|
| 19 |
+
("use strict");
|
| 20 |
+
// 定义可能的基本URL
|
| 21 |
+
const possibleBaseURLs = [
|
| 22 |
+
"https://linux.do",
|
| 23 |
+
"https://meta.discourse.org",
|
| 24 |
+
"https://meta.appinn.net",
|
| 25 |
+
"https://community.openai.com",
|
| 26 |
+
];
|
| 27 |
+
const commentLimit = 1000;
|
| 28 |
+
const topicListLimit = 100;
|
| 29 |
+
const likeLimit = 50;
|
| 30 |
+
// 获取当前页面的URL
|
| 31 |
+
const currentURL = window.location.href;
|
| 32 |
+
|
| 33 |
+
// 确定当前页面对应的BASE_URL
|
| 34 |
+
let BASE_URL = possibleBaseURLs.find((url) => currentURL.startsWith(url));
|
| 35 |
+
console.log("currentURL:", currentURL);
|
| 36 |
+
// 环境变量:阅读网址,如果没有找到匹配的URL,则默认为第一个
|
| 37 |
+
if (!BASE_URL) {
|
| 38 |
+
BASE_URL = possibleBaseURLs[0];
|
| 39 |
+
console.log("默认BASE_URL设置为: " + BASE_URL);
|
| 40 |
+
} else {
|
| 41 |
+
console.log("当前BASE_URL是: " + BASE_URL);
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
console.log("脚本正在运行在: " + BASE_URL);
|
| 45 |
+
//1.进入网页 https://linux.do/t/topic/数字(1,2,3,4)
|
| 46 |
+
//2.使滚轮均衡的往下移动模拟刷文章
|
| 47 |
+
// 检查是否是第一次运行脚本
|
| 48 |
+
function checkFirstRun() {
|
| 49 |
+
if (localStorage.getItem("isFirstRun") === null) {
|
| 50 |
+
console.log("脚本第一次运行,执行初始化操作...");
|
| 51 |
+
updateInitialData();
|
| 52 |
+
localStorage.setItem("isFirstRun", "false");
|
| 53 |
+
} else {
|
| 54 |
+
console.log("脚本非第一次运行");
|
| 55 |
+
}
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
// 更新初始数据的函数
|
| 59 |
+
function updateInitialData() {
|
| 60 |
+
localStorage.setItem("read", "false"); // 开始时自动滚动关闭
|
| 61 |
+
localStorage.setItem("autoLikeEnabled", "false"); //默认关闭自动点赞
|
| 62 |
+
console.log("执行了初始数据更新操作");
|
| 63 |
+
}
|
| 64 |
+
const delay = 2000; // 滚动检查的间隔(毫秒)
|
| 65 |
+
let scrollInterval = null;
|
| 66 |
+
let checkScrollTimeout = null;
|
| 67 |
+
let autoLikeInterval = null;
|
| 68 |
+
|
| 69 |
+
function scrollToBottomSlowly(distancePerStep = 20, delayPerStep = 50) {
|
| 70 |
+
if (scrollInterval !== null) {
|
| 71 |
+
clearInterval(scrollInterval);
|
| 72 |
+
}
|
| 73 |
+
scrollInterval = setInterval(() => {
|
| 74 |
+
window.scrollBy(0, distancePerStep);
|
| 75 |
+
}, delayPerStep); // 每50毫秒滚动20像素
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
function getLatestTopic() {
|
| 79 |
+
let latestPage = Number(localStorage.getItem("latestPage")) || 0;
|
| 80 |
+
let topicList = [];
|
| 81 |
+
let isDataSufficient = false;
|
| 82 |
+
|
| 83 |
+
while (!isDataSufficient) {
|
| 84 |
+
latestPage++;
|
| 85 |
+
const url = `${BASE_URL}/latest.json?no_definitions=true&page=${latestPage}`;
|
| 86 |
+
|
| 87 |
+
$.ajax({
|
| 88 |
+
url: url,
|
| 89 |
+
async: false,
|
| 90 |
+
success: function (result) {
|
| 91 |
+
if (
|
| 92 |
+
result &&
|
| 93 |
+
result.topic_list &&
|
| 94 |
+
result.topic_list.topics.length > 0
|
| 95 |
+
) {
|
| 96 |
+
result.topic_list.topics.forEach((topic) => {
|
| 97 |
+
// 未读且评论数小于 commentLimit
|
| 98 |
+
if (commentLimit > topic.posts_count) {
|
| 99 |
+
//其实不需要 !topic.unseen &&
|
| 100 |
+
topicList.push(topic);
|
| 101 |
+
}
|
| 102 |
+
});
|
| 103 |
+
|
| 104 |
+
// 检查是否已获得足够的 topics
|
| 105 |
+
if (topicList.length >= topicListLimit) {
|
| 106 |
+
isDataSufficient = true;
|
| 107 |
+
}
|
| 108 |
+
} else {
|
| 109 |
+
isDataSufficient = true; // 没有更多内容时停止请求
|
| 110 |
+
}
|
| 111 |
+
},
|
| 112 |
+
error: function (XMLHttpRequest, textStatus, errorThrown) {
|
| 113 |
+
console.error(XMLHttpRequest, textStatus, errorThrown);
|
| 114 |
+
isDataSufficient = true; // 遇到错误时也停止请求
|
| 115 |
+
},
|
| 116 |
+
});
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
+
if (topicList.length > topicListLimit) {
|
| 120 |
+
topicList = topicList.slice(0, topicListLimit);
|
| 121 |
+
}
|
| 122 |
+
|
| 123 |
+
// 其实不需要对latestPage操作
|
| 124 |
+
// localStorage.setItem("latestPage", latestPage);
|
| 125 |
+
localStorage.setItem("topicList", JSON.stringify(topicList));
|
| 126 |
+
}
|
| 127 |
+
|
| 128 |
+
function openNewTopic() {
|
| 129 |
+
let topicListStr = localStorage.getItem("topicList");
|
| 130 |
+
let topicList = topicListStr ? JSON.parse(topicListStr) : [];
|
| 131 |
+
|
| 132 |
+
// 如果列表为空,则获取最新文章
|
| 133 |
+
if (topicList.length === 0) {
|
| 134 |
+
getLatestTopic();
|
| 135 |
+
topicListStr = localStorage.getItem("topicList");
|
| 136 |
+
topicList = topicListStr ? JSON.parse(topicListStr) : [];
|
| 137 |
+
}
|
| 138 |
+
|
| 139 |
+
// 如果获取到新文章,打开第一个
|
| 140 |
+
if (topicList.length > 0) {
|
| 141 |
+
const topic = topicList.shift();
|
| 142 |
+
localStorage.setItem("topicList", JSON.stringify(topicList));
|
| 143 |
+
if (topic.last_read_post_number) {
|
| 144 |
+
window.location.href = `${BASE_URL}/t/topic/${topic.id}/${topic.last_read_post_number}`;
|
| 145 |
+
} else {
|
| 146 |
+
window.location.href = `${BASE_URL}/t/topic/${topic.id}`;
|
| 147 |
+
}
|
| 148 |
+
}
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
// 检查是否已滚动到底部(不断重复执行),到底部时跳转到下一个话题
|
| 152 |
+
function checkScroll() {
|
| 153 |
+
if (localStorage.getItem("read")) {
|
| 154 |
+
if (
|
| 155 |
+
window.innerHeight + window.scrollY >=
|
| 156 |
+
document.body.offsetHeight - 100
|
| 157 |
+
) {
|
| 158 |
+
console.log("已滚动到底部");
|
| 159 |
+
openNewTopic();
|
| 160 |
+
} else {
|
| 161 |
+
scrollToBottomSlowly();
|
| 162 |
+
if (checkScrollTimeout !== null) {
|
| 163 |
+
clearTimeout(checkScrollTimeout);
|
| 164 |
+
}
|
| 165 |
+
checkScrollTimeout = setTimeout(checkScroll, delay);
|
| 166 |
+
}
|
| 167 |
+
}
|
| 168 |
+
}
|
| 169 |
+
|
| 170 |
+
// 入口函数
|
| 171 |
+
window.addEventListener("load", () => {
|
| 172 |
+
checkFirstRun();
|
| 173 |
+
console.log(
|
| 174 |
+
"autoRead",
|
| 175 |
+
localStorage.getItem("read"),
|
| 176 |
+
"autoLikeEnabled",
|
| 177 |
+
localStorage.getItem("autoLikeEnabled")
|
| 178 |
+
);
|
| 179 |
+
if (localStorage.getItem("read") === "true") {
|
| 180 |
+
console.log("执行正常的滚动和检查逻辑");
|
| 181 |
+
checkScroll();
|
| 182 |
+
if (isAutoLikeEnabled()) {
|
| 183 |
+
autoLike();
|
| 184 |
+
}
|
| 185 |
+
}
|
| 186 |
+
});
|
| 187 |
+
|
| 188 |
+
// 获取当前时间戳
|
| 189 |
+
const currentTime = Date.now();
|
| 190 |
+
// 获取存储的时间戳
|
| 191 |
+
const defaultTimestamp = new Date("1999-01-01T00:00:00Z").getTime(); //默认值为1999年
|
| 192 |
+
const storedTime = parseInt(
|
| 193 |
+
localStorage.getItem("clickCounterTimestamp") ||
|
| 194 |
+
defaultTimestamp.toString(),
|
| 195 |
+
10
|
| 196 |
+
);
|
| 197 |
+
|
| 198 |
+
// 获取当前的点击计数,如果不存在则初始化为0
|
| 199 |
+
let clickCounter = parseInt(localStorage.getItem("clickCounter") || "0", 10);
|
| 200 |
+
// 检查是否超过24小时(24小时 = 24 * 60 * 60 * 1000 毫秒)
|
| 201 |
+
if (currentTime - storedTime > 24 * 60 * 60 * 1000) {
|
| 202 |
+
// 超过24小时,清空点击计数器并更新时间戳
|
| 203 |
+
clickCounter = 0;
|
| 204 |
+
localStorage.setItem("clickCounter", "0");
|
| 205 |
+
localStorage.setItem("clickCounterTimestamp", currentTime.toString());
|
| 206 |
+
}
|
| 207 |
+
|
| 208 |
+
console.log(`Initial clickCounter: ${clickCounter}`);
|
| 209 |
+
function triggerClick(button) {
|
| 210 |
+
const event = new MouseEvent("click", {
|
| 211 |
+
bubbles: true,
|
| 212 |
+
cancelable: true,
|
| 213 |
+
view: window,
|
| 214 |
+
});
|
| 215 |
+
button.dispatchEvent(event);
|
| 216 |
+
}
|
| 217 |
+
|
| 218 |
+
function autoLike() {
|
| 219 |
+
console.log(`Initial clickCounter: ${clickCounter}`);
|
| 220 |
+
// 寻找所有的discourse-reactions-reaction-button
|
| 221 |
+
const buttons = document.querySelectorAll(
|
| 222 |
+
".discourse-reactions-reaction-button"
|
| 223 |
+
);
|
| 224 |
+
if (buttons.length === 0) {
|
| 225 |
+
console.error(
|
| 226 |
+
"No buttons found with the selector '.discourse-reactions-reaction-button'"
|
| 227 |
+
);
|
| 228 |
+
return;
|
| 229 |
+
}
|
| 230 |
+
console.log(`Found ${buttons.length} buttons.`); // 调试信息
|
| 231 |
+
|
| 232 |
+
// 逐个点击找到的按钮
|
| 233 |
+
buttons.forEach((button, index) => {
|
| 234 |
+
if (
|
| 235 |
+
(button.title !== "点赞此帖子" && button.title !== "Like this post") ||
|
| 236 |
+
clickCounter >= likeLimit
|
| 237 |
+
) {
|
| 238 |
+
return;
|
| 239 |
+
}
|
| 240 |
+
|
| 241 |
+
// 使用setTimeout来错开每次点击的时间,避免同时触发点击
|
| 242 |
+
autoLikeInterval = setTimeout(() => {
|
| 243 |
+
// 模拟点击
|
| 244 |
+
triggerClick(button); // 使用自定义的触发点击方法
|
| 245 |
+
console.log(`Clicked like button ${index + 1}`);
|
| 246 |
+
clickCounter++; // 更新点击计数器
|
| 247 |
+
// 将新的点击计数存储到localStorage
|
| 248 |
+
localStorage.setItem("clickCounter", clickCounter.toString());
|
| 249 |
+
// 如果点击次数达到likeLimit次,则设置点赞变量为false
|
| 250 |
+
if (clickCounter === likeLimit) {
|
| 251 |
+
console.log(
|
| 252 |
+
`Reached ${likeLimit} likes, setting the like variable to false.`
|
| 253 |
+
);
|
| 254 |
+
localStorage.setItem("autoLikeEnabled", "false"); // 使用localStorage存储点赞变量状态
|
| 255 |
+
} else {
|
| 256 |
+
console.log("clickCounter:", clickCounter);
|
| 257 |
+
}
|
| 258 |
+
}, index * 3000); // 这里的1000毫秒是两次点击之间的间隔,可以根据需要调整
|
| 259 |
+
});
|
| 260 |
+
}
|
| 261 |
+
const button = document.createElement("button");
|
| 262 |
+
// 初始化按钮文本基于当前的阅读状态
|
| 263 |
+
button.textContent =
|
| 264 |
+
localStorage.getItem("read") === "true" ? "停止阅读" : "开始阅读";
|
| 265 |
+
button.style.position = "fixed";
|
| 266 |
+
button.style.bottom = "10px"; // 之前是 top
|
| 267 |
+
button.style.left = "10px"; // 之前是 right
|
| 268 |
+
button.style.zIndex = 1000;
|
| 269 |
+
button.style.backgroundColor = "#f0f0f0"; // 浅灰色背景
|
| 270 |
+
button.style.color = "#000"; // 黑色文本
|
| 271 |
+
button.style.border = "1px solid #ddd"; // 浅灰色边框
|
| 272 |
+
button.style.padding = "5px 10px"; // 内边距
|
| 273 |
+
button.style.borderRadius = "5px"; // 圆角
|
| 274 |
+
document.body.appendChild(button);
|
| 275 |
+
|
| 276 |
+
button.onclick = function () {
|
| 277 |
+
const currentlyReading = localStorage.getItem("read") === "true";
|
| 278 |
+
const newReadState = !currentlyReading;
|
| 279 |
+
localStorage.setItem("read", newReadState.toString());
|
| 280 |
+
button.textContent = newReadState ? "停止阅读" : "开始阅读";
|
| 281 |
+
if (!newReadState) {
|
| 282 |
+
if (scrollInterval !== null) {
|
| 283 |
+
clearInterval(scrollInterval);
|
| 284 |
+
scrollInterval = null;
|
| 285 |
+
}
|
| 286 |
+
if (checkScrollTimeout !== null) {
|
| 287 |
+
clearTimeout(checkScrollTimeout);
|
| 288 |
+
checkScrollTimeout = null;
|
| 289 |
+
}
|
| 290 |
+
localStorage.removeItem("navigatingToNextTopic");
|
| 291 |
+
} else {
|
| 292 |
+
// 如果是Linuxdo,就导航到我的帖子
|
| 293 |
+
if (BASE_URL == "https://linux.do") {
|
| 294 |
+
window.location.href = "https://linux.do/t/topic/13716/700";
|
| 295 |
+
} else {
|
| 296 |
+
window.location.href = `${BASE_URL}/t/topic/1`;
|
| 297 |
+
}
|
| 298 |
+
checkScroll();
|
| 299 |
+
}
|
| 300 |
+
};
|
| 301 |
+
|
| 302 |
+
//自动点赞按钮
|
| 303 |
+
// 在页面上添加一个控制自动点赞的按钮
|
| 304 |
+
const toggleAutoLikeButton = document.createElement("button");
|
| 305 |
+
toggleAutoLikeButton.textContent = isAutoLikeEnabled()
|
| 306 |
+
? "禁用自动点赞"
|
| 307 |
+
: "启用自动点赞";
|
| 308 |
+
toggleAutoLikeButton.style.position = "fixed";
|
| 309 |
+
toggleAutoLikeButton.style.bottom = "50px"; // 之前是 top,且与另一个按钮错开位置
|
| 310 |
+
toggleAutoLikeButton.style.left = "10px"; // 之前是 right
|
| 311 |
+
toggleAutoLikeButton.style.zIndex = "1000";
|
| 312 |
+
toggleAutoLikeButton.style.backgroundColor = "#f0f0f0"; // 浅灰色背景
|
| 313 |
+
toggleAutoLikeButton.style.color = "#000"; // 黑色文本
|
| 314 |
+
toggleAutoLikeButton.style.border = "1px solid #ddd"; // 浅灰色边框
|
| 315 |
+
toggleAutoLikeButton.style.padding = "5px 10px"; // 内边距
|
| 316 |
+
toggleAutoLikeButton.style.borderRadius = "5px"; // 圆角
|
| 317 |
+
document.body.appendChild(toggleAutoLikeButton);
|
| 318 |
+
|
| 319 |
+
// 为按钮添加点击事件处理函数
|
| 320 |
+
toggleAutoLikeButton.addEventListener("click", () => {
|
| 321 |
+
const isEnabled = !isAutoLikeEnabled();
|
| 322 |
+
setAutoLikeEnabled(isEnabled);
|
| 323 |
+
toggleAutoLikeButton.textContent = isEnabled
|
| 324 |
+
? "禁用自动点赞"
|
| 325 |
+
: "启用自动点赞";
|
| 326 |
+
});
|
| 327 |
+
// 判断是否启用自动点赞
|
| 328 |
+
function isAutoLikeEnabled() {
|
| 329 |
+
// 从localStorage获取autoLikeEnabled的值,如果未设置,默认为"true"
|
| 330 |
+
return localStorage.getItem("autoLikeEnabled") !== "false";
|
| 331 |
+
}
|
| 332 |
+
|
| 333 |
+
// 设置自动点赞的启用状态
|
| 334 |
+
function setAutoLikeEnabled(enabled) {
|
| 335 |
+
localStorage.setItem("autoLikeEnabled", enabled ? "true" : "false");
|
| 336 |
+
}
|
| 337 |
+
})();
|
index_likeUser.js
ADDED
|
@@ -0,0 +1,395 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// ==UserScript==
|
| 2 |
+
// @name Auto Like Specific User
|
| 3 |
+
// @namespace http://tampermonkey.net/
|
| 4 |
+
// @version 1.1.2
|
| 5 |
+
// @description 自动点赞特定用户,适用于discourse
|
| 6 |
+
// @author liuweiqing
|
| 7 |
+
// @match https://meta.discourse.org/*
|
| 8 |
+
// @match https://linux.do/*
|
| 9 |
+
// @match https://meta.appinn.net/*
|
| 10 |
+
// @match https://community.openai.com/
|
| 11 |
+
// @grant none
|
| 12 |
+
// @license MIT
|
| 13 |
+
// @icon https://www.google.com/s2/favicons?domain=linux.do
|
| 14 |
+
// @downloadURL https://update.greasyfork.org/scripts/489464/Auto%20Read.user.js
|
| 15 |
+
// @updateURL https://update.greasyfork.org/scripts/489464/Auto%20Read.meta.js
|
| 16 |
+
// @run-at document-end
|
| 17 |
+
// ==/UserScript==
|
| 18 |
+
|
| 19 |
+
(function () {
|
| 20 |
+
("use strict");
|
| 21 |
+
// 定义可能的基本URL
|
| 22 |
+
const possibleBaseURLs = [
|
| 23 |
+
"https://meta.discourse.org",
|
| 24 |
+
"https://linux.do",
|
| 25 |
+
"https://meta.appinn.net",
|
| 26 |
+
"https://community.openai.com",
|
| 27 |
+
];
|
| 28 |
+
const commentLimit = 1000;
|
| 29 |
+
const specificUserPostListLimit = 100;
|
| 30 |
+
const currentURL = window.location.href;
|
| 31 |
+
let specificUser = localStorage.getItem("specificUser") || "14790897";
|
| 32 |
+
let likeLimit = parseInt(localStorage.getItem("likeLimit") || 200, 10);
|
| 33 |
+
let BASE_URL = possibleBaseURLs.find((url) => currentURL.startsWith(url));
|
| 34 |
+
|
| 35 |
+
// 环境变量:阅读网址,如果没有找到匹配的URL,则默认为第一个
|
| 36 |
+
if (!BASE_URL) {
|
| 37 |
+
BASE_URL = possibleBaseURLs[0];
|
| 38 |
+
console.log("当前BASE_URL设置为(默认): " + BASE_URL);
|
| 39 |
+
} else {
|
| 40 |
+
console.log("当前BASE_URL是: " + BASE_URL);
|
| 41 |
+
}
|
| 42 |
+
// 获取当前时间戳
|
| 43 |
+
const currentTime = Date.now();
|
| 44 |
+
// 获取存储的时间戳
|
| 45 |
+
const defaultTimestamp = new Date("1999-01-01T00:00:00Z").getTime(); //默认值为1999年
|
| 46 |
+
const storedTime = parseInt(
|
| 47 |
+
localStorage.getItem("clickCounterTimestamp") ||
|
| 48 |
+
defaultTimestamp.toString(),
|
| 49 |
+
10
|
| 50 |
+
);
|
| 51 |
+
|
| 52 |
+
// 获取当前的点击计数,如果不存在则初始化为0
|
| 53 |
+
let clickCounter = parseInt(localStorage.getItem("clickCounter") || "0", 10);
|
| 54 |
+
// 检查是否超过12小时(12小时 = 12 * 60 * 60 * 1000 毫秒)
|
| 55 |
+
if (currentTime - storedTime > 12 * 60 * 60 * 1000) {
|
| 56 |
+
// 超过24小时,清空点击计数器并更新时间戳
|
| 57 |
+
clickCounter = 0;
|
| 58 |
+
localStorage.setItem("clickCounter", "0");
|
| 59 |
+
localStorage.setItem("clickCounterTimestamp", currentTime.toString());
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
console.log(`Initial clickCounter: ${clickCounter}`);
|
| 63 |
+
// 入口函数
|
| 64 |
+
window.addEventListener("load", () => {
|
| 65 |
+
console.log("autoRead", localStorage.getItem("read"));
|
| 66 |
+
checkFirstRun();
|
| 67 |
+
if (localStorage.getItem("read") === "true") {
|
| 68 |
+
console.log("点赞开始");
|
| 69 |
+
setTimeout(() => {
|
| 70 |
+
likeSpecificPost();
|
| 71 |
+
}, 2000);
|
| 72 |
+
setTimeout(() => {
|
| 73 |
+
openSpecificUserPost();
|
| 74 |
+
}, 4000);
|
| 75 |
+
}
|
| 76 |
+
});
|
| 77 |
+
function checkFirstRun() {
|
| 78 |
+
if (localStorage.getItem("isFirstRun") === null) {
|
| 79 |
+
console.log("脚本第一次运行,执行初始化操作...");
|
| 80 |
+
updateInitialData();
|
| 81 |
+
localStorage.setItem("isFirstRun", "false");
|
| 82 |
+
} else {
|
| 83 |
+
console.log("脚本非第一次运行");
|
| 84 |
+
}
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
function updateInitialData() {
|
| 88 |
+
localStorage.setItem("read", "false"); // 开始时自动滚动关闭
|
| 89 |
+
console.log("执行了初始数据更新操作");
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
function getLatestTopic() {
|
| 93 |
+
let lastOffset = Number(localStorage.getItem("lastOffset")) || 0;
|
| 94 |
+
let specificUserPostList = [];
|
| 95 |
+
let isDataSufficient = false;
|
| 96 |
+
|
| 97 |
+
while (!isDataSufficient) {
|
| 98 |
+
// lastOffset += 20;
|
| 99 |
+
lastOffset += 1; //对于page来说
|
| 100 |
+
// 举例:https://linux.do/user_actions.json?offset=0&username=14790897&filter=5
|
| 101 |
+
// const url = `${BASE_URL}/user_actions.json?offset=${lastOffset}&username=${specificUser}&filter=5`;
|
| 102 |
+
//举例:https://linux.do/search?q=%4014790897%20in%3Aunseen
|
| 103 |
+
const url = `${BASE_URL}/search?q=%40${specificUser}%20in%3Aunseen`; //&page=${lastOffset}
|
| 104 |
+
$.ajax({
|
| 105 |
+
url: url,
|
| 106 |
+
async: false,
|
| 107 |
+
headers: {
|
| 108 |
+
Accept: "application/json",
|
| 109 |
+
},
|
| 110 |
+
success: function (result) {
|
| 111 |
+
// if (result && result.user_actions && result.user_actions.length > 0) {
|
| 112 |
+
// result.user_actions.forEach((action) => {
|
| 113 |
+
if (result && result.posts && result.posts.length > 0) {
|
| 114 |
+
result.posts.forEach((action) => {
|
| 115 |
+
const topicId = action.topic_id;
|
| 116 |
+
// const postId = action.post_id;
|
| 117 |
+
const postNumber = action.post_number;
|
| 118 |
+
specificUserPostList.push({
|
| 119 |
+
topic_id: topicId,
|
| 120 |
+
// post_id: postId,
|
| 121 |
+
post_number: postNumber,
|
| 122 |
+
});
|
| 123 |
+
});
|
| 124 |
+
|
| 125 |
+
// 检查是否已获得足够的 Posts
|
| 126 |
+
if (specificUserPostList.length >= specificUserPostListLimit) {
|
| 127 |
+
isDataSufficient = true;
|
| 128 |
+
}
|
| 129 |
+
} else {
|
| 130 |
+
isDataSufficient = true; // 没有更多内容时停止请求
|
| 131 |
+
}
|
| 132 |
+
},
|
| 133 |
+
error: function (XMLHttpRequest, textStatus, errorThrown) {
|
| 134 |
+
console.error(XMLHttpRequest, textStatus, errorThrown);
|
| 135 |
+
isDataSufficient = true; // 遇到错误时也停止请求
|
| 136 |
+
},
|
| 137 |
+
});
|
| 138 |
+
}
|
| 139 |
+
|
| 140 |
+
// 如果列表超出限制,则截断
|
| 141 |
+
if (specificUserPostList.length > specificUserPostListLimit) {
|
| 142 |
+
specificUserPostList = specificUserPostList.slice(
|
| 143 |
+
0,
|
| 144 |
+
specificUserPostListLimit
|
| 145 |
+
);
|
| 146 |
+
}
|
| 147 |
+
|
| 148 |
+
// 存储 lastOffset 和 specificUserPostList 到 localStorage
|
| 149 |
+
localStorage.setItem("lastOffset", lastOffset);
|
| 150 |
+
localStorage.setItem(
|
| 151 |
+
"specificUserPostList",
|
| 152 |
+
JSON.stringify(specificUserPostList)
|
| 153 |
+
);
|
| 154 |
+
}
|
| 155 |
+
|
| 156 |
+
function openSpecificUserPost() {
|
| 157 |
+
let specificUserPostListStr = localStorage.getItem("specificUserPostList");
|
| 158 |
+
let specificUserPostList = specificUserPostListStr
|
| 159 |
+
? JSON.parse(specificUserPostListStr)
|
| 160 |
+
: [];
|
| 161 |
+
|
| 162 |
+
// 如果列表为空,则获取最新文章
|
| 163 |
+
if (specificUserPostList.length === 0) {
|
| 164 |
+
getLatestTopic();
|
| 165 |
+
specificUserPostListStr = localStorage.getItem("specificUserPostList");
|
| 166 |
+
specificUserPostList = specificUserPostListStr
|
| 167 |
+
? JSON.parse(specificUserPostListStr)
|
| 168 |
+
: [];
|
| 169 |
+
}
|
| 170 |
+
|
| 171 |
+
// 如果获取到新文章,打开第一个
|
| 172 |
+
if (specificUserPostList.length > 0) {
|
| 173 |
+
const post = specificUserPostList.shift(); // 获取列表中的第一个对象
|
| 174 |
+
localStorage.setItem(
|
| 175 |
+
"specificUserPostList",
|
| 176 |
+
JSON.stringify(specificUserPostList)
|
| 177 |
+
);
|
| 178 |
+
|
| 179 |
+
window.location.href = `${BASE_URL}/t/topic/${post.topic_id}/${post.post_number}`;
|
| 180 |
+
} else {
|
| 181 |
+
console.error("未能获取到新的帖子数据。");
|
| 182 |
+
}
|
| 183 |
+
}
|
| 184 |
+
|
| 185 |
+
// 检查是否点赞
|
| 186 |
+
// const postId = data.post_id;
|
| 187 |
+
|
| 188 |
+
// const targetId = `discourse-reactions-counter-${postId}-right`;
|
| 189 |
+
|
| 190 |
+
// const element = document.getElementById(targetId);
|
| 191 |
+
function likeSpecificPost() {
|
| 192 |
+
const urlParts = window.location.pathname.split("/");
|
| 193 |
+
const lastPart = urlParts[urlParts.length - 1]; // 获取最后一部分
|
| 194 |
+
let buttons, reactionButton;
|
| 195 |
+
console.log("post number:", lastPart);
|
| 196 |
+
if (lastPart < 10000) {
|
| 197 |
+
buttons = document.querySelectorAll(
|
| 198 |
+
"button[aria-label]" //[class*='reply']
|
| 199 |
+
);
|
| 200 |
+
|
| 201 |
+
let targetButton = null;
|
| 202 |
+
buttons.forEach((button) => {
|
| 203 |
+
const ariaLabel = button.getAttribute("aria-label");
|
| 204 |
+
if (ariaLabel && ariaLabel.includes(`#${lastPart}`)) {
|
| 205 |
+
targetButton = button;
|
| 206 |
+
console.log("找到post number按钮:", targetButton);
|
| 207 |
+
return;
|
| 208 |
+
}
|
| 209 |
+
});
|
| 210 |
+
|
| 211 |
+
if (targetButton) {
|
| 212 |
+
// 找到按钮后,获取其父级元素
|
| 213 |
+
const parentElement = targetButton.parentElement;
|
| 214 |
+
console.log("父级元素:", parentElement);
|
| 215 |
+
reactionButton = parentElement.querySelector(
|
| 216 |
+
".discourse-reactions-reaction-button"
|
| 217 |
+
);
|
| 218 |
+
} else {
|
| 219 |
+
console.log(`未找到包含 #${lastPart} 的按钮`);
|
| 220 |
+
}
|
| 221 |
+
} else {
|
| 222 |
+
//大于10000说明是主题帖,选择第一个
|
| 223 |
+
reactionButton = document.querySelectorAll(
|
| 224 |
+
".discourse-reactions-reaction-button"
|
| 225 |
+
)[0];
|
| 226 |
+
}
|
| 227 |
+
if (
|
| 228 |
+
reactionButton.title !== "点赞此帖子" &&
|
| 229 |
+
reactionButton.title !== "Like this post"
|
| 230 |
+
) {
|
| 231 |
+
console.log("已经点赞过");
|
| 232 |
+
return "already liked";
|
| 233 |
+
} else if (clickCounter >= likeLimit) {
|
| 234 |
+
console.log("已经达到点赞上限");
|
| 235 |
+
localStorage.setItem("read", false);
|
| 236 |
+
return;
|
| 237 |
+
}
|
| 238 |
+
triggerClick(reactionButton);
|
| 239 |
+
clickCounter++;
|
| 240 |
+
console.log(
|
| 241 |
+
`Clicked like button ${clickCounter},已点赞用户${specificUser}`
|
| 242 |
+
);
|
| 243 |
+
localStorage.setItem("clickCounter", clickCounter.toString());
|
| 244 |
+
// 如果点击次数达到likeLimit次,则设置点赞变量为false
|
| 245 |
+
if (clickCounter === likeLimit) {
|
| 246 |
+
console.log(
|
| 247 |
+
`Reached ${likeLimit} likes, setting the like variable to false.`
|
| 248 |
+
);
|
| 249 |
+
localStorage.setItem("read", false);
|
| 250 |
+
} else {
|
| 251 |
+
console.log("clickCounter:", clickCounter);
|
| 252 |
+
}
|
| 253 |
+
}
|
| 254 |
+
|
| 255 |
+
function triggerClick(button) {
|
| 256 |
+
const event = new MouseEvent("click", {
|
| 257 |
+
bubbles: true,
|
| 258 |
+
cancelable: true,
|
| 259 |
+
view: window,
|
| 260 |
+
});
|
| 261 |
+
button.dispatchEvent(event);
|
| 262 |
+
}
|
| 263 |
+
|
| 264 |
+
const button = document.createElement("button");
|
| 265 |
+
button.textContent =
|
| 266 |
+
localStorage.getItem("read") === "true" ? "停止阅读" : "开始阅读";
|
| 267 |
+
button.style.position = "fixed";
|
| 268 |
+
button.style.bottom = "20px";
|
| 269 |
+
button.style.left = "20px";
|
| 270 |
+
button.style.zIndex = 1000;
|
| 271 |
+
button.style.backgroundColor = "#e0e0e0";
|
| 272 |
+
button.style.color = "#333";
|
| 273 |
+
button.style.border = "1px solid #aaa";
|
| 274 |
+
button.style.padding = "8px 16px";
|
| 275 |
+
button.style.borderRadius = "8px";
|
| 276 |
+
document.body.appendChild(button);
|
| 277 |
+
|
| 278 |
+
button.onclick = function () {
|
| 279 |
+
const currentlyReading = localStorage.getItem("read") === "true";
|
| 280 |
+
const newReadState = !currentlyReading;
|
| 281 |
+
localStorage.setItem("read", newReadState.toString());
|
| 282 |
+
button.textContent = newReadState ? "停止阅读" : "开始阅读";
|
| 283 |
+
if (newReadState) {
|
| 284 |
+
if (BASE_URL == "https://linux.do") {
|
| 285 |
+
const maxPostNumber = 600;
|
| 286 |
+
const randomPostNumber = Math.floor(Math.random() * maxPostNumber) + 1;
|
| 287 |
+
const newUrl = `https://linux.do/t/topic/13716/${randomPostNumber}`;
|
| 288 |
+
window.location.href = newUrl;
|
| 289 |
+
} else {
|
| 290 |
+
window.location.href = `${BASE_URL}/t/topic/1`;
|
| 291 |
+
}
|
| 292 |
+
}
|
| 293 |
+
};
|
| 294 |
+
|
| 295 |
+
const userInput = document.createElement("input");
|
| 296 |
+
userInput.type = "text";
|
| 297 |
+
userInput.placeholder = "输入要点赞的用户ID";
|
| 298 |
+
userInput.style.position = "fixed";
|
| 299 |
+
userInput.style.bottom = "90px";
|
| 300 |
+
userInput.style.left = "20px";
|
| 301 |
+
userInput.style.zIndex = "1000";
|
| 302 |
+
userInput.style.padding = "6px";
|
| 303 |
+
userInput.style.border = "1px solid #aaa";
|
| 304 |
+
userInput.style.borderRadius = "8px";
|
| 305 |
+
userInput.style.backgroundColor = "#e0e0e0";
|
| 306 |
+
userInput.style.width = "100px";
|
| 307 |
+
userInput.value = localStorage.getItem("specificUser") || "14790897";
|
| 308 |
+
|
| 309 |
+
document.body.appendChild(userInput);
|
| 310 |
+
|
| 311 |
+
const saveUserButton = document.createElement("button");
|
| 312 |
+
saveUserButton.textContent = "保存用户ID";
|
| 313 |
+
saveUserButton.style.position = "fixed";
|
| 314 |
+
saveUserButton.style.bottom = "60px";
|
| 315 |
+
saveUserButton.style.left = "20px";
|
| 316 |
+
saveUserButton.style.zIndex = "1000";
|
| 317 |
+
saveUserButton.style.backgroundColor = "#e0e0e0";
|
| 318 |
+
saveUserButton.style.color = "#333";
|
| 319 |
+
saveUserButton.style.border = "1px solid #aaa";
|
| 320 |
+
saveUserButton.style.padding = "8px 16px";
|
| 321 |
+
saveUserButton.style.borderRadius = "8px";
|
| 322 |
+
document.body.appendChild(saveUserButton);
|
| 323 |
+
|
| 324 |
+
saveUserButton.onclick = function () {
|
| 325 |
+
const newSpecificUser = userInput.value.trim();
|
| 326 |
+
if (newSpecificUser) {
|
| 327 |
+
localStorage.setItem("specificUser", newSpecificUser);
|
| 328 |
+
localStorage.removeItem("specificUserPostList");
|
| 329 |
+
localStorage.removeItem("lastOffset");
|
| 330 |
+
specificUser = newSpecificUser;
|
| 331 |
+
console.log(
|
| 332 |
+
`新的specificUser已保存: ${specificUser},specificUserPostList已重置`
|
| 333 |
+
);
|
| 334 |
+
}
|
| 335 |
+
};
|
| 336 |
+
|
| 337 |
+
const likeLimitInput = document.createElement("input");
|
| 338 |
+
likeLimitInput.type = "number";
|
| 339 |
+
likeLimitInput.placeholder = "输入点赞数量";
|
| 340 |
+
likeLimitInput.style.position = "fixed";
|
| 341 |
+
likeLimitInput.style.bottom = "180px";
|
| 342 |
+
likeLimitInput.style.left = "20px";
|
| 343 |
+
likeLimitInput.style.zIndex = "1000";
|
| 344 |
+
likeLimitInput.style.padding = "6px";
|
| 345 |
+
likeLimitInput.style.border = "1px solid #aaa";
|
| 346 |
+
likeLimitInput.style.borderRadius = "8px";
|
| 347 |
+
likeLimitInput.style.backgroundColor = "#e0e0e0";
|
| 348 |
+
likeLimitInput.style.width = "100px";
|
| 349 |
+
likeLimitInput.value = localStorage.getItem("likeLimit") || 200;
|
| 350 |
+
document.body.appendChild(likeLimitInput);
|
| 351 |
+
|
| 352 |
+
const saveLikeLimitButton = document.createElement("button");
|
| 353 |
+
saveLikeLimitButton.textContent = "保存点赞数量";
|
| 354 |
+
saveLikeLimitButton.style.position = "fixed";
|
| 355 |
+
saveLikeLimitButton.style.bottom = "140px";
|
| 356 |
+
saveLikeLimitButton.style.left = "20px";
|
| 357 |
+
saveLikeLimitButton.style.zIndex = "1000";
|
| 358 |
+
saveLikeLimitButton.style.backgroundColor = "#e0e0e0";
|
| 359 |
+
saveLikeLimitButton.style.color = "#333";
|
| 360 |
+
saveLikeLimitButton.style.border = "1px solid #aaa";
|
| 361 |
+
saveLikeLimitButton.style.padding = "8px 16px";
|
| 362 |
+
saveLikeLimitButton.style.borderRadius = "8px";
|
| 363 |
+
document.body.appendChild(saveLikeLimitButton);
|
| 364 |
+
|
| 365 |
+
saveLikeLimitButton.onclick = function () {
|
| 366 |
+
const newLikeLimit = parseInt(likeLimitInput.value.trim(), 10);
|
| 367 |
+
if (newLikeLimit && newLikeLimit > 0) {
|
| 368 |
+
localStorage.setItem("likeLimit", newLikeLimit);
|
| 369 |
+
likeLimit = newLikeLimit;
|
| 370 |
+
console.log(`新的likeLimit已保存: ${likeLimit}`);
|
| 371 |
+
}
|
| 372 |
+
};
|
| 373 |
+
|
| 374 |
+
// 增加清除数据的按钮
|
| 375 |
+
const clearDataButton = document.createElement("button");
|
| 376 |
+
clearDataButton.textContent = "清除所有数据";
|
| 377 |
+
clearDataButton.style.position = "fixed";
|
| 378 |
+
clearDataButton.style.bottom = "20px";
|
| 379 |
+
clearDataButton.style.left = "140px";
|
| 380 |
+
clearDataButton.style.zIndex = "1000";
|
| 381 |
+
clearDataButton.style.backgroundColor = "#ff6666"; // 红色背景,提示删除操作
|
| 382 |
+
clearDataButton.style.color = "#fff"; // 白色文本
|
| 383 |
+
clearDataButton.style.border = "1px solid #ff3333"; // 深红色边框
|
| 384 |
+
clearDataButton.style.padding = "8px 16px";
|
| 385 |
+
clearDataButton.style.borderRadius = "8px";
|
| 386 |
+
document.body.appendChild(clearDataButton);
|
| 387 |
+
|
| 388 |
+
clearDataButton.onclick = function () {
|
| 389 |
+
localStorage.removeItem("lastOffset");
|
| 390 |
+
localStorage.removeItem("clickCounter");
|
| 391 |
+
localStorage.removeItem("clickCounterTimestamp");
|
| 392 |
+
localStorage.removeItem("specificUserPostList");
|
| 393 |
+
console.log("所有数据已清除,除了 specificUser 和 specificUserPostList");
|
| 394 |
+
};
|
| 395 |
+
})();
|
index_likeUser_activity.js
ADDED
|
@@ -0,0 +1,395 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// ==UserScript==
|
| 2 |
+
// @name Auto Like Specific User base on activity
|
| 3 |
+
// @namespace http://tampermonkey.net/
|
| 4 |
+
// @version 1.1.2
|
| 5 |
+
// @description 自动点赞特定用户,适用于discourse
|
| 6 |
+
// @author liuweiqing
|
| 7 |
+
// @match https://meta.discourse.org/*
|
| 8 |
+
// @match https://linux.do/*
|
| 9 |
+
// @match https://meta.appinn.net/*
|
| 10 |
+
// @match https://community.openai.com/
|
| 11 |
+
// @grant none
|
| 12 |
+
// @license MIT
|
| 13 |
+
// @icon https://www.google.com/s2/favicons?domain=linux.do
|
| 14 |
+
// @downloadURL https://update.greasyfork.org/scripts/489464/Auto%20Read.user.js
|
| 15 |
+
// @updateURL https://update.greasyfork.org/scripts/489464/Auto%20Read.meta.js
|
| 16 |
+
// @run-at document-end
|
| 17 |
+
// ==/UserScript==
|
| 18 |
+
|
| 19 |
+
(function () {
|
| 20 |
+
("use strict");
|
| 21 |
+
// 定义可能的基本URL
|
| 22 |
+
const possibleBaseURLs = [
|
| 23 |
+
"https://meta.discourse.org",
|
| 24 |
+
"https://linux.do",
|
| 25 |
+
"https://meta.appinn.net",
|
| 26 |
+
"https://community.openai.com",
|
| 27 |
+
];
|
| 28 |
+
const commentLimit = 1000;
|
| 29 |
+
const specificUserPostListLimit = 100;
|
| 30 |
+
const currentURL = window.location.href;
|
| 31 |
+
let specificUser = localStorage.getItem("specificUser") || "14790897";
|
| 32 |
+
let likeLimit = parseInt(localStorage.getItem("likeLimit") || 200, 10);
|
| 33 |
+
let BASE_URL = possibleBaseURLs.find((url) => currentURL.startsWith(url));
|
| 34 |
+
|
| 35 |
+
// 环境变量:阅读网址,如果没有找到匹配的URL,则默认为第一个
|
| 36 |
+
if (!BASE_URL) {
|
| 37 |
+
BASE_URL = possibleBaseURLs[0];
|
| 38 |
+
console.log("当前BASE_URL设置为(默认): " + BASE_URL);
|
| 39 |
+
} else {
|
| 40 |
+
console.log("当前BASE_URL是: " + BASE_URL);
|
| 41 |
+
}
|
| 42 |
+
// 获取当前时间戳
|
| 43 |
+
const currentTime = Date.now();
|
| 44 |
+
// 获取存储的时间戳
|
| 45 |
+
const defaultTimestamp = new Date("1999-01-01T00:00:00Z").getTime(); //默认值为1999年
|
| 46 |
+
const storedTime = parseInt(
|
| 47 |
+
localStorage.getItem("clickCounterTimestamp") ||
|
| 48 |
+
defaultTimestamp.toString(),
|
| 49 |
+
10
|
| 50 |
+
);
|
| 51 |
+
|
| 52 |
+
// 获取当前的点击计数,如果不存在则初始化为0
|
| 53 |
+
let clickCounter = parseInt(localStorage.getItem("clickCounter") || "0", 10);
|
| 54 |
+
// 检查是否超过12小时(12小时 = 12 * 60 * 60 * 1000 毫秒)
|
| 55 |
+
if (currentTime - storedTime > 12 * 60 * 60 * 1000) {
|
| 56 |
+
// 超过24小时,清空点击计数器并更新时间戳
|
| 57 |
+
clickCounter = 0;
|
| 58 |
+
localStorage.setItem("clickCounter", "0");
|
| 59 |
+
localStorage.setItem("clickCounterTimestamp", currentTime.toString());
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
console.log(`Initial clickCounter: ${clickCounter}`);
|
| 63 |
+
// 入口函数
|
| 64 |
+
window.addEventListener("load", () => {
|
| 65 |
+
console.log("autoRead", localStorage.getItem("read"));
|
| 66 |
+
checkFirstRun();
|
| 67 |
+
if (localStorage.getItem("read") === "true") {
|
| 68 |
+
console.log("点赞开始");
|
| 69 |
+
setTimeout(() => {
|
| 70 |
+
likeSpecificPost();
|
| 71 |
+
}, 2000);
|
| 72 |
+
setTimeout(() => {
|
| 73 |
+
openSpecificUserPost();
|
| 74 |
+
}, 4000);
|
| 75 |
+
}
|
| 76 |
+
});
|
| 77 |
+
function checkFirstRun() {
|
| 78 |
+
if (localStorage.getItem("isFirstRun") === null) {
|
| 79 |
+
console.log("脚本第一次运行,执行初始化操作...");
|
| 80 |
+
updateInitialData();
|
| 81 |
+
localStorage.setItem("isFirstRun", "false");
|
| 82 |
+
} else {
|
| 83 |
+
console.log("脚本非第一次运行");
|
| 84 |
+
}
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
function updateInitialData() {
|
| 88 |
+
localStorage.setItem("read", "false"); // 开始时自动滚动关闭
|
| 89 |
+
console.log("执行了初始数据更新操作");
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
function getLatestTopic() {
|
| 93 |
+
let lastOffset = Number(localStorage.getItem("lastOffset")) || 0;
|
| 94 |
+
let specificUserPostList = [];
|
| 95 |
+
let isDataSufficient = false;
|
| 96 |
+
|
| 97 |
+
while (!isDataSufficient) {
|
| 98 |
+
lastOffset += 20;
|
| 99 |
+
// lastOffset += 1; //对于page来说
|
| 100 |
+
// 举例:https://linux.do/user_actions.json?offset=0&username=14790897&filter=5
|
| 101 |
+
const url = `${BASE_URL}/user_actions.json?offset=${lastOffset}&username=${specificUser}&filter=5`;
|
| 102 |
+
//举例:https://linux.do/search?q=%4014790897%20in%3Aunseen
|
| 103 |
+
// const url = `${BASE_URL}/search?q=%40${specificUser}%20in%3Aunseen`; //&page=${lastOffset}
|
| 104 |
+
$.ajax({
|
| 105 |
+
url: url,
|
| 106 |
+
async: false,
|
| 107 |
+
headers: {
|
| 108 |
+
Accept: "application/json",
|
| 109 |
+
},
|
| 110 |
+
success: function (result) {
|
| 111 |
+
if (result && result.user_actions && result.user_actions.length > 0) {
|
| 112 |
+
result.user_actions.forEach((action) => {
|
| 113 |
+
// if (result && result.posts && result.posts.length > 0) {
|
| 114 |
+
// result.posts.forEach((action) => {
|
| 115 |
+
const topicId = action.topic_id;
|
| 116 |
+
// const postId = action.post_id;
|
| 117 |
+
const postNumber = action.post_number;
|
| 118 |
+
specificUserPostList.push({
|
| 119 |
+
topic_id: topicId,
|
| 120 |
+
// post_id: postId,
|
| 121 |
+
post_number: postNumber,
|
| 122 |
+
});
|
| 123 |
+
});
|
| 124 |
+
|
| 125 |
+
// 检查是否已获得足够的 Posts
|
| 126 |
+
if (specificUserPostList.length >= specificUserPostListLimit) {
|
| 127 |
+
isDataSufficient = true;
|
| 128 |
+
}
|
| 129 |
+
} else {
|
| 130 |
+
isDataSufficient = true; // 没有更多内容时停止请求
|
| 131 |
+
}
|
| 132 |
+
},
|
| 133 |
+
error: function (XMLHttpRequest, textStatus, errorThrown) {
|
| 134 |
+
console.error(XMLHttpRequest, textStatus, errorThrown);
|
| 135 |
+
isDataSufficient = true; // 遇到错误时也停止请求
|
| 136 |
+
},
|
| 137 |
+
});
|
| 138 |
+
}
|
| 139 |
+
|
| 140 |
+
// 如果列表超出限制,则截断
|
| 141 |
+
if (specificUserPostList.length > specificUserPostListLimit) {
|
| 142 |
+
specificUserPostList = specificUserPostList.slice(
|
| 143 |
+
0,
|
| 144 |
+
specificUserPostListLimit
|
| 145 |
+
);
|
| 146 |
+
}
|
| 147 |
+
|
| 148 |
+
// 存储 lastOffset 和 specificUserPostList 到 localStorage
|
| 149 |
+
localStorage.setItem("lastOffset", lastOffset);
|
| 150 |
+
localStorage.setItem(
|
| 151 |
+
"specificUserPostList",
|
| 152 |
+
JSON.stringify(specificUserPostList)
|
| 153 |
+
);
|
| 154 |
+
}
|
| 155 |
+
|
| 156 |
+
function openSpecificUserPost() {
|
| 157 |
+
let specificUserPostListStr = localStorage.getItem("specificUserPostList");
|
| 158 |
+
let specificUserPostList = specificUserPostListStr
|
| 159 |
+
? JSON.parse(specificUserPostListStr)
|
| 160 |
+
: [];
|
| 161 |
+
|
| 162 |
+
// 如果列表为空,则获取最新文章
|
| 163 |
+
if (specificUserPostList.length === 0) {
|
| 164 |
+
getLatestTopic();
|
| 165 |
+
specificUserPostListStr = localStorage.getItem("specificUserPostList");
|
| 166 |
+
specificUserPostList = specificUserPostListStr
|
| 167 |
+
? JSON.parse(specificUserPostListStr)
|
| 168 |
+
: [];
|
| 169 |
+
}
|
| 170 |
+
|
| 171 |
+
// 如果获取到新文章,打开第一个
|
| 172 |
+
if (specificUserPostList.length > 0) {
|
| 173 |
+
const post = specificUserPostList.shift(); // 获取列表中的第一个对象
|
| 174 |
+
localStorage.setItem(
|
| 175 |
+
"specificUserPostList",
|
| 176 |
+
JSON.stringify(specificUserPostList)
|
| 177 |
+
);
|
| 178 |
+
|
| 179 |
+
window.location.href = `${BASE_URL}/t/topic/${post.topic_id}/${post.post_number}`;
|
| 180 |
+
} else {
|
| 181 |
+
console.error("未能获取到新的帖子数据。");
|
| 182 |
+
}
|
| 183 |
+
}
|
| 184 |
+
|
| 185 |
+
// 检查是否点赞
|
| 186 |
+
// const postId = data.post_id;
|
| 187 |
+
|
| 188 |
+
// const targetId = `discourse-reactions-counter-${postId}-right`;
|
| 189 |
+
|
| 190 |
+
// const element = document.getElementById(targetId);
|
| 191 |
+
function likeSpecificPost() {
|
| 192 |
+
const urlParts = window.location.pathname.split("/");
|
| 193 |
+
const lastPart = urlParts[urlParts.length - 1]; // 获取最后一部分
|
| 194 |
+
let buttons, reactionButton;
|
| 195 |
+
console.log("post number:", lastPart);
|
| 196 |
+
if (lastPart < 10000) {
|
| 197 |
+
buttons = document.querySelectorAll(
|
| 198 |
+
"button[aria-label]" //[class*='reply']
|
| 199 |
+
);
|
| 200 |
+
|
| 201 |
+
let targetButton = null;
|
| 202 |
+
buttons.forEach((button) => {
|
| 203 |
+
const ariaLabel = button.getAttribute("aria-label");
|
| 204 |
+
if (ariaLabel && ariaLabel.includes(`#${lastPart}`)) {
|
| 205 |
+
targetButton = button;
|
| 206 |
+
console.log("找到post number按钮:", targetButton);
|
| 207 |
+
return;
|
| 208 |
+
}
|
| 209 |
+
});
|
| 210 |
+
|
| 211 |
+
if (targetButton) {
|
| 212 |
+
// 找到按钮后,获取其父级元素
|
| 213 |
+
const parentElement = targetButton.parentElement;
|
| 214 |
+
console.log("父级元素:", parentElement);
|
| 215 |
+
reactionButton = parentElement.querySelector(
|
| 216 |
+
".discourse-reactions-reaction-button"
|
| 217 |
+
);
|
| 218 |
+
} else {
|
| 219 |
+
console.log(`未找到包含 #${lastPart} 的按钮`);
|
| 220 |
+
}
|
| 221 |
+
} else {
|
| 222 |
+
//大于10000说明是主题帖,选择第一个
|
| 223 |
+
reactionButton = document.querySelectorAll(
|
| 224 |
+
".discourse-reactions-reaction-button"
|
| 225 |
+
)[0];
|
| 226 |
+
}
|
| 227 |
+
if (
|
| 228 |
+
reactionButton.title !== "点赞此帖子" &&
|
| 229 |
+
reactionButton.title !== "Like this post"
|
| 230 |
+
) {
|
| 231 |
+
console.log("已经点赞过");
|
| 232 |
+
return "already liked";
|
| 233 |
+
} else if (clickCounter >= likeLimit) {
|
| 234 |
+
console.log("已经达到点赞上限");
|
| 235 |
+
localStorage.setItem("read", false);
|
| 236 |
+
return;
|
| 237 |
+
}
|
| 238 |
+
triggerClick(reactionButton);
|
| 239 |
+
clickCounter++;
|
| 240 |
+
console.log(
|
| 241 |
+
`Clicked like button ${clickCounter},已点赞用户${specificUser}`
|
| 242 |
+
);
|
| 243 |
+
localStorage.setItem("clickCounter", clickCounter.toString());
|
| 244 |
+
// 如果点击次数达到likeLimit次,则设置点赞变量为false
|
| 245 |
+
if (clickCounter === likeLimit) {
|
| 246 |
+
console.log(
|
| 247 |
+
`Reached ${likeLimit} likes, setting the like variable to false.`
|
| 248 |
+
);
|
| 249 |
+
localStorage.setItem("read", false);
|
| 250 |
+
} else {
|
| 251 |
+
console.log("clickCounter:", clickCounter);
|
| 252 |
+
}
|
| 253 |
+
}
|
| 254 |
+
|
| 255 |
+
function triggerClick(button) {
|
| 256 |
+
const event = new MouseEvent("click", {
|
| 257 |
+
bubbles: true,
|
| 258 |
+
cancelable: true,
|
| 259 |
+
view: window,
|
| 260 |
+
});
|
| 261 |
+
button.dispatchEvent(event);
|
| 262 |
+
}
|
| 263 |
+
|
| 264 |
+
const button = document.createElement("button");
|
| 265 |
+
button.textContent =
|
| 266 |
+
localStorage.getItem("read") === "true" ? "停止阅读" : "开始阅读";
|
| 267 |
+
button.style.position = "fixed";
|
| 268 |
+
button.style.bottom = "20px";
|
| 269 |
+
button.style.left = "20px";
|
| 270 |
+
button.style.zIndex = 1000;
|
| 271 |
+
button.style.backgroundColor = "#e0e0e0";
|
| 272 |
+
button.style.color = "#333";
|
| 273 |
+
button.style.border = "1px solid #aaa";
|
| 274 |
+
button.style.padding = "8px 16px";
|
| 275 |
+
button.style.borderRadius = "8px";
|
| 276 |
+
document.body.appendChild(button);
|
| 277 |
+
|
| 278 |
+
button.onclick = function () {
|
| 279 |
+
const currentlyReading = localStorage.getItem("read") === "true";
|
| 280 |
+
const newReadState = !currentlyReading;
|
| 281 |
+
localStorage.setItem("read", newReadState.toString());
|
| 282 |
+
button.textContent = newReadState ? "停止阅读" : "开始阅读";
|
| 283 |
+
if (newReadState) {
|
| 284 |
+
if (BASE_URL == "https://linux.do") {
|
| 285 |
+
const maxPostNumber = 600;
|
| 286 |
+
const randomPostNumber = Math.floor(Math.random() * maxPostNumber) + 1;
|
| 287 |
+
const newUrl = `https://linux.do/t/topic/13716/${randomPostNumber}`;
|
| 288 |
+
window.location.href = newUrl;
|
| 289 |
+
} else {
|
| 290 |
+
window.location.href = `${BASE_URL}/t/topic/1`;
|
| 291 |
+
}
|
| 292 |
+
}
|
| 293 |
+
};
|
| 294 |
+
|
| 295 |
+
const userInput = document.createElement("input");
|
| 296 |
+
userInput.type = "text";
|
| 297 |
+
userInput.placeholder = "输入要点赞的用户ID";
|
| 298 |
+
userInput.style.position = "fixed";
|
| 299 |
+
userInput.style.bottom = "90px";
|
| 300 |
+
userInput.style.left = "20px";
|
| 301 |
+
userInput.style.zIndex = "1000";
|
| 302 |
+
userInput.style.padding = "6px";
|
| 303 |
+
userInput.style.border = "1px solid #aaa";
|
| 304 |
+
userInput.style.borderRadius = "8px";
|
| 305 |
+
userInput.style.backgroundColor = "#e0e0e0";
|
| 306 |
+
userInput.style.width = "100px";
|
| 307 |
+
userInput.value = localStorage.getItem("specificUser") || "14790897";
|
| 308 |
+
|
| 309 |
+
document.body.appendChild(userInput);
|
| 310 |
+
|
| 311 |
+
const saveUserButton = document.createElement("button");
|
| 312 |
+
saveUserButton.textContent = "保存用户ID";
|
| 313 |
+
saveUserButton.style.position = "fixed";
|
| 314 |
+
saveUserButton.style.bottom = "60px";
|
| 315 |
+
saveUserButton.style.left = "20px";
|
| 316 |
+
saveUserButton.style.zIndex = "1000";
|
| 317 |
+
saveUserButton.style.backgroundColor = "#e0e0e0";
|
| 318 |
+
saveUserButton.style.color = "#333";
|
| 319 |
+
saveUserButton.style.border = "1px solid #aaa";
|
| 320 |
+
saveUserButton.style.padding = "8px 16px";
|
| 321 |
+
saveUserButton.style.borderRadius = "8px";
|
| 322 |
+
document.body.appendChild(saveUserButton);
|
| 323 |
+
|
| 324 |
+
saveUserButton.onclick = function () {
|
| 325 |
+
const newSpecificUser = userInput.value.trim();
|
| 326 |
+
if (newSpecificUser) {
|
| 327 |
+
localStorage.setItem("specificUser", newSpecificUser);
|
| 328 |
+
localStorage.removeItem("specificUserPostList");
|
| 329 |
+
localStorage.removeItem("lastOffset");
|
| 330 |
+
specificUser = newSpecificUser;
|
| 331 |
+
console.log(
|
| 332 |
+
`新的specificUser已保存: ${specificUser},specificUserPostList已重置`
|
| 333 |
+
);
|
| 334 |
+
}
|
| 335 |
+
};
|
| 336 |
+
|
| 337 |
+
const likeLimitInput = document.createElement("input");
|
| 338 |
+
likeLimitInput.type = "number";
|
| 339 |
+
likeLimitInput.placeholder = "输入点赞数量";
|
| 340 |
+
likeLimitInput.style.position = "fixed";
|
| 341 |
+
likeLimitInput.style.bottom = "180px";
|
| 342 |
+
likeLimitInput.style.left = "20px";
|
| 343 |
+
likeLimitInput.style.zIndex = "1000";
|
| 344 |
+
likeLimitInput.style.padding = "6px";
|
| 345 |
+
likeLimitInput.style.border = "1px solid #aaa";
|
| 346 |
+
likeLimitInput.style.borderRadius = "8px";
|
| 347 |
+
likeLimitInput.style.backgroundColor = "#e0e0e0";
|
| 348 |
+
likeLimitInput.style.width = "100px";
|
| 349 |
+
likeLimitInput.value = localStorage.getItem("likeLimit") || 200;
|
| 350 |
+
document.body.appendChild(likeLimitInput);
|
| 351 |
+
|
| 352 |
+
const saveLikeLimitButton = document.createElement("button");
|
| 353 |
+
saveLikeLimitButton.textContent = "保存点赞数量";
|
| 354 |
+
saveLikeLimitButton.style.position = "fixed";
|
| 355 |
+
saveLikeLimitButton.style.bottom = "140px";
|
| 356 |
+
saveLikeLimitButton.style.left = "20px";
|
| 357 |
+
saveLikeLimitButton.style.zIndex = "1000";
|
| 358 |
+
saveLikeLimitButton.style.backgroundColor = "#e0e0e0";
|
| 359 |
+
saveLikeLimitButton.style.color = "#333";
|
| 360 |
+
saveLikeLimitButton.style.border = "1px solid #aaa";
|
| 361 |
+
saveLikeLimitButton.style.padding = "8px 16px";
|
| 362 |
+
saveLikeLimitButton.style.borderRadius = "8px";
|
| 363 |
+
document.body.appendChild(saveLikeLimitButton);
|
| 364 |
+
|
| 365 |
+
saveLikeLimitButton.onclick = function () {
|
| 366 |
+
const newLikeLimit = parseInt(likeLimitInput.value.trim(), 10);
|
| 367 |
+
if (newLikeLimit && newLikeLimit > 0) {
|
| 368 |
+
localStorage.setItem("likeLimit", newLikeLimit);
|
| 369 |
+
likeLimit = newLikeLimit;
|
| 370 |
+
console.log(`新的likeLimit已保存: ${likeLimit}`);
|
| 371 |
+
}
|
| 372 |
+
};
|
| 373 |
+
|
| 374 |
+
// 增加清除数据的按钮
|
| 375 |
+
const clearDataButton = document.createElement("button");
|
| 376 |
+
clearDataButton.textContent = "清除所有数据";
|
| 377 |
+
clearDataButton.style.position = "fixed";
|
| 378 |
+
clearDataButton.style.bottom = "20px";
|
| 379 |
+
clearDataButton.style.left = "140px";
|
| 380 |
+
clearDataButton.style.zIndex = "1000";
|
| 381 |
+
clearDataButton.style.backgroundColor = "#ff6666"; // 红色背景,提示删除操作
|
| 382 |
+
clearDataButton.style.color = "#fff"; // 白色文本
|
| 383 |
+
clearDataButton.style.border = "1px solid #ff3333"; // 深红色边框
|
| 384 |
+
clearDataButton.style.padding = "8px 16px";
|
| 385 |
+
clearDataButton.style.borderRadius = "8px";
|
| 386 |
+
document.body.appendChild(clearDataButton);
|
| 387 |
+
|
| 388 |
+
clearDataButton.onclick = function () {
|
| 389 |
+
localStorage.removeItem("lastOffset");
|
| 390 |
+
localStorage.removeItem("clickCounter");
|
| 391 |
+
localStorage.removeItem("clickCounterTimestamp");
|
| 392 |
+
localStorage.removeItem("specificUserPostList");
|
| 393 |
+
console.log("所有数据已清除,除了 specificUser 和 specificUserPostList");
|
| 394 |
+
};
|
| 395 |
+
})();
|
index_passage_list_old_not_use.js
ADDED
|
@@ -0,0 +1,363 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// ==UserScript==
|
| 2 |
+
// @name Auto Read
|
| 3 |
+
// @namespace http://tampermonkey.net/
|
| 4 |
+
// @version 1.3.2
|
| 5 |
+
// @description 自动刷linuxdo文章
|
| 6 |
+
// @author liuweiqing
|
| 7 |
+
// @match https://meta.discourse.org/*
|
| 8 |
+
// @match https://linux.do/*
|
| 9 |
+
// @match https://meta.appinn.net/*
|
| 10 |
+
// @match https://community.openai.com/
|
| 11 |
+
// @grant none
|
| 12 |
+
// @license MIT
|
| 13 |
+
// @icon https://www.google.com/s2/favicons?domain=linux.do
|
| 14 |
+
// ==/UserScript==
|
| 15 |
+
|
| 16 |
+
(function () {
|
| 17 |
+
("use strict");
|
| 18 |
+
// 定义可能的基本URL
|
| 19 |
+
const possibleBaseURLs = [
|
| 20 |
+
"https://meta.discourse.org",
|
| 21 |
+
"https://linux.do",
|
| 22 |
+
"https://meta.appinn.net",
|
| 23 |
+
"https://community.openai.com",
|
| 24 |
+
];
|
| 25 |
+
|
| 26 |
+
// 获取当前页面的URL
|
| 27 |
+
const currentURL = window.location.href;
|
| 28 |
+
|
| 29 |
+
// 确定当前页面对应的BASE_URL
|
| 30 |
+
let BASE_URL = possibleBaseURLs.find((url) => currentURL.startsWith(url));
|
| 31 |
+
|
| 32 |
+
// 环境变量:阅读网址,如果没有找到匹配的URL,则默认为第一个
|
| 33 |
+
if (!BASE_URL) {
|
| 34 |
+
BASE_URL = possibleBaseURLs[0];
|
| 35 |
+
console.log("默认BASE_URL设置为: " + BASE_URL);
|
| 36 |
+
} else {
|
| 37 |
+
console.log("当前BASE_URL是: " + BASE_URL);
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
// 以下是脚本的其余部分
|
| 41 |
+
console.log("脚本正在运行在: " + BASE_URL);
|
| 42 |
+
//1.进入网页 https://linux.do/t/topic/数字(1,2,3,4)
|
| 43 |
+
//2.使滚轮均衡的往下移动模拟刷文章
|
| 44 |
+
// 检查是否是第一次运行脚本
|
| 45 |
+
function checkFirstRun() {
|
| 46 |
+
if (localStorage.getItem("isFirstRun") === null) {
|
| 47 |
+
// 是第一次运行,执行初始化操作
|
| 48 |
+
console.log("脚本第一次运行,执行初始化操作...");
|
| 49 |
+
updateInitialData();
|
| 50 |
+
|
| 51 |
+
// 设置 isFirstRun 标记为 false
|
| 52 |
+
localStorage.setItem("isFirstRun", "false");
|
| 53 |
+
} else {
|
| 54 |
+
// 非第一次运行
|
| 55 |
+
console.log("脚本非第一次运行");
|
| 56 |
+
}
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
// 更新初始数据的函数
|
| 60 |
+
function updateInitialData() {
|
| 61 |
+
localStorage.setItem("read", "false"); // 开始时自动滚动关闭
|
| 62 |
+
localStorage.setItem("autoLikeEnabled", "false"); //默认关闭自动点赞
|
| 63 |
+
console.log("执行了初始数据更新操作");
|
| 64 |
+
}
|
| 65 |
+
const delay = 2000; // 滚动检查的间隔(毫秒)
|
| 66 |
+
let scrollInterval = null;
|
| 67 |
+
let checkScrollTimeout = null;
|
| 68 |
+
let autoLikeInterval = null;
|
| 69 |
+
//在主页去寻找可以进入的话题,同时可以在文章页进行浏览
|
| 70 |
+
function scrollToBottomSlowly(
|
| 71 |
+
stopDistance = 9999999999,
|
| 72 |
+
callback = undefined,
|
| 73 |
+
distancePerStep = 20,
|
| 74 |
+
delayPerStep = 50
|
| 75 |
+
) {
|
| 76 |
+
if (scrollInterval !== null) {
|
| 77 |
+
clearInterval(scrollInterval);
|
| 78 |
+
}
|
| 79 |
+
scrollInterval = setInterval(() => {
|
| 80 |
+
if (
|
| 81 |
+
window.innerHeight + window.scrollY >=
|
| 82 |
+
document.body.offsetHeight - 100 ||
|
| 83 |
+
window.innerHeight + window.scrollY >= stopDistance
|
| 84 |
+
) {
|
| 85 |
+
clearInterval(scrollInterval);
|
| 86 |
+
scrollInterval = null;
|
| 87 |
+
if (typeof callback === "function") {
|
| 88 |
+
callback(); // 当滚动结束时调用回调函数
|
| 89 |
+
}
|
| 90 |
+
} else {
|
| 91 |
+
window.scrollBy(0, distancePerStep);
|
| 92 |
+
}
|
| 93 |
+
}, delayPerStep);
|
| 94 |
+
}
|
| 95 |
+
// 功能:跳转到下一个话题
|
| 96 |
+
|
| 97 |
+
function navigateToNextTopic() {
|
| 98 |
+
// 定义包含文章列表的数组
|
| 99 |
+
const urls = [
|
| 100 |
+
`${BASE_URL}/latest`,
|
| 101 |
+
`${BASE_URL}/top`,
|
| 102 |
+
`${BASE_URL}/latest?ascending=false&order=posts`,
|
| 103 |
+
// `${BASE_URL}/unread`, // 示例:如果你想将这个URL启用,只需去掉前面的注释
|
| 104 |
+
];
|
| 105 |
+
|
| 106 |
+
// 生成一个随机索引
|
| 107 |
+
const randomIndex = Math.floor(Math.random() * urls.length);
|
| 108 |
+
|
| 109 |
+
// 根据随机索引选择一个URL
|
| 110 |
+
const nextTopicURL = urls[randomIndex]; // 在跳转之前,标记即将跳转到下一个话题
|
| 111 |
+
localStorage.setItem("navigatingToNextTopic", "true");
|
| 112 |
+
// 尝试导航到下一个话题
|
| 113 |
+
window.location.href = nextTopicURL;
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
// 检查是否已滚动到底部(不断重复执行)
|
| 117 |
+
function checkScroll() {
|
| 118 |
+
if (localStorage.getItem("read")) {
|
| 119 |
+
if (
|
| 120 |
+
window.innerHeight + window.scrollY >=
|
| 121 |
+
document.body.offsetHeight - 100
|
| 122 |
+
) {
|
| 123 |
+
console.log("已滚动到底部");
|
| 124 |
+
navigateToNextTopic();
|
| 125 |
+
} else {
|
| 126 |
+
scrollToBottomSlowly();
|
| 127 |
+
if (checkScrollTimeout !== null) {
|
| 128 |
+
clearTimeout(checkScrollTimeout);
|
| 129 |
+
}
|
| 130 |
+
checkScrollTimeout = setTimeout(checkScroll, delay);
|
| 131 |
+
}
|
| 132 |
+
}
|
| 133 |
+
}
|
| 134 |
+
|
| 135 |
+
// 入口函数
|
| 136 |
+
window.addEventListener("load", () => {
|
| 137 |
+
checkFirstRun();
|
| 138 |
+
console.log(
|
| 139 |
+
"autoRead",
|
| 140 |
+
localStorage.getItem("read"),
|
| 141 |
+
"autoLikeEnabled",
|
| 142 |
+
localStorage.getItem("autoLikeEnabled")
|
| 143 |
+
);
|
| 144 |
+
if (localStorage.getItem("read") === "true") {
|
| 145 |
+
// 检查是否正在导航到下一个话题
|
| 146 |
+
if (localStorage.getItem("navigatingToNextTopic") === "true") {
|
| 147 |
+
console.log("正在导航到下一个话题");
|
| 148 |
+
// 等待一段时间或直到页面完全加载
|
| 149 |
+
// 页面加载完成后,移除标记
|
| 150 |
+
localStorage.removeItem("navigatingToNextTopic");
|
| 151 |
+
// 使用setTimeout延迟执行
|
| 152 |
+
setTimeout(() => {
|
| 153 |
+
// 先随机滚动一段距离然后再查找链接
|
| 154 |
+
scrollToBottomSlowly(
|
| 155 |
+
Math.random() * document.body.offsetHeight * 3,
|
| 156 |
+
searchLinkClick,
|
| 157 |
+
20,
|
| 158 |
+
20
|
| 159 |
+
);
|
| 160 |
+
}, 2000); // 延迟2000毫秒(即2秒)
|
| 161 |
+
} else {
|
| 162 |
+
console.log("执行正常的滚动和检查逻辑");
|
| 163 |
+
// 执行正常的滚动和检查逻辑
|
| 164 |
+
checkScroll();
|
| 165 |
+
if (isAutoLikeEnabled()) {
|
| 166 |
+
//自动点赞
|
| 167 |
+
autoLike();
|
| 168 |
+
}
|
| 169 |
+
}
|
| 170 |
+
}
|
| 171 |
+
});
|
| 172 |
+
// 创建一个控制滚动的按钮
|
| 173 |
+
function searchLinkClick() {
|
| 174 |
+
// 在新页面加载后执行检查
|
| 175 |
+
// 使用CSS属性选择器寻找href属性符合特定格式的<a>标签
|
| 176 |
+
const links = document.querySelectorAll('a[href^="/t/"]');
|
| 177 |
+
|
| 178 |
+
// 筛选出未阅读的链接
|
| 179 |
+
const unreadLinks = Array.from(links).filter((link) => {
|
| 180 |
+
// 向上遍历DOM树,查找包含'visited'类的父级元素,最多查找三次
|
| 181 |
+
let parent = link.parentElement;
|
| 182 |
+
let times = 0; // 查找次数计数器
|
| 183 |
+
while (parent && times < 3) {
|
| 184 |
+
if (parent.classList.contains("visited")) {
|
| 185 |
+
// 如果找到包含'visited'类的父级元素,中断循环
|
| 186 |
+
return false; // 父级元素包含'visited'类,排除这个链接
|
| 187 |
+
}
|
| 188 |
+
parent = parent.parentElement; // 继续向上查找
|
| 189 |
+
times++; // 增加查找次数
|
| 190 |
+
}
|
| 191 |
+
|
| 192 |
+
// 如果链接未被读过,且在向上查找三次内,其父级元素中没有包含'visited'类,则保留这个链接
|
| 193 |
+
return true;
|
| 194 |
+
});
|
| 195 |
+
|
| 196 |
+
// 如果找到了这样的链接
|
| 197 |
+
if (unreadLinks.length > 0) {
|
| 198 |
+
// 从所有匹配的链接中随机选择一个
|
| 199 |
+
const randomIndex = Math.floor(Math.random() * unreadLinks.length);
|
| 200 |
+
const link = unreadLinks[randomIndex];
|
| 201 |
+
// 打印找到的链接(可选)
|
| 202 |
+
console.log("Found link:", link.href);
|
| 203 |
+
// 导航到该链接
|
| 204 |
+
window.location.href = link.href;
|
| 205 |
+
} else {
|
| 206 |
+
// 如果没有找到符合条件的链接,打印消息(可选)
|
| 207 |
+
console.log("No link with the specified format was found.");
|
| 208 |
+
scrollToBottomSlowly(
|
| 209 |
+
Math.random() * document.body.offsetHeight * 3,
|
| 210 |
+
searchLinkClick
|
| 211 |
+
);
|
| 212 |
+
}
|
| 213 |
+
}
|
| 214 |
+
// 获取当前时间戳
|
| 215 |
+
const currentTime = Date.now();
|
| 216 |
+
// 获取存储的时间戳
|
| 217 |
+
const defaultTimestamp = new Date("1999-01-01T00:00:00Z").getTime(); //默认值为1999年
|
| 218 |
+
const storedTime = parseInt(
|
| 219 |
+
localStorage.getItem("clickCounterTimestamp") ||
|
| 220 |
+
defaultTimestamp.toString(),
|
| 221 |
+
10
|
| 222 |
+
);
|
| 223 |
+
|
| 224 |
+
// 获取当前的点击计数,如果不存在则初始化为0
|
| 225 |
+
let clickCounter = parseInt(localStorage.getItem("clickCounter") || "0", 10);
|
| 226 |
+
// 检查是否超过24小时(24小时 = 24 * 60 * 60 * 1000 毫秒)
|
| 227 |
+
if (currentTime - storedTime > 24 * 60 * 60 * 1000) {
|
| 228 |
+
// 超过24小时,清空点击计数器并更新时间戳
|
| 229 |
+
clickCounter = 0;
|
| 230 |
+
localStorage.setItem("clickCounter", "0");
|
| 231 |
+
localStorage.setItem("clickCounterTimestamp", currentTime.toString());
|
| 232 |
+
}
|
| 233 |
+
|
| 234 |
+
console.log(`Initial clickCounter: ${clickCounter}`);
|
| 235 |
+
function triggerClick(button) {
|
| 236 |
+
const event = new MouseEvent("click", {
|
| 237 |
+
bubbles: true,
|
| 238 |
+
cancelable: true,
|
| 239 |
+
view: window,
|
| 240 |
+
});
|
| 241 |
+
button.dispatchEvent(event);
|
| 242 |
+
}
|
| 243 |
+
|
| 244 |
+
function autoLike() {
|
| 245 |
+
console.log(`Initial clickCounter: ${clickCounter}`);
|
| 246 |
+
// 寻找所有的discourse-reactions-reaction-button
|
| 247 |
+
const buttons = document.querySelectorAll(
|
| 248 |
+
".discourse-reactions-reaction-button"
|
| 249 |
+
);
|
| 250 |
+
if (buttons.length === 0) {
|
| 251 |
+
console.error(
|
| 252 |
+
"No buttons found with the selector '.discourse-reactions-reaction-button'"
|
| 253 |
+
);
|
| 254 |
+
return;
|
| 255 |
+
}
|
| 256 |
+
console.log(`Found ${buttons.length} buttons.`); // 调试信息
|
| 257 |
+
|
| 258 |
+
// 逐个点击找到的按钮
|
| 259 |
+
buttons.forEach((button, index) => {
|
| 260 |
+
if (
|
| 261 |
+
(button.title !== "点赞此帖子" && button.title !== "Like this post") ||
|
| 262 |
+
clickCounter >= 50
|
| 263 |
+
) {
|
| 264 |
+
return;
|
| 265 |
+
}
|
| 266 |
+
|
| 267 |
+
// 使用setTimeout来错开每次点击的时间,避免同时触发点击
|
| 268 |
+
autoLikeInterval = setTimeout(() => {
|
| 269 |
+
// 模拟点击
|
| 270 |
+
triggerClick(button); // 使用自定义的触发点击方法
|
| 271 |
+
console.log(`Clicked like button ${index + 1}`);
|
| 272 |
+
clickCounter++; // 更新点击计数器
|
| 273 |
+
// 将新的点击计数存储到localStorage
|
| 274 |
+
localStorage.setItem("clickCounter", clickCounter.toString());
|
| 275 |
+
// 如果点击次数达到50次,则设置点赞变量为false
|
| 276 |
+
if (clickCounter === 50) {
|
| 277 |
+
console.log("Reached 50 likes, setting the like variable to false.");
|
| 278 |
+
localStorage.setItem("autoLikeEnabled", "false"); // 使用localStorage存储点赞变量状态
|
| 279 |
+
} else {
|
| 280 |
+
console.log("clickCounter:", clickCounter);
|
| 281 |
+
}
|
| 282 |
+
}, index * 3000); // 这里的3000毫秒��两次点击之间的间隔,可以根据需要调整
|
| 283 |
+
});
|
| 284 |
+
}
|
| 285 |
+
const button = document.createElement("button");
|
| 286 |
+
// 初始化按钮文本基于当前的阅读状态
|
| 287 |
+
button.textContent =
|
| 288 |
+
localStorage.getItem("read") === "true" ? "停止阅读" : "开始阅读";
|
| 289 |
+
button.style.position = "fixed";
|
| 290 |
+
button.style.bottom = "10px"; // 之前是 top
|
| 291 |
+
button.style.left = "10px"; // 之前是 right
|
| 292 |
+
button.style.zIndex = 1000;
|
| 293 |
+
button.style.backgroundColor = "#f0f0f0"; // 浅灰色背景
|
| 294 |
+
button.style.color = "#000"; // 黑色文本
|
| 295 |
+
button.style.border = "1px solid #ddd"; // 浅灰色边框
|
| 296 |
+
button.style.padding = "5px 10px"; // 内边距
|
| 297 |
+
button.style.borderRadius = "5px"; // 圆角
|
| 298 |
+
document.body.appendChild(button);
|
| 299 |
+
|
| 300 |
+
button.onclick = function () {
|
| 301 |
+
const currentlyReading = localStorage.getItem("read") === "true";
|
| 302 |
+
const newReadState = !currentlyReading;
|
| 303 |
+
localStorage.setItem("read", newReadState.toString());
|
| 304 |
+
button.textContent = newReadState ? "停止阅读" : "开始阅读";
|
| 305 |
+
if (!newReadState) {
|
| 306 |
+
if (scrollInterval !== null) {
|
| 307 |
+
clearInterval(scrollInterval);
|
| 308 |
+
scrollInterval = null;
|
| 309 |
+
}
|
| 310 |
+
if (checkScrollTimeout !== null) {
|
| 311 |
+
clearTimeout(checkScrollTimeout);
|
| 312 |
+
checkScrollTimeout = null;
|
| 313 |
+
}
|
| 314 |
+
localStorage.removeItem("navigatingToNextTopic");
|
| 315 |
+
} else {
|
| 316 |
+
// 如果是Linuxdo,就导航到我的帖子
|
| 317 |
+
if (BASE_URL == "https://linux.do") {
|
| 318 |
+
window.location.href = "https://linux.do/t/topic/13716/340";
|
| 319 |
+
} else if (BASE_URL == "https://meta.appinn.net") {
|
| 320 |
+
window.location.href = "https://meta.appinn.net/t/topic/52006";
|
| 321 |
+
} else {
|
| 322 |
+
window.location.href = `${BASE_URL}/t/topic/1`;
|
| 323 |
+
}
|
| 324 |
+
checkScroll();
|
| 325 |
+
}
|
| 326 |
+
};
|
| 327 |
+
|
| 328 |
+
//自动点赞按钮
|
| 329 |
+
// 在页面上添加一个控制自动点赞的按钮
|
| 330 |
+
const toggleAutoLikeButton = document.createElement("button");
|
| 331 |
+
toggleAutoLikeButton.textContent = isAutoLikeEnabled()
|
| 332 |
+
? "禁用自动点赞"
|
| 333 |
+
: "启用自动点赞";
|
| 334 |
+
toggleAutoLikeButton.style.position = "fixed";
|
| 335 |
+
toggleAutoLikeButton.style.bottom = "50px"; // 之前是 top,且与另一个按钮错开位置
|
| 336 |
+
toggleAutoLikeButton.style.left = "10px"; // 之前是 right
|
| 337 |
+
toggleAutoLikeButton.style.zIndex = "1000";
|
| 338 |
+
toggleAutoLikeButton.style.backgroundColor = "#f0f0f0"; // 浅灰色背景
|
| 339 |
+
toggleAutoLikeButton.style.color = "#000"; // 黑色文本
|
| 340 |
+
toggleAutoLikeButton.style.border = "1px solid #ddd"; // 浅灰色边框
|
| 341 |
+
toggleAutoLikeButton.style.padding = "5px 10px"; // 内边距
|
| 342 |
+
toggleAutoLikeButton.style.borderRadius = "5px"; // 圆角
|
| 343 |
+
document.body.appendChild(toggleAutoLikeButton);
|
| 344 |
+
|
| 345 |
+
// 为按钮添加点击事件处理函数
|
| 346 |
+
toggleAutoLikeButton.addEventListener("click", () => {
|
| 347 |
+
const isEnabled = !isAutoLikeEnabled();
|
| 348 |
+
setAutoLikeEnabled(isEnabled);
|
| 349 |
+
toggleAutoLikeButton.textContent = isEnabled
|
| 350 |
+
? "禁用自动点赞"
|
| 351 |
+
: "启用自动点赞";
|
| 352 |
+
});
|
| 353 |
+
// 判断是否启用自动点赞
|
| 354 |
+
function isAutoLikeEnabled() {
|
| 355 |
+
// 从localStorage获取autoLikeEnabled的值,如果未设置,默认为"true"
|
| 356 |
+
return localStorage.getItem("autoLikeEnabled") !== "false";
|
| 357 |
+
}
|
| 358 |
+
|
| 359 |
+
// 设置自动点赞的启用状态
|
| 360 |
+
function setAutoLikeEnabled(enabled) {
|
| 361 |
+
localStorage.setItem("autoLikeEnabled", enabled ? "true" : "false");
|
| 362 |
+
}
|
| 363 |
+
})();
|
invite_codecow.js
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// ==UserScript==
|
| 2 |
+
// @name 自动上传邀请码
|
| 3 |
+
// @namespace https://linux.do
|
| 4 |
+
// @version 0.0.8
|
| 5 |
+
// @description 直接上传邀请码!
|
| 6 |
+
// @author codecow
|
| 7 |
+
// @match https://linux.do/*
|
| 8 |
+
// @icon https://www.google.com/s2/favicons?sz=64&domain=linux.do
|
| 9 |
+
// @require https://cdn.bootcdn.net/ajax/libs/jquery/1.9.1/jquery.min.js
|
| 10 |
+
// @grant none
|
| 11 |
+
// @license MIT
|
| 12 |
+
// @run-at document-end
|
| 13 |
+
// ==/UserScript==
|
| 14 |
+
|
| 15 |
+
(function () {
|
| 16 |
+
"use strict";
|
| 17 |
+
|
| 18 |
+
const BASE_URL = "https://linux.do";
|
| 19 |
+
const UPLOAD_URL = "https://linuxdo-invites.speedcow.top/upload";
|
| 20 |
+
let username = "";
|
| 21 |
+
let csrfToken = document
|
| 22 |
+
.querySelector('meta[name="csrf-token"]')
|
| 23 |
+
.getAttribute("content");
|
| 24 |
+
const CHECK_INTERVAL_MS = 1000; // 检查间隔
|
| 25 |
+
|
| 26 |
+
let headers = {
|
| 27 |
+
accept: "*/*",
|
| 28 |
+
"accept-language": "zh-CN,zh;q=0.9,en;q=0.8",
|
| 29 |
+
"cache-control": "no-cache",
|
| 30 |
+
"discourse-logged-in": "true",
|
| 31 |
+
"discourse-present": "true",
|
| 32 |
+
pragma: "no-cache",
|
| 33 |
+
"x-csrf-token": csrfToken,
|
| 34 |
+
"x-requested-with": "XMLHttpRequest",
|
| 35 |
+
"sec-ch-ua":
|
| 36 |
+
'"Google Chrome";v="123", "Not:A-Brand";v="8", "Chromium";v="123"',
|
| 37 |
+
"Discourse-Logged-In": "true",
|
| 38 |
+
"sec-ch-ua-arch": '"x86"',
|
| 39 |
+
"sec-ch-ua-platform-version": '"10.0.0"',
|
| 40 |
+
"X-Requested-With": "XMLHttpRequest",
|
| 41 |
+
"sec-ch-ua-full-version-list":
|
| 42 |
+
'"Google Chrome";v="123.0.6312.60", "Not:A-Brand";v="8.0.0.0", "Chromium";v="123.0.6312.60"',
|
| 43 |
+
"sec-ch-ua-bitness": '"64"',
|
| 44 |
+
"sec-ch-ua-model": "",
|
| 45 |
+
"sec-ch-ua-platform": '"Windows"',
|
| 46 |
+
"Discourse-Present": "true",
|
| 47 |
+
"sec-ch-ua-mobile": "?0",
|
| 48 |
+
"User-Agent":
|
| 49 |
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36",
|
| 50 |
+
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
|
| 51 |
+
"sec-ch-ua-full-version": '"123.0.6312.60"',
|
| 52 |
+
Referer: `${BASE_URL}/u/${username}/invited/pending`,
|
| 53 |
+
};
|
| 54 |
+
let upload_headers = {
|
| 55 |
+
"Content-Type": "application/json",
|
| 56 |
+
};
|
| 57 |
+
// 循环flag
|
| 58 |
+
let flag = true;
|
| 59 |
+
// 邀请链接
|
| 60 |
+
let inviteLinks = [];
|
| 61 |
+
|
| 62 |
+
// 获取过期时间
|
| 63 |
+
async function getExpiresAt() {
|
| 64 |
+
let date = new Date();
|
| 65 |
+
date.setDate(date.getDate() + 1);
|
| 66 |
+
// 格式化日期和时间
|
| 67 |
+
const year = date.getFullYear();
|
| 68 |
+
const month = (date.getMonth() + 1).toString().padStart(2, "0"); // 月份是从0开始的
|
| 69 |
+
const day = date.getDate().toString().padStart(2, "0");
|
| 70 |
+
const hours = date.getHours().toString().padStart(2, "0");
|
| 71 |
+
const minutes = date.getMinutes().toString().padStart(2, "0");
|
| 72 |
+
|
| 73 |
+
// 时区处理,这里简化处理为+08:00,具体时区可能需要动态获取或计算
|
| 74 |
+
const timezone = "+08:00";
|
| 75 |
+
|
| 76 |
+
// 构建expires_at参数值
|
| 77 |
+
return `${year}-${month}-${day} ${hours}:${minutes}${timezone}`;
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
// 获取新的邀请链接
|
| 81 |
+
async function fetchInvite() {
|
| 82 |
+
try {
|
| 83 |
+
let response = await fetch(`${BASE_URL}/invites`, {
|
| 84 |
+
headers: headers,
|
| 85 |
+
method: "POST",
|
| 86 |
+
mode: "cors",
|
| 87 |
+
credentials: "include",
|
| 88 |
+
body: `max_redemptions_allowed=2&expires_at=${encodeURIComponent(
|
| 89 |
+
await getExpiresAt()
|
| 90 |
+
)}`,
|
| 91 |
+
});
|
| 92 |
+
|
| 93 |
+
if (!response.ok) {
|
| 94 |
+
throw new Error(`HTTP error! status: ${response.status}`);
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
let data = await response.json();
|
| 98 |
+
if (data.error_type === "rate_limit") {
|
| 99 |
+
console.log("Rate limit reached, stopping.");
|
| 100 |
+
flag = false;
|
| 101 |
+
} else {
|
| 102 |
+
inviteLinks.push(data.link);
|
| 103 |
+
await uploadInvites(inviteLinks);
|
| 104 |
+
}
|
| 105 |
+
} catch (error) {
|
| 106 |
+
console.log("Error:", error);
|
| 107 |
+
flag = false;
|
| 108 |
+
}
|
| 109 |
+
}
|
| 110 |
+
|
| 111 |
+
// 获取已有的邀请链接列表
|
| 112 |
+
async function fetchInvites() {
|
| 113 |
+
try {
|
| 114 |
+
let response = await fetch(`${BASE_URL}/u/${username}/invited/pending`, {
|
| 115 |
+
headers: headers,
|
| 116 |
+
method: "GET",
|
| 117 |
+
mode: "cors",
|
| 118 |
+
credentials: "include",
|
| 119 |
+
});
|
| 120 |
+
if (!response.ok) {
|
| 121 |
+
throw new Error(`HTTP error! status: ${response.status}`);
|
| 122 |
+
}
|
| 123 |
+
|
| 124 |
+
let data = await response.json();
|
| 125 |
+
for (let invite of data.invites) {
|
| 126 |
+
inviteLinks.push(invite.link);
|
| 127 |
+
}
|
| 128 |
+
await uploadInvites(inviteLinks);
|
| 129 |
+
while (flag) {
|
| 130 |
+
await fetchInvite();
|
| 131 |
+
if (!flag) {
|
| 132 |
+
break;
|
| 133 |
+
}
|
| 134 |
+
}
|
| 135 |
+
} catch (error) {
|
| 136 |
+
console.log("Error:", error);
|
| 137 |
+
flag = false;
|
| 138 |
+
}
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
// 上传邀请链接
|
| 142 |
+
async function uploadInvites(inviteLinks) {
|
| 143 |
+
try {
|
| 144 |
+
const inviteLinksObject = inviteLinks.reduce((obj, link) => {
|
| 145 |
+
obj[link] = link;
|
| 146 |
+
return obj;
|
| 147 |
+
}, {});
|
| 148 |
+
await fetch(`${UPLOAD_URL}?username=${username}`, {
|
| 149 |
+
headers: upload_headers,
|
| 150 |
+
method: "POST",
|
| 151 |
+
mode: "no-cors",
|
| 152 |
+
body: JSON.stringify(inviteLinksObject),
|
| 153 |
+
});
|
| 154 |
+
} catch (error) {
|
| 155 |
+
console.log("Error:", error);
|
| 156 |
+
flag = false;
|
| 157 |
+
}
|
| 158 |
+
}
|
| 159 |
+
|
| 160 |
+
// 获取用户名称
|
| 161 |
+
const checkInterval = setInterval(function () {
|
| 162 |
+
// 查找id为current-user的li元素
|
| 163 |
+
const currentUserLi = document.querySelector("#current-user");
|
| 164 |
+
// 如果找到了元素
|
| 165 |
+
if (currentUserLi) {
|
| 166 |
+
// 查找该元素下的button
|
| 167 |
+
const button = currentUserLi.querySelector("button");
|
| 168 |
+
|
| 169 |
+
// 如果找到了button元素
|
| 170 |
+
if (button) {
|
| 171 |
+
// 获取button的href属性值
|
| 172 |
+
const href = button.getAttribute("href");
|
| 173 |
+
username = href.replace("/u/", "");
|
| 174 |
+
// username = document.getElementsByClassName("header-dropdown-toggle current-user")[0].querySelector("button").getAttribute("href").replace("/u/", "");
|
| 175 |
+
clearInterval(checkInterval); // 停止检查
|
| 176 |
+
}
|
| 177 |
+
}
|
| 178 |
+
if (username !== "") {
|
| 179 |
+
fetchInvites().then((r) => {
|
| 180 |
+
console.log("done");
|
| 181 |
+
});
|
| 182 |
+
}
|
| 183 |
+
}, CHECK_INTERVAL_MS); // 每隔1秒检查一次
|
| 184 |
+
})();
|
package-lock.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
package.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"type": "module",
|
| 3 |
+
"dependencies": {
|
| 4 |
+
"dotenv": "^16.4.5",
|
| 5 |
+
"express": "^4.21.2",
|
| 6 |
+
"node-telegram-bot-api": "^0.66.0",
|
| 7 |
+
"puppeteer": "^22.15.0",
|
| 8 |
+
"puppeteer-extra": "^3.3.6",
|
| 9 |
+
"puppeteer-extra-plugin-stealth": "^2.11.2",
|
| 10 |
+
"puppeteer-real-browser": "^1.2.31",
|
| 11 |
+
"xvfb": "^0.4.0"
|
| 12 |
+
},
|
| 13 |
+
"version": "1.9.0"
|
| 14 |
+
}
|
pnpm-lock.yaml
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
pteer.js
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import fs from "fs";
|
| 2 |
+
import path from "path";
|
| 3 |
+
import puppeteer from "puppeteer-extra";
|
| 4 |
+
import StealthPlugin from "puppeteer-extra-plugin-stealth";
|
| 5 |
+
import dotenv from "dotenv";
|
| 6 |
+
import { fileURLToPath } from "url";
|
| 7 |
+
import { dirname, join } from "path";
|
| 8 |
+
|
| 9 |
+
puppeteer.use(StealthPlugin());
|
| 10 |
+
|
| 11 |
+
// Load the default .env file
|
| 12 |
+
dotenv.config();
|
| 13 |
+
if (fs.existsSync(".env.local")) {
|
| 14 |
+
console.log("Using .env.local file to supply config environment variables");
|
| 15 |
+
const envConfig = dotenv.parse(fs.readFileSync(".env.local"));
|
| 16 |
+
for (const k in envConfig) {
|
| 17 |
+
process.env[k] = envConfig[k];
|
| 18 |
+
}
|
| 19 |
+
}
|
| 20 |
+
// 从环境变量解析用户名和密码
|
| 21 |
+
const usernames = process.env.USERNAMES.split(",");
|
| 22 |
+
const passwords = process.env.PASSWORDS.split(",");
|
| 23 |
+
const loginUrl = process.env.WEBSITE;
|
| 24 |
+
// 每个浏览器实例之间的延迟时间(毫秒)
|
| 25 |
+
const delayBetweenInstances = 10000;
|
| 26 |
+
//随机等待时间
|
| 27 |
+
function delayClick(time) {
|
| 28 |
+
return new Promise(function (resolve) {
|
| 29 |
+
setTimeout(resolve, time);
|
| 30 |
+
});
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
(async () => {
|
| 34 |
+
try {
|
| 35 |
+
if (usernames.length !== passwords.length) {
|
| 36 |
+
console.log(usernames.length, usernames, passwords.length, passwords);
|
| 37 |
+
console.log("用户名和密码的数量不匹配!");
|
| 38 |
+
return;
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
// 并发启动浏览器实例进行登录
|
| 42 |
+
const loginPromises = usernames.map((username, index) => {
|
| 43 |
+
const password = passwords[index];
|
| 44 |
+
const delay = index * delayBetweenInstances;
|
| 45 |
+
return new Promise((resolve, reject) => {
|
| 46 |
+
//其实直接使用await就可以了
|
| 47 |
+
setTimeout(() => {
|
| 48 |
+
launchBrowserForUser(username, password).then(resolve).catch(reject);
|
| 49 |
+
}, delay);
|
| 50 |
+
});
|
| 51 |
+
});
|
| 52 |
+
|
| 53 |
+
// 等待所有登录操作完成
|
| 54 |
+
await Promise.all(loginPromises);
|
| 55 |
+
} catch (error) {
|
| 56 |
+
// 错误处理逻辑
|
| 57 |
+
console.error("发生错误:", error);
|
| 58 |
+
}
|
| 59 |
+
})();
|
| 60 |
+
async function launchBrowserForUser(username, password) {
|
| 61 |
+
try {
|
| 62 |
+
const browser = await puppeteer.launch({
|
| 63 |
+
headless: process.env.ENVIRONMENT !== "dev", // 当ENVIRONMENT不是'dev'时启用无头模式
|
| 64 |
+
args: [
|
| 65 |
+
"--no-sandbox",
|
| 66 |
+
"--disable-setuid-sandbox",
|
| 67 |
+
"--disable-infobars",
|
| 68 |
+
"--window-position=0,0",
|
| 69 |
+
"--ignore-certifcate-errors",
|
| 70 |
+
"--ignore-certifcate-errors-spki-list",
|
| 71 |
+
"--disable-web-security",
|
| 72 |
+
"--disable-features=IsolateOrigins,site-per-process",
|
| 73 |
+
"--allow-running-insecure-content",
|
| 74 |
+
"--disable-blink-features=AutomationControlled",
|
| 75 |
+
"--no-sandbox",
|
| 76 |
+
"--mute-audio",
|
| 77 |
+
"--no-zygote",
|
| 78 |
+
"--no-xshm",
|
| 79 |
+
"--window-size=1920,1080",
|
| 80 |
+
"--no-first-run",
|
| 81 |
+
"--no-default-browser-check",
|
| 82 |
+
"--disable-dev-shm-usage",
|
| 83 |
+
"--disable-gpu",
|
| 84 |
+
"--enable-webgl",
|
| 85 |
+
"--ignore-certificate-errors",
|
| 86 |
+
"--lang=en-US,en;q=0.9",
|
| 87 |
+
"--password-store=basic",
|
| 88 |
+
"--disable-gpu-sandbox",
|
| 89 |
+
"--disable-software-rasterizer",
|
| 90 |
+
"--disable-background-timer-throttling",
|
| 91 |
+
"--disable-backgrounding-occluded-windows",
|
| 92 |
+
"--disable-renderer-backgrounding",
|
| 93 |
+
"--disable-infobars",
|
| 94 |
+
"--disable-breakpad",
|
| 95 |
+
"--disable-canvas-aa",
|
| 96 |
+
"--disable-2d-canvas-clip-aa",
|
| 97 |
+
"--disable-gl-drawing-for-tests",
|
| 98 |
+
"--enable-low-end-device-mode",
|
| 99 |
+
], //linux需要
|
| 100 |
+
defaultViewport: {
|
| 101 |
+
width: 1280,
|
| 102 |
+
height: 800,
|
| 103 |
+
userAgent:
|
| 104 |
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36",
|
| 105 |
+
},
|
| 106 |
+
});
|
| 107 |
+
const page = await browser.newPage();
|
| 108 |
+
// 设置额外的 headers
|
| 109 |
+
await page.setExtraHTTPHeaders({
|
| 110 |
+
"accept-language": "en-US,en;q=0.9",
|
| 111 |
+
});
|
| 112 |
+
|
| 113 |
+
// 调用封装的反检测函数
|
| 114 |
+
await bypassDetection(page);
|
| 115 |
+
// 验证 `navigator.webdriver` 属性是否为 undefined
|
| 116 |
+
const isWebDriverUndefined = await page.evaluate(() => {
|
| 117 |
+
return `${navigator.webdriver}`;
|
| 118 |
+
});
|
| 119 |
+
|
| 120 |
+
console.log("navigator.webdriver is :", isWebDriverUndefined); // 输出应为 true
|
| 121 |
+
page.on("pageerror", (error) => {
|
| 122 |
+
console.error(`Page error: ${error.message}`);
|
| 123 |
+
});
|
| 124 |
+
page.on("error", async (error) => {
|
| 125 |
+
console.error(`Error: ${error.message}`);
|
| 126 |
+
// 检查是否是 localStorage 的访问权限错误
|
| 127 |
+
if (
|
| 128 |
+
error.message.includes(
|
| 129 |
+
"Failed to read the 'localStorage' property from 'Window'"
|
| 130 |
+
)
|
| 131 |
+
) {
|
| 132 |
+
console.log("Trying to refresh the page to resolve the issue...");
|
| 133 |
+
await page.reload(); // 刷新页面
|
| 134 |
+
// 重新尝试你的操作...
|
| 135 |
+
}
|
| 136 |
+
});
|
| 137 |
+
page.on("console", async (msg) => {
|
| 138 |
+
console.log("PAGE LOG:", msg.text());
|
| 139 |
+
// 使用一个标志变量来检测是否已经刷新过页面
|
| 140 |
+
if (
|
| 141 |
+
!page._isReloaded &&
|
| 142 |
+
msg.text().includes("the server responded with a status of 429")
|
| 143 |
+
) {
|
| 144 |
+
// 设置标志变量为 true,表示即将刷新页面
|
| 145 |
+
page._isReloaded = true;
|
| 146 |
+
//由于油候脚本它这个时候可能会导航到新的网页,会导致直接执行代码报错,所以使用这个来在每个新网页加载之前来执行
|
| 147 |
+
await page.evaluateOnNewDocument(() => {
|
| 148 |
+
localStorage.setItem("autoLikeEnabled", "false");
|
| 149 |
+
});
|
| 150 |
+
// 等待一段时间,比如 3 秒
|
| 151 |
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
| 152 |
+
console.log("Retrying now...");
|
| 153 |
+
// 尝试刷新页面
|
| 154 |
+
await page.reload();
|
| 155 |
+
}
|
| 156 |
+
});
|
| 157 |
+
|
| 158 |
+
//登录操作
|
| 159 |
+
await page.goto(loginUrl, { waitUntil: "networkidle0" });
|
| 160 |
+
console.log("登录操作");
|
| 161 |
+
// 使用XPath查询找到包含"登录"或"login"文本的按钮
|
| 162 |
+
await page.evaluate(() => {
|
| 163 |
+
let loginButton = Array.from(document.querySelectorAll("button")).find(
|
| 164 |
+
(button) =>
|
| 165 |
+
button.textContent.includes("登录") ||
|
| 166 |
+
button.textContent.includes("login")
|
| 167 |
+
);
|
| 168 |
+
// 如果没有找到,尝试根据类名查找
|
| 169 |
+
if (!loginButton) {
|
| 170 |
+
loginButton = document.querySelector(
|
| 171 |
+
".widget-button.btn.btn-primary.btn-small.login-button.btn-icon-text"
|
| 172 |
+
);
|
| 173 |
+
}
|
| 174 |
+
console.log(loginButton);
|
| 175 |
+
if (loginButton) {
|
| 176 |
+
loginButton.click();
|
| 177 |
+
console.log("Login button clicked.");
|
| 178 |
+
} else {
|
| 179 |
+
console.log("Login button not found.");
|
| 180 |
+
}
|
| 181 |
+
});
|
| 182 |
+
|
| 183 |
+
await login(page, username, password);
|
| 184 |
+
// 查找具有类名 "avatar" 的 img 元素验证登录是否成功
|
| 185 |
+
const avatarImg = await page.$("img.avatar");
|
| 186 |
+
|
| 187 |
+
if (avatarImg) {
|
| 188 |
+
console.log("找到avatarImg,登录成功");
|
| 189 |
+
// 可以继续对 avatarImg 进行操作,比如获取其属性等
|
| 190 |
+
} else {
|
| 191 |
+
console.log("未找到avatarImg,登录失败");
|
| 192 |
+
}
|
| 193 |
+
|
| 194 |
+
//真正执行阅读脚本
|
| 195 |
+
// 读取外部脚本文件的内容
|
| 196 |
+
const externalScriptPath = path.join(
|
| 197 |
+
dirname(fileURLToPath(import.meta.url)),
|
| 198 |
+
"external.js"
|
| 199 |
+
);
|
| 200 |
+
const externalScript = fs.readFileSync(externalScriptPath, "utf8");
|
| 201 |
+
|
| 202 |
+
// 在每个新的文档加载时执行外部脚本
|
| 203 |
+
await page.evaluateOnNewDocument((...args) => {
|
| 204 |
+
const [scriptToEval] = args;
|
| 205 |
+
eval(scriptToEval);
|
| 206 |
+
}, externalScript);
|
| 207 |
+
// 添加一个监听器来监听每次页面加载完成的事件
|
| 208 |
+
page.on("load", async () => {
|
| 209 |
+
// await page.evaluate(externalScript); //因为这个是在页面加载好之后执行的,而脚本是在页面加载好时刻来判断是否要执行,由于已经加载好了,脚本就不会起作用
|
| 210 |
+
});
|
| 211 |
+
await page.goto("https://linux.do/t/topic/13716/190");
|
| 212 |
+
} catch (err) {
|
| 213 |
+
console.log(err);
|
| 214 |
+
}
|
| 215 |
+
}
|
| 216 |
+
async function login(page, username, password) {
|
| 217 |
+
// 等待用户名输入框加载
|
| 218 |
+
await page.waitForSelector("#login-account-name");
|
| 219 |
+
// 模拟人类在找到输入框后的短暂停顿
|
| 220 |
+
await delayClick(500); // 延迟500毫秒
|
| 221 |
+
// 清空输入框并输入用户名
|
| 222 |
+
await page.click("#login-account-name", { clickCount: 3 });
|
| 223 |
+
await page.type("#login-account-name", username, {
|
| 224 |
+
delay: 100,
|
| 225 |
+
}); // 输入时在每个按键之间添加额外的延迟
|
| 226 |
+
|
| 227 |
+
// 等待密码输入框加载
|
| 228 |
+
await page.waitForSelector("#login-account-password");
|
| 229 |
+
// 模拟人类在输入用户名后的短暂停顿
|
| 230 |
+
await delayClick(500);
|
| 231 |
+
// 清空输入框并输入密码
|
| 232 |
+
await page.click("#login-account-password", { clickCount: 3 });
|
| 233 |
+
await page.type("#login-account-password", password, {
|
| 234 |
+
delay: 100,
|
| 235 |
+
});
|
| 236 |
+
|
| 237 |
+
// 模拟人类在输入完成后思考的短暂停顿
|
| 238 |
+
await delayClick(1000);
|
| 239 |
+
|
| 240 |
+
// 假设登录按钮的ID是'login-button',点击登录按钮
|
| 241 |
+
await page.waitForSelector("#login-button");
|
| 242 |
+
await delayClick(500); // 模拟在点击登录按钮前的短暂停顿
|
| 243 |
+
try {
|
| 244 |
+
await Promise.all([
|
| 245 |
+
page.waitForNavigation({ waitUntil: "domcontentloaded" }), // 等待 页面跳转 DOMContentLoaded 事件
|
| 246 |
+
page.click("#login-button"), // 点击登录按钮触发跳转
|
| 247 |
+
]); //注意如果登录失败,这里会一直等待跳转,导致脚本执行失败
|
| 248 |
+
} catch (error) {
|
| 249 |
+
console.error("Navigation timed out in login.:", error);
|
| 250 |
+
throw new Error("Navigation timed out in login.");
|
| 251 |
+
}
|
| 252 |
+
await delayClick(1000);
|
| 253 |
+
}
|
| 254 |
+
|
| 255 |
+
async function bypassDetection(page) {
|
| 256 |
+
// 在页面上下文中执行脚本,修改 `navigator.webdriver` 以及其他反检测属性
|
| 257 |
+
await page.evaluateOnNewDocument(() => {
|
| 258 |
+
// Overwrite the `navigator.webdriver` property to return `undefined`.
|
| 259 |
+
Object.defineProperty(navigator, "webdriver", {
|
| 260 |
+
get: () => undefined,
|
| 261 |
+
});
|
| 262 |
+
|
| 263 |
+
// Pass the Chrome Test.
|
| 264 |
+
window.chrome = {
|
| 265 |
+
runtime: {},
|
| 266 |
+
// Add more if needed
|
| 267 |
+
};
|
| 268 |
+
|
| 269 |
+
// Pass the Permissions Test.
|
| 270 |
+
const originalQuery = window.navigator.permissions.query;
|
| 271 |
+
window.navigator.permissions.query = (parameters) =>
|
| 272 |
+
parameters.name === "notifications"
|
| 273 |
+
? Promise.resolve({ state: Notification.permission })
|
| 274 |
+
: originalQuery(parameters);
|
| 275 |
+
|
| 276 |
+
// Pass the Plugins Length Test.
|
| 277 |
+
Object.defineProperty(navigator, "plugins", {
|
| 278 |
+
get: () => [1, 2, 3, 4, 5], // Make plugins array non-empty
|
| 279 |
+
});
|
| 280 |
+
|
| 281 |
+
// Pass the Languages Test.
|
| 282 |
+
Object.defineProperty(navigator, "languages", {
|
| 283 |
+
get: () => ["en-US", "en"],
|
| 284 |
+
});
|
| 285 |
+
});
|
| 286 |
+
}
|
test_linuxdo.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { connect } from "puppeteer-real-browser";
|
| 2 |
+
|
| 3 |
+
while (true) {
|
| 4 |
+
console.log("Start of test.js");
|
| 5 |
+
const { page, browser } = await connect({
|
| 6 |
+
headless: 'auto',
|
| 7 |
+
args: [],
|
| 8 |
+
customConfig: {},
|
| 9 |
+
skipTarget: [],
|
| 10 |
+
fingerprint: false,
|
| 11 |
+
turnstile: true,
|
| 12 |
+
connectOption: {},
|
| 13 |
+
});
|
| 14 |
+
var cl = setInterval(() => {
|
| 15 |
+
page
|
| 16 |
+
.screenshot({ path: "example.png", fullPage: true })
|
| 17 |
+
.catch((e) => console.log(e.message));
|
| 18 |
+
}, 1000);
|
| 19 |
+
console.log("Connected to browser");
|
| 20 |
+
// https://nopecha.com/demo/cloudflare https://linux.do
|
| 21 |
+
await page.goto("https://linux.do", {
|
| 22 |
+
waitUntil: "domcontentloaded",
|
| 23 |
+
});
|
| 24 |
+
console.log("Navigated to page");
|
| 25 |
+
await page.waitForSelector(".link_row", {
|
| 26 |
+
timeout: 60000,
|
| 27 |
+
});
|
| 28 |
+
// await page.screenshot({ path: 'example.png' });
|
| 29 |
+
clearInterval(cl);
|
| 30 |
+
await browser.close();
|
| 31 |
+
console.log("End of test.js");
|
| 32 |
+
}
|
随笔.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# TimeoutError: Navigation timeout of 30000 ms exceeded 为什么puppeteer经常出现这个错误
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
在使用 Puppeteer 进行网页自动化或爬虫开发时,`TimeoutError: Navigation timeout of 30000 ms exceeded` 错误是一个常见问题。这个错误意味着 Puppeteer 在尝试加载页面或执行某个操作时超出了预设的最大等待时间(默认为 30,000 毫秒,即 30 秒)。这种情况通常由以下几个原因引起:
|
| 5 |
+
|
| 6 |
+
### 1. 网页加载慢
|
| 7 |
+
网页可能因为多种原因加载缓慢:
|
| 8 |
+
- 网络连接慢。
|
| 9 |
+
- 服务器响应时间长。
|
| 10 |
+
- 页面资源(如大型图片、视频或复杂的 JavaScript)多且复杂,加载时间长。
|
| 11 |
+
|
| 12 |
+
### 2. 重定向
|
| 13 |
+
有些网页在加载过程中可能会进行多次重定向,这些重定向可能会导致总加载时间超过设定的超时限制。
|
| 14 |
+
|
| 15 |
+
### 3. JavaScript 执行延迟
|
| 16 |
+
如果网页依赖于复杂的 JavaScript 或外部脚本执行完成才能呈现,且这些脚本执行缓慢,可能会触发超时错误。
|
| 17 |
+
|
| 18 |
+
### 4. 服务器问题
|
| 19 |
+
服务器可能会由于各种原因(例如高负载、维护或配置问题)响应缓慢,导致页面加载延迟。
|
| 20 |
+
|
| 21 |
+
### 解决策略
|
| 22 |
+
要解决或规避这个问题,可以采取以下几种方法:
|
| 23 |
+
|
| 24 |
+
#### a. 增加超时时间
|
| 25 |
+
如果你认为页面需要更长时间加载,可以增加超时设置:
|
| 26 |
+
```javascript
|
| 27 |
+
await page.goto('http://example.com', {waitUntil: 'networkidle0', timeout: 60000}); // 设置超时时间为60秒
|
| 28 |
+
```
|
| 29 |
+
|
| 30 |
+
#### b. 修改等待条件
|
| 31 |
+
Puppeteer 在等待 `networkidle0` 或 `networkidle2` 事件时可能会超时。这些事件分别代表没有网络连接或只有两个网络连接在指定时间内处于空闲状态。你可以改变等待策略以更适应页面的加载方式:
|
| 32 |
+
```javascript
|
| 33 |
+
await page.goto('http://example.com', {waitUntil: 'domcontentloaded'});
|
| 34 |
+
```
|
| 35 |
+
这会让 Puppeteer 等待到 `DOM` 内容加载完成即继续执行,而不是等待所有外部资源加载完成。
|
| 36 |
+
|
| 37 |
+
#### c. 检查和优化页面加载项
|
| 38 |
+
如果你对服务器或页面有控制权,尝试优化网页性能。这可能包括减少页面资源大小、优化图片和使用更快的服务器。
|
| 39 |
+
|
| 40 |
+
#### d. 错误处理
|
| 41 |
+
增加错误处理逻辑,当加载超时时采取其他操作,例如重试或记录错误:
|
| 42 |
+
```javascript
|
| 43 |
+
try {
|
| 44 |
+
await page.goto('http://example.com', {waitUntil: 'networkidle0', timeout: 30000});
|
| 45 |
+
} catch (error) {
|
| 46 |
+
console.error('Navigation timeout exceeded: ' + error.message);
|
| 47 |
+
// 可以在此处添加重试逻辑或其他错误处理代码
|
| 48 |
+
}
|
| 49 |
+
```
|
| 50 |
+
|
| 51 |
+
#### e. 使用 headless:false 调试
|
| 52 |
+
在开发过程中,设置 `headless:false` 来观察 Puppeteer 在浏览器中实际执行的情况可能有助于发现问题原因:
|
| 53 |
+
```javascript
|
| 54 |
+
const browser = await puppeteer.launch({headless: false});
|
| 55 |
+
```
|
| 56 |
+
|
| 57 |
+
通过上述方法,你可以有效地解决或减少遇到 `TimeoutError: Navigation timeout of 30000 ms exceeded` 错误的情况,从而提高 Puppeteer 脚本的稳定性和效率。
|