Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
ios and Android
e.douyin.com
提交
0b745b62
E
e.douyin.com
项目概览
ios and Android
/
e.douyin.com
与 Fork 源项目一致
Fork自
inscode / NodeJS
通知
1
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
DevOps
流水线
流水线任务
计划
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
E
e.douyin.com
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
DevOps
DevOps
流水线
流水线任务
计划
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
流水线任务
提交
Issue看板
提交
0b745b62
编写于
7月 02, 2025
作者:
R
root
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
Wed Jul 2 19:37:00 CST 2025 inscode
上级
9b4c3294
变更
3
隐藏空白更改
内联
并排
Showing
3 changed file
with
159 addition
and
137 deletion
+159
-137
fetchWithPuppeteer.js
fetchWithPuppeteer.js
+32
-38
public/index.html
public/index.html
+125
-97
run.js
run.js
+2
-2
未找到文件。
fetchWithPuppeteer.js
浏览文件 @
0b745b62
...
...
@@ -107,10 +107,8 @@ function getTimestamp() {
return
`
${
YYYY
}${
MM
}${
DD
}
_
${
hh
}${
mm
}${
ss
}
`
;
}
async
function
detectChromePath
()
{
function
detectChromePath
()
{
const
candidates
=
[
'
google-chrome
'
,
'
chromium-browser
'
,
'
chromium
'
];
// 1. 先尝试检测已安装的浏览器
for
(
const
cmd
of
candidates
)
{
try
{
const
path
=
execSync
(
`which
${
cmd
}
`
).
toString
().
trim
();
...
...
@@ -122,38 +120,7 @@ async function detectChromePath() {
continue
;
}
}
// 2. 尝试自动安装Chromium
console
.
log
(
'
🔄 未检测到Chromium,尝试自动安装...
'
);
try
{
execSync
(
'
apt-get update && apt-get install -y chromium
'
,
{
stdio
:
'
inherit
'
});
const
chromiumPath
=
execSync
(
'
which chromium
'
).
toString
().
trim
();
if
(
chromiumPath
)
{
console
.
log
(
`✅ Chromium 安装成功,路径:
${
chromiumPath
}
`
);
return
chromiumPath
;
}
}
catch
(
installErr
)
{
console
.
error
(
'
❌ Chromium自动安装失败:
'
,
installErr
.
message
);
}
// 3. 提供详细的安装指引
const
errorMsg
=
`
❌ 未检测到可用的 Chrome/Chromium
解决方案:
1. 自动安装(推荐):
- 运行: apt-get update && apt-get install -y chromium
2. 手动安装:
- Debian/Ubuntu: apt-get install chromium
- CentOS: yum install chromium
- MacOS: brew install chromium
- Windows: 下载安装 Chrome 或 Chromium
3. 指定已安装的浏览器路径:
- 修改 fetchWithPuppeteer.js 中的 executablePath 参数
`
;
throw
new
Error
(
errorMsg
);
throw
new
Error
(
"
❌ 未检测到可用的 Chrome/Chromium,请先安装!
"
);
}
/**
* 等待出现 + 自动轮询点击指定验证选项
...
...
@@ -205,7 +172,16 @@ async function checkTimerAndVerifyAutoRetry(page, verifyCode = '888888', timeout
let
isKeyinput
=
false
;
let
isClickbtt
=
false
;
pageGlobal
=
page
;
// 保存给后端接口用
try
{
while
(
Date
.
now
()
-
start
<
timeout
)
{
if
(
page
.
isClosed
())
{
console
.
log
(
'
🛑 检测到页面已关闭,结束轮询!
'
);
break
;
}
const
[
timerElement
]
=
await
page
.
$x
(
xpathForTimer
);
if
(
timerElement
)
{
...
...
@@ -287,11 +263,16 @@ async function checkTimerAndVerifyAutoRetry(page, verifyCode = '888888', timeout
console
.
log
(
`[完成了] 倒计时元素[ isClickbtt:
${
getTimestamp
()}
isKeyinput:
${
getTimestamp
()}
]`
);
// return;
}
}
//where
const
beforeClickFilename
=
`img/before_[超时]_
${
getTimestamp
()}
.png`
;
await
page
.
screenshot
({
path
:
beforeClickFilename
});
}
catch
(
err
)
{
console
.
log
(
'
🚫 访问 mainFrame 报错,说明页面已关闭,结束轮询!
'
);
}
console
.
log
(
`❌ [超时] (
${
timeout
/
1000
}
s) 未检测到倒计时 <= 30,未执行验证`
);
}
...
...
@@ -316,6 +297,7 @@ async function fetchQrFromPage(entryUrl, accountName = 'userTest') {
}
});
// try {
browserInstance
=
browser
;
...
...
@@ -366,6 +348,12 @@ async function fetchQrFromPage(entryUrl, accountName = 'userTest') {
}
// 监听二维码状态查询接口
if
(
url
.
includes
(
'
check_qrconnect
'
)
||
url
.
includes
(
"
check_qrcode
"
))
{
if
(
!
response
.
ok
)
{
console
.
error
(
`❌ 上游接口返回错误:
${
response
.
status
}
${
response
.
statusText
}
`
);
console
.
error
(
`❌ 响应内容:
${
text
}
`
);
return
;
}
const
text
=
await
response
.
text
();
console
.
log
(
'
📡 原始响应:
'
,
text
);
const
json
=
JSON
.
parse
(
text
);
...
...
@@ -388,7 +376,9 @@ async function fetchQrFromPage(entryUrl, accountName = 'userTest') {
}
// 已过期可选:刷新二维码
if
(
json
?.
data
?.
status
===
'
expired
'
&&
json
?.
data
?.
qrcode
)
{
//json?.data?.status === 'expired' && refused
if
(
json
?.
data
?.
qrcode
)
{
qrCodeContent
=
json
.
data
.
qrcode
;
console
.
log
(
'
♻️ 二维码已过期,已更新 base64
'
);
await
page
.
reload
({
waitUntil
:
'
networkidle2
'
});
...
...
@@ -463,8 +453,12 @@ page.on('framenavigated', async (frame) => {
console
.
log
(
"
📸 错误截图已保存为 error_screenshot.png
"
);
}
// } finally {
// console.log('🛑 自动关闭 Puppeteer');
// await browser.close();
// }
//await browser.close();
return
hookResult
;
//
return hookResult;
}
// module.exports = fetchQrFromPage;
...
...
public/index.html
浏览文件 @
0b745b62
...
...
@@ -2,103 +2,142 @@
<html
lang=
"zh-CN"
>
<head>
<meta
charset=
"UTF-8"
/>
<meta
name=
"viewport"
content=
"width=device-width, initial-scale=1.0"
/>
<title>
抖音扫码登录状态页
</title>
<style>
body
{
font-family
:
Arial
,
sans-serif
;
padding
:
20px
;
}
#qrcode-section
,
#scanned-section
,
#verify-section
,
#logged-section
,
#account1
,
#start-btn1
{
display
:
none
;
margin-bottom
:
20px
;
}
img
{
max-width
:
200px
;
}
#avatar
{
border-radius
:
50%
;
}
input
{
padding
:
5px
;
}
button
{
padding
:
5px
10px
;
}
.status
{
font-weight
:
bold
;
color
:
#0077cc
;
}
</style>
</head>
<body>
<h1>
扫码登录
</h1>
body
{
font-family
:
-apple-system
,
BlinkMacSystemFont
,
Arial
,
sans-serif
;
margin
:
0
;
padding
:
20px
;
background
:
#f9f9f9
;
color
:
#333
;
text-align
:
center
;
}
h1
{
margin-bottom
:
20px
;
}
<!-- 启动按钮 -->
<input
id=
"account"
placeholder=
"输入账户名"
>
<button
id=
"start-btn"
>
启动扫码
</button>
.container
{
max-width
:
400px
;
margin
:
0
auto
;
background
:
#fff
;
border-radius
:
10px
;
padding
:
20px
;
box-shadow
:
0
4px
20px
rgba
(
0
,
0
,
0
,
0.05
);
}
<!-- 停止按钮(可选) -->
<button
id=
"stop-btn"
>
停止运行
</button>
input
{
width
:
100%
;
padding
:
10px
;
margin
:
5px
0
15px
;
border
:
1px
solid
#ddd
;
border-radius
:
5px
;
box-sizing
:
border-box
;
font-size
:
16px
;
}
button
{
width
:
100%
;
background
:
#0077cc
;
border
:
none
;
color
:
#fff
;
font-size
:
16px
;
padding
:
12px
;
border-radius
:
5px
;
cursor
:
pointer
;
margin-bottom
:
10px
;
}
<!-- 新状态提示 -->
<div
id=
"qrcode-status"
>
当前状态:
<span
id=
"scan-status"
class=
"status"
>
加载中...
</span></div>
<!-- 一、二维码状态 -->
<div
id=
"qrcode-section"
>
<h2>
请使用抖音 APP 扫码
</h2>
<img
id=
"qrcode"
alt=
"等待生成二维码..."
/>
</div>
button
:hover
{
background
:
#005fa3
;
}
<img
id=
"avatar"
alt=
"头像"
/>
<!-- 二、已扫码状态 -->
<div
id=
"scanned-section"
>
<h2>
已扫码,请在手机确认
</h2>
<p>
<span
id=
"scanned-nickname"
></span></p>
</div>
img
{
max-width
:
200px
;
border-radius
:
10px
;
display
:
block
;
margin
:
10px
auto
;
}
<!-- 三、需要验证码状态 -->
<div
id=
"verify-section"
>
<h2>
请输入短信验证码
</h2>
<div
id=
"verify-error"
style=
"color:red;"
></div>
#avatar
{
border-radius
:
50%
;
width
:
100px
;
height
:
100px
;
object-fit
:
cover
;
}
<input
id=
"code"
placeholder=
"输入验证码"
/>
<button
id=
"submit"
>
提交验证码
</button>
</div>
.status
{
font-weight
:
bold
;
color
:
#0077cc
;
}
<!-- 四、登录成功状态 -->
<div
id=
"logged-section"
>
<h2>
🎉 登录成功
</h2>
<p>
昵称:
<span
id=
"nickname"
></span></p>
<img
id=
"logged-avatar"
alt=
"头像"
/>
</div>
#verify-error
{
color
:
red
;
margin
:
10px
0
;
}
<script>
document
.
getElementById
(
'
start-btn
'
).
onclick
=
async
()
=>
{
const
account
=
document
.
getElementById
(
'
account
'
).
value
.
trim
();
if
(
!
account
)
return
alert
(
'
请先输入账户名
'
);
@media
(
prefers-color-scheme
:
dark
)
{
body
{
background
:
#121212
;
color
:
#eee
;
}
.container
{
background
:
#1e1e1e
;
box-shadow
:
none
;
}
input
{
background
:
#333
;
border
:
1px
solid
#555
;
color
:
#fff
;
}
button
{
background
:
#3399ff
;
}
button
:hover
{
background
:
#0077cc
;
}
}
</style>
</head>
<body>
const
res
=
await
fetch
(
'
/start
'
,
{
method
:
'
POST
'
,
headers
:
{
'
Content-Type
'
:
'
application/json
'
},
body
:
JSON
.
stringify
({
account
})
}).
then
(
r
=>
r
.
json
());
<div
class=
"container"
>
<h1>
抖音扫码登录
</h1>
alert
(
res
.
success
?
res
.
msg
:
res
.
msg
);
};
<input
id=
"account"
placeholder=
"输入账户名"
>
<button
id=
"start-btn"
>
🚀 启动扫码
</button>
<button
id=
"stop-btn"
>
🛑 停止运行
</button>
document
.
getElementById
(
'
stop-btn
'
).
onclick
=
async
()
=>
{
const
res
=
await
fetch
(
'
/stop
'
,
{
method
:
'
POST
'
}).
then
(
r
=>
r
.
json
());
alert
(
res
.
success
?
res
.
msg
:
res
.
msg
);
};
</script>
<p
id=
"qrcode-status"
>
当前状态:
<span
id=
"scan-status"
class=
"status"
>
加载中...
</span></p>
<div
id=
"qrcode-section"
>
<h2>
请使用抖音 APP 扫码
</h2>
<img
id=
"qrcode"
alt=
"等待生成二维码..."
/>
</div>
<img
id=
"avatar"
alt=
"头像"
/>
<div
id=
"scanned-section"
>
<h2>
已扫码,请在手机确认
</h2>
<p>
昵称:
<span
id=
"scanned-nickname"
></span></p>
</div>
<div
id=
"verify-section"
>
<h2>
请输入短信验证码
</h2>
<div
id=
"verify-error"
></div>
<input
id=
"code"
placeholder=
"输入验证码"
/>
<button
id=
"submit"
>
提交验证码
</button>
</div>
<div
id=
"logged-section"
>
<h2>
🎉 登录成功
</h2>
<p>
昵称:
<span
id=
"nickname"
></span></p>
<img
id=
"logged-avatar"
alt=
"头像"
/>
</div>
</div>
<script>
document
.
getElementById
(
'
start-btn
'
).
onclick
=
async
()
=>
{
const
account
=
document
.
getElementById
(
'
account
'
).
value
.
trim
();
if
(
!
account
)
return
alert
(
'
请先输入账户名
'
);
const
res
=
await
fetch
(
'
/start
'
,
{
method
:
'
POST
'
,
headers
:
{
'
Content-Type
'
:
'
application/json
'
},
body
:
JSON
.
stringify
({
account
})
}).
then
(
r
=>
r
.
json
());
alert
(
res
.
success
?
res
.
msg
:
res
.
msg
);
};
document
.
getElementById
(
'
stop-btn
'
).
onclick
=
async
()
=>
{
const
res
=
await
fetch
(
'
/stop
'
,
{
method
:
'
POST
'
}).
then
(
r
=>
r
.
json
());
alert
(
res
.
success
?
res
.
msg
:
res
.
msg
);
};
async
function
pollStatus
()
{
const
res
=
await
fetch
(
'
/status
'
).
then
(
r
=>
r
.
json
());
// 更新状态文本
document
.
getElementById
(
'
scan-status
'
).
innerText
=
res
.
scanStatus
||
'
等待启动
'
;
document
.
getElementById
(
'
verify-error
'
).
innerText
=
res
.
verifyError
||
''
;
// 重置所有区域显示
document
.
getElementById
(
'
qrcode-section
'
).
style
.
display
=
'
none
'
;
document
.
getElementById
(
'
scanned-section
'
).
style
.
display
=
'
none
'
;
document
.
getElementById
(
'
verify-section
'
).
style
.
display
=
'
none
'
;
document
.
getElementById
(
'
logged-section
'
).
style
.
display
=
'
none
'
;
document
.
getElementById
(
'
avatar
'
).
style
.
display
=
'
none
'
;
document
.
getElementById
(
'
qrcode-status
'
).
style
.
display
=
'
none
'
;
// 如果已登录
// 隐藏所有状态块
[
'
qrcode-section
'
,
'
scanned-section
'
,
'
verify-section
'
,
'
logged-section
'
,
'
avatar
'
].
forEach
(
id
=>
{
document
.
getElementById
(
id
).
style
.
display
=
'
none
'
;
});
if
(
res
.
user
?.
basic
?.
nickname
)
{
document
.
getElementById
(
'
logged-section
'
).
style
.
display
=
'
block
'
;
document
.
getElementById
(
'
nickname
'
).
innerText
=
res
.
user
.
basic
.
nickname
;
...
...
@@ -106,53 +145,42 @@ document.getElementById('stop-btn').onclick = async () => {
if
(
avatarUrl
)
{
document
.
getElementById
(
'
logged-avatar
'
).
src
=
avatarUrl
;
}
return
;
// 已登录直接返回
return
;
}
document
.
getElementById
(
'
qrcode-status
'
).
style
.
display
=
'
block
'
;
//显示头像
if
(
res
.
scanUser
?.
avatar_url
)
{
document
.
getElementById
(
'
avatar
'
).
style
.
display
=
'
block
'
;
document
.
getElementById
(
'
avatar
'
).
src
=
res
.
scanUser
.
avatar_url
;
}
// 否则根据状态显示
if
(
res
.
scanUser
?.
avatar_url
)
{
document
.
getElementById
(
'
avatar
'
).
style
.
display
=
'
block
'
;
document
.
getElementById
(
'
avatar
'
).
src
=
res
.
scanUser
.
avatar_url
;
}
if
(
res
.
scanStatus
===
'
new
'
)
{
document
.
getElementById
(
'
avatar
'
).
style
.
display
=
'
none
'
;
document
.
getElementById
(
'
qrcode-section
'
).
style
.
display
=
'
block
'
;
if
(
res
.
qrcode
)
{
document
.
getElementById
(
'
qrcode
'
).
src
=
'
data:image/png;base64,
'
+
res
.
qrcode
;
}
}
else
if
(
res
.
scanStatus
===
'
scanned
'
)
{
document
.
getElementById
(
'
scanned-section
'
).
style
.
display
=
'
block
'
;
if
(
res
.
scanUser
?.
avatar_url
)
{
document
.
getElementById
(
'
avatar
'
).
src
=
res
.
scanUser
.
avatar_url
;
}
document
.
getElementById
(
'
scanned-nickname
'
).
innerText
=
res
.
scanUser
?.
screen_name
||
''
;
}
else
if
(
res
.
scanStatus
===
'
verify
'
&&
res
.
pendingVerify
)
{
document
.
getElementById
(
'
verify-section
'
).
style
.
display
=
'
block
'
;
}
}
// 提交验证码
document
.
getElementById
(
'
submit
'
).
onclick
=
async
()
=>
{
const
code
=
document
.
getElementById
(
'
code
'
).
value
.
trim
();
if
(
!
code
)
{
alert
(
'
请输入验证码
'
);
return
;
}
if
(
!
code
)
return
alert
(
'
请输入验证码
'
);
const
res
=
await
fetch
(
'
/verify
'
,
{
method
:
'
POST
'
,
headers
:
{
'
Content-Type
'
:
'
application/json
'
},
headers
:
{
'
Content-Type
'
:
'
application/json
'
},
body
:
JSON
.
stringify
({
code
})
}).
then
(
r
=>
r
.
json
());
alert
(
res
.
success
?
'
✅ 验证码已提交!
'
:
'
❌ 提交失败
'
);
};
// 定时轮询状态
setInterval
(
pollStatus
,
2000
);
pollStatus
();
</script>
</body>
</html>
run.js
浏览文件 @
0b745b62
...
...
@@ -26,8 +26,8 @@ app.post('/start', async (req, res) => {
account
).
then
(()
=>
{
console
.
log
(
`✅ [结束] 账户:
${
account
}
扫码流程结束`
);
currentBot
=
null
;
// 自动释放锁
currentAccount
=
null
;
//
currentBot = null; // 自动释放锁
//
currentAccount = null;
});
res
.
json
({
success
:
true
,
msg
:
`已启动账户:
${
account
}
`
});
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录