Compare commits
2
Commits
d9a66c8b86
...
74a90d6190
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
74a90d6190 | ||
|
|
4be154d0cb |
@@ -9,6 +9,7 @@ source/
|
|||||||
├── openapi/main.py # 聚合层 (127.0.0.1:9888) /pixiv/get_image /bilibili/get_video
|
├── openapi/main.py # 聚合层 (127.0.0.1:9888) /pixiv/get_image /bilibili/get_video
|
||||||
├── pixiv/
|
├── pixiv/
|
||||||
│ ├── pixiv.py # Pixiv 图片服务 (9088)
|
│ ├── pixiv.py # Pixiv 图片服务 (9088)
|
||||||
|
│ ├── refresh_token.py # Pixiv OAuth token 刷新守护
|
||||||
│ └── tokens.template.json # Pixiv token 模板
|
│ └── tokens.template.json # Pixiv token 模板
|
||||||
└── bilibili/
|
└── bilibili/
|
||||||
├── bilibili.py # bilibili 视频服务 (19990)
|
├── bilibili.py # bilibili 视频服务 (19990)
|
||||||
@@ -16,18 +17,19 @@ source/
|
|||||||
├── qr_login.py # 扫码登录获取 cookie
|
├── qr_login.py # 扫码登录获取 cookie
|
||||||
└── qr_login_fast.py
|
└── qr_login_fast.py
|
||||||
nginx/
|
nginx/
|
||||||
└── openapi.rrrosmontis.icu.conf # nginx 统一入口(/pixiv/ /bilibili/ /img-master/ 图片反代)
|
└── openapi.rrrosmontis.icu.conf # nginx 统一入口(/pixiv/ /bilibili/ /img-master/ /img-original/ 图片反代)
|
||||||
systemd/
|
systemd/
|
||||||
├── openapi.service # 聚合层
|
├── openapi.service # 聚合层
|
||||||
├── pixiv.service # Pixiv 图片服务
|
├── pixiv.service # Pixiv 图片服务
|
||||||
├── bilibili.service # bilibili 视频服务
|
├── bilibili.service # bilibili 视频服务
|
||||||
└── bilibili-refresh.service # cookie 刷新守护(每 50–70 分钟随机刷新)
|
├── bilibili-refresh.service # cookie 刷新守护(每 50–70 分钟随机刷新)
|
||||||
|
└── pixiv-token-refresh.service # Pixiv token 刷新守护(每 30–60 分钟随机刷新)
|
||||||
```
|
```
|
||||||
|
|
||||||
## 部署
|
## 部署
|
||||||
|
|
||||||
1. token:复制 `source/pixiv/tokens.template.json` 为 `source/pixiv/tokens.json`,填入实际 pixiv token
|
1. token:复制 `source/pixiv/tokens.template.json` 为 `source/pixiv/tokens.json`,填入实际 pixiv token
|
||||||
2. bilibili cookie:放置 `source/bilibili/cookies.txt`(`name=value` 格式),或运行 `qr_login.py` 扫码生成
|
2. bilibili cookie:放置 `source/bilibili/cookies.txt`(`name=value` 格式),或运行 `qr_login.py` 扫码生成
|
||||||
3. 服务:将 `systemd/*.service` 安装到 `/etc/systemd/system/`,启用并启动 openapi / pixiv / bilibili / bilibili-refresh(`systemctl enable --now`)
|
3. 服务:将 `systemd/*.service` 安装到 `/etc/systemd/system/`,启用并启动 openapi / pixiv / bilibili / bilibili-refresh / pixiv-token-refresh(`systemctl enable --now`)
|
||||||
4. nginx:将 `nginx/openapi.rrrosmontis.icu.conf` 放入 `/etc/nginx/conf.d/`,`nginx -t && systemctl reload nginx`
|
4. nginx:将 `nginx/openapi.rrrosmontis.icu.conf` 放入 `/etc/nginx/conf.d/`,`nginx -t && systemctl reload nginx`
|
||||||
5. 定时任务:无 cron/timer——bilibili-refresh 为常驻守护,内部循环每 50–70 分钟刷新 cookie,systemd `Restart=always` 兜底
|
5. 定时任务:无 cron/timer——bilibili-refresh(每 50–70 分钟刷 cookie)与 pixiv-token-refresh(每 30–60 分钟刷 Pixiv OAuth token,轮换后的 refresh_token 原子落盘)均为常驻守护,内部循环随机间隔,systemd `Restart=always` 兜底
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# openapi.rrrosmontis.icu 统一入口(原 api/pixiv/apixiv.bu4.ren 合并)
|
# openapi.rrrosmontis.icu 统一入口(原 api/pixiv/apixiv.bu4.ren 合并)
|
||||||
# /pixiv/ /bilibili/ -> openapi 聚合层 127.0.0.1:9888 (main.py)
|
# /pixiv/ /bilibili/ -> openapi 聚合层 127.0.0.1:9888 (main.py)
|
||||||
# /img-master/ -> i.pximg.net 图片反代(原 apixiv.bu4.ren 逻辑,方案二)
|
# /img-master/ /img-original/ -> i.pximg.net 图片反代(原 apixiv.bu4.ren 逻辑,方案二)
|
||||||
|
# 2026-09-02 补 /img-original/:App API 原图 URL 为 /img-original/ 前缀,此前未配置导致 404
|
||||||
|
|
||||||
proxy_cache_path /var/cache/nginx/openapi_img_cache levels=1:2 keys_zone=openapi_img_cache:20m inactive=1d max_size=5g;
|
proxy_cache_path /var/cache/nginx/openapi_img_cache levels=1:2 keys_zone=openapi_img_cache:20m inactive=1d max_size=5g;
|
||||||
|
|
||||||
@@ -57,6 +58,31 @@ server {
|
|||||||
add_header X-Cache-Status $upstream_cache_status;
|
add_header X-Cache-Status $upstream_cache_status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ---- 图片反代 i.pximg.net(/img-original/,App API 原图路径,2026-09-02 补)----
|
||||||
|
location ^~ /img-original/ {
|
||||||
|
set $backend i.pximg.net;
|
||||||
|
proxy_pass https://$backend$request_uri;
|
||||||
|
|
||||||
|
proxy_set_header Host i.pximg.net;
|
||||||
|
proxy_set_header User-Agent "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36";
|
||||||
|
proxy_set_header Referer "https://www.pixiv.net/";
|
||||||
|
|
||||||
|
proxy_ssl_server_name on;
|
||||||
|
proxy_ssl_name i.pximg.net;
|
||||||
|
|
||||||
|
proxy_connect_timeout 10s;
|
||||||
|
proxy_read_timeout 300s;
|
||||||
|
|
||||||
|
proxy_cache openapi_img_cache;
|
||||||
|
proxy_cache_valid 200 7d;
|
||||||
|
proxy_cache_valid 404 5m;
|
||||||
|
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
|
||||||
|
proxy_cache_lock on;
|
||||||
|
proxy_cache_key "$scheme$proxy_host$request_uri";
|
||||||
|
|
||||||
|
add_header X-Cache-Status $upstream_cache_status;
|
||||||
|
}
|
||||||
|
|
||||||
# ---- pixiv(openapi 聚合层 /pixiv/get_image)----
|
# ---- pixiv(openapi 聚合层 /pixiv/get_image)----
|
||||||
location ^~ /pixiv/ {
|
location ^~ /pixiv/ {
|
||||||
proxy_pass http://127.0.0.1:9888;
|
proxy_pass http://127.0.0.1:9888;
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# refresh_token.py — Pixiv OAuth token 定时刷新守护
|
||||||
|
# 逻辑(照旧): 启动立即刷新一次, 之后每 random 30~60 分钟刷新一次, 失败 5 分钟后重试。
|
||||||
|
# 每次刷新把新的 access_token + refresh_token 原子落盘:
|
||||||
|
# Pixiv 每次刷新会轮换 refresh_token, 必须保存新的, 否则续期链会断。
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import random
|
||||||
|
import time
|
||||||
|
import traceback
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import requests
|
||||||
|
import urllib3
|
||||||
|
|
||||||
|
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||||
|
|
||||||
|
TOKEN_PATH = Path(__file__).parent / "tokens.json"
|
||||||
|
UA = "PixivIOSApp/7.13.0 (iOS 14.6; iPhone)"
|
||||||
|
INTERVAL_RANGE = (30 * 60, 60 * 60) # 半小时 ~ 一小时 之间随机
|
||||||
|
RETRY_INTERVAL = 5 * 60 # 失败后 5 分钟重试
|
||||||
|
|
||||||
|
|
||||||
|
def log(msg):
|
||||||
|
print(time.strftime("[%Y-%m-%d %H:%M:%S]") + " " + str(msg), flush=True)
|
||||||
|
|
||||||
|
|
||||||
|
def do_refresh():
|
||||||
|
data = json.loads(TOKEN_PATH.read_text(encoding="utf-8"))
|
||||||
|
rt = data.get("refresh_token")
|
||||||
|
if not rt:
|
||||||
|
raise RuntimeError("tokens.json 缺少 refresh_token")
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
"client_id": "MOBrBDS8blbauoSck0ZfDbtuzpyT",
|
||||||
|
"client_secret": "lsACyCD94FhDUtGTXi3QzcFE2uU1hqtDaKeqrdwj",
|
||||||
|
"grant_type": "refresh_token",
|
||||||
|
"refresh_token": rt,
|
||||||
|
"include_policy": 1,
|
||||||
|
"get_secure_url": 1,
|
||||||
|
}
|
||||||
|
headers = {"User-Agent": UA, "App-OS": "iOS", "App-OS-Version": "14.6"}
|
||||||
|
try:
|
||||||
|
resp = requests.post("https://oauth.secure.pixiv.net/auth/token",
|
||||||
|
data=payload, headers=headers, timeout=20, verify=True)
|
||||||
|
verify_mode = "CA验证"
|
||||||
|
except requests.exceptions.SSLError:
|
||||||
|
resp = requests.post("https://oauth.secure.pixiv.net/auth/token",
|
||||||
|
data=payload, headers=headers, timeout=20, verify=False)
|
||||||
|
verify_mode = "CA验证失败,回退不验证"
|
||||||
|
|
||||||
|
if resp.status_code != 200:
|
||||||
|
raise RuntimeError(f"HTTP {resp.status_code}: {resp.text[:120]}")
|
||||||
|
j = resp.json()
|
||||||
|
new_at = j.get("access_token")
|
||||||
|
new_rt = j.get("refresh_token")
|
||||||
|
if not new_at or not new_rt:
|
||||||
|
raise RuntimeError(f"响应缺少 token 字段: keys={list(j.keys())}")
|
||||||
|
|
||||||
|
data["access_token"] = new_at
|
||||||
|
data["refresh_token"] = new_rt
|
||||||
|
tmp = TOKEN_PATH.with_suffix(".json.tmp")
|
||||||
|
tmp.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||||
|
os.chmod(tmp, 0o600)
|
||||||
|
os.replace(tmp, TOKEN_PATH)
|
||||||
|
log(f"刷新成功 ({verify_mode}) expires_in={j.get('expires_in')}s "
|
||||||
|
f"access_token={new_at[:8]}... refresh_token={new_rt[:8]}... 已落盘")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
log(f"守护启动, 刷新间隔 random {INTERVAL_RANGE[0]//60}~{INTERVAL_RANGE[1]//60} min")
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
do_refresh()
|
||||||
|
time.sleep(random.uniform(*INTERVAL_RANGE))
|
||||||
|
except Exception as e:
|
||||||
|
log(f"刷新失败: {e}")
|
||||||
|
traceback.print_exc()
|
||||||
|
time.sleep(RETRY_INTERVAL)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Pixiv Token Refresh Daemon
|
||||||
|
After=network-online.target
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Environment=PATH=/home/openapi/env/general/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||||
|
Environment=SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt
|
||||||
|
Type=simple
|
||||||
|
WorkingDirectory=/home/openapi/source/pixiv
|
||||||
|
ExecStart=/home/openapi/env/general/bin/python /home/openapi/source/pixiv/refresh_token.py
|
||||||
|
Restart=always
|
||||||
|
RestartSec=30
|
||||||
|
StandardOutput=append:/var/log/pixiv_token_refresh.log
|
||||||
|
StandardError=append:/var/log/pixiv_token_refresh.log
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
Reference in New Issue
Block a user