|
|
# 一、思维导图
|
|
|
|
|
|
![小红书自动化脚本](小红书自动化脚本.png)
|
|
|
|
|
|
# 二、整体架构
|
|
|
|
|
|
通过这个脚本,我们可以实现小红书的**自动打开**、**进入随机直播间**、**自动滑屏切换**、**点赞**、**关注**、**评论**以及**发布帖子**等功能。该脚本尤其适合那些需要频繁进行直播互动操作的人群,接下来,我们将逐步分析这段代码中的关键实现部分。
|
|
|
|
|
|
---
|
|
|
|
|
|
#### 1. 初始化及权限检查
|
|
|
|
|
|
首先,我们的代码会检查应用的悬浮窗权限。如果没有权限,则会通过悬浮窗提示用户授权,并立即退出脚本,以确保之后的界面显示功能能够正常工作。
|
|
|
|
|
|
```javascript
|
|
|
if (!floaty.checkPermission()) {
|
|
|
toast("请授予悬浮窗权限");
|
|
|
floaty.requestPermission();
|
|
|
exit();
|
|
|
}
|
|
|
```
|
|
|
|
|
|
`floaty.checkPermission()` 用于检查悬浮窗权限;如果没有授权,则`floaty.requestPermission()` 会请求权限,并提示用户。
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
#### 2. 创建悬浮窗组件
|
|
|
|
|
|
接下来,我们通过 `floaty.window` 创建一个包含按钮、滑动条、复选框和输入框的悬浮窗界面,以期为用户提供丰富的控制选项,这些控件允许用户开启与关闭点赞、设置点赞速度、切换评论模式、开启与关闭评论、开启与关闭自动滑屏与自动关注以及输入帖子内容等,我们使用XML 结构定义界面布局,这样可以使得用户看到的界面整洁,且功能齐全。
|
|
|
|
|
|
```javascript
|
|
|
let window = floaty.window(
|
|
|
<vertical padding="8">
|
|
|
<button id="toggle" text="爱猫的悬浮窗" />
|
|
|
<!-- 包含控制按钮的组件 -->
|
|
|
</vertical>
|
|
|
);
|
|
|
```
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
#### 3. 悬浮窗拖动与收起功能
|
|
|
|
|
|
为增强用户体验,我们的脚本实现了悬浮窗的拖动和展开/收起功能,以便在不需要的时候收起,免得占用太多屏幕控件。`window.toggle.setOnTouchListener` 方法实现了拖动逻辑,通过判断按下时间来识别用户是拖动还是点击操作。
|
|
|
|
|
|
```javascript
|
|
|
window.toggle.setOnTouchListener(function(view, event) {
|
|
|
switch (event.getAction()) {
|
|
|
case event.ACTION_DOWN:
|
|
|
x = event.getRawX();
|
|
|
y = event.getRawY();
|
|
|
windowX = window.getX();
|
|
|
windowY = window.getY();
|
|
|
downTime = new Date().getTime();
|
|
|
return true;
|
|
|
case event.ACTION_MOVE:
|
|
|
window.setPosition(windowX + (event.getRawX() - x), windowY + (event.getRawY() - y));
|
|
|
return true;
|
|
|
case event.ACTION_UP:
|
|
|
if (new Date().getTime() - downTime < 200) {
|
|
|
window.toggle.performClick();
|
|
|
}
|
|
|
return true;
|
|
|
}
|
|
|
return true;
|
|
|
});
|
|
|
```
|
|
|
|
|
|
通过这种方式,用户可以自由拖动悬浮窗位置,并通过点击展开或收起控制面板以节省屏幕空间。
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
#### 4. 自动点赞功能
|
|
|
|
|
|
点赞是直播互动中的关键操作,我们的脚本通过 `window.likeButton.click` 事件监听来实现点赞的开关控制,用户可以点击按钮来启动或停止点赞,并通过滑动条来调节点赞速度。
|
|
|
|
|
|
```javascript
|
|
|
function startLiking() {
|
|
|
stopLiking(); // 先停止之前的点赞定时器,防止重复
|
|
|
toast("开始点赞");
|
|
|
likingInterval = setInterval(() => {
|
|
|
likePost();
|
|
|
}, likeSpeed);
|
|
|
}
|
|
|
```
|
|
|
|
|
|
点赞是通过 `click()`来模拟点击屏幕右侧的指定位置实现的,其频率由 `likeSpeed` 控制,`window.speedControl.setOnSeekBarChangeListener` 用于监听滑动条变动,以实时调整点赞速度。
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
#### 5. 评论功能:静态与动态评论
|
|
|
|
|
|
评论功能允许用户选择发送静态或者动态评论,静态评论是从我们预先设置的 `staticComments` 数组中随机抽取的,而动态评论则允许用户自定义输入内容(之后会重复发送用户的自定义内容), `getStaticComment()` 函数负责从预设数组中随机选择评论。
|
|
|
|
|
|
```javascript
|
|
|
function getStaticComment() {
|
|
|
return staticComments[Math.floor(Math.random() * staticComments.length)];
|
|
|
}
|
|
|
```
|
|
|
|
|
|
评论功能的逻辑由 `startLiveOrComment` 函数实现。该函数根据用户选择的模式(静态/动态)来生成评论,并自动输入到评论框中。
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
#### 6. 自动关注和自动滑屏
|
|
|
|
|
|
我们的脚本中提供了自动关注主播和自动滑屏切换直播间的功能,开启这两个功能后,脚本会自动检测并点击屏幕中的“关注”按钮(通过查找id和关键字同时实现,如果找不到id就找关键字),同时通过模拟滑动操作,实现了在观看一定时间后(自定义的)自动切换下一个直播的功能。
|
|
|
|
|
|
```javascript
|
|
|
// 自动关注按钮
|
|
|
window.follow.click(() => {
|
|
|
if (!isAutoFollowing) {
|
|
|
autoFollowThread = threads.start(() => {
|
|
|
while (isAutoFollowing) {
|
|
|
let followButton = id("fv").findOne(2000);
|
|
|
if (followButton) followButton.click();
|
|
|
sleep(1000);
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
});
|
|
|
```
|
|
|
|
|
|
`swipe(device.width / 3, device.Height / 2, device.width / 2, device.Height / 4, 100);` 模拟向上滑动的功能,切换视频内容。
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
#### 7. 自动发帖功能
|
|
|
|
|
|
最后,我们脚本提供了一个简易的发帖功能。用户可以发布图文或纯文字帖子,通过 `createPost(content, isImagePost)` 函数来实现发帖操作,函数可以模拟一系列点击操作,输入用户指定的内容,并选择添加图片或是不添加图片(根据选择的模式)并发布帖子。
|
|
|
|
|
|
```javascript
|
|
|
function createPost(content, isImagePost) {
|
|
|
click(539.5, 2338); // 定位并点击发帖按钮
|
|
|
if (isImagePost) {
|
|
|
click(307, 395); // 选择图片区域
|
|
|
setText(content); // 输入帖子内容
|
|
|
click(964.5, 173.5); // 点击发布
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
`isImagePost` 参数决定是图文发布还是文字发布。这种模拟操作实现了简单的发帖功能,便于自动化管理发布内容。
|
|
|
|
|
|
---
|
|
|
|
|
|
###
|
|
|
|
|
|
# 三、完整代码解释
|
|
|
|
|
|
```javascript
|
|
|
const staticComments = [ /* 静态评论内容数组,用于随机选择评论内容 */ ];
|
|
|
const deviceW = 1080; // 设备宽度(可用device.width代替,但该函数有时会出现返回值为0的情况)
|
|
|
const deviceH = 2400; // 设备高度(可用device.height代替,但该函数有时会出现返回值为0的情况)
|
|
|
let isLiking = false; // 控制点赞开关
|
|
|
let iscomment = false; // 控制评论开关
|
|
|
let likeSpeed = 1000; // 初始点赞速度(毫秒)
|
|
|
let likingInterval; // 存储点赞的定时器
|
|
|
|
|
|
// 检查悬浮窗权限,如果没有则请求权限并退出脚本
|
|
|
if (!floaty.checkPermission()) {
|
|
|
toast("请授予悬浮窗权限");
|
|
|
floaty.requestPermission();
|
|
|
exit();
|
|
|
}
|
|
|
|
|
|
// 创建悬浮窗
|
|
|
let window = floaty.window(
|
|
|
<vertical padding="8">
|
|
|
<button id="toggle" text="爱猫的悬浮窗" />
|
|
|
<vertical id="controls" visibility="gone">
|
|
|
<button id="openButton" text="打开直播" />
|
|
|
<button id="likeButton" text="开始点赞" />
|
|
|
<text text="速度" textColor="#ffffff" />
|
|
|
<seekbar id="speedControl" max="2000" progress="500" layout_weight="1" />
|
|
|
<checkbox id="enableComment" text="是否发送评论" textColor="#ffffff"/>
|
|
|
<checkbox id="enableStaticComment" text="使用静态评论" textColor="#ffffff"/>
|
|
|
<input id="liveCommentInput" hint="在这里输入动态评论内容" />
|
|
|
<button id="startLiveButton" text="发评论" />
|
|
|
<button id="follow" text="关注" w="*"/>
|
|
|
<button id="autoRefresh" text="自动刷" w="*"/>
|
|
|
<button id="stopButton" text="停止运行" />
|
|
|
<input id="postContentInput" hint="请输入发帖内容" />
|
|
|
<checkbox id="isImagePost" text="图文发布" textColor="#ffffff"/>
|
|
|
<button id="publishPostButton" text="发布帖子" />
|
|
|
</vertical>
|
|
|
</vertical>
|
|
|
);
|
|
|
setTimeout(() => {
|
|
|
ui.run(() => {
|
|
|
window.liveCommentInput.setHintTextColor(colors.parseColor("#ffffff")); // 设置提示文字颜色为白色
|
|
|
window.postContentInput.setHintTextColor(colors.parseColor("#ffffff"));
|
|
|
});
|
|
|
}, 500); // 延迟 500 毫秒
|
|
|
|
|
|
// 设置初始位置和拖动
|
|
|
window.setPosition(100, 100);
|
|
|
window.setSize(-2, -2);
|
|
|
|
|
|
// 监听触摸事件以实现拖动功能
|
|
|
window.toggle.setOnTouchListener(function(view, event) {
|
|
|
switch (event.getAction()) {
|
|
|
case event.ACTION_DOWN:
|
|
|
x = event.getRawX();
|
|
|
y = event.getRawY();
|
|
|
windowX = window.getX();
|
|
|
windowY = window.getY();
|
|
|
downTime = new Date().getTime();
|
|
|
return true;
|
|
|
case event.ACTION_MOVE:
|
|
|
window.setPosition(windowX + (event.getRawX() - x), windowY + (event.getRawY() - y));
|
|
|
return true;
|
|
|
case event.ACTION_UP:
|
|
|
if (new Date().getTime() - downTime < 200) {
|
|
|
window.toggle.performClick();
|
|
|
}
|
|
|
return true;
|
|
|
}
|
|
|
return true;
|
|
|
});
|
|
|
|
|
|
// 切换悬浮窗的展开和收起状态
|
|
|
window.toggle.click(function() {
|
|
|
if (window.controls.visibility === 0) {
|
|
|
window.controls.visibility = 8; // 收回
|
|
|
window.toggle.setText("展开");
|
|
|
} else {
|
|
|
window.controls.visibility = 0; // 展开
|
|
|
window.toggle.setText("爱猫的悬浮窗");
|
|
|
}
|
|
|
});
|
|
|
|
|
|
// 打开直播按钮事件
|
|
|
window.openButton.click(() => {
|
|
|
toast("开始运行");
|
|
|
threads.start(function() {
|
|
|
startLiveOrSignIn(); // 在新线程中调用,避免阻塞 UI 线程
|
|
|
});
|
|
|
});
|
|
|
|
|
|
// 停止按钮事件
|
|
|
window.stopButton.click(() => {
|
|
|
toast("停止运行");
|
|
|
window.close(); // 关闭悬浮窗
|
|
|
exit(); // 停止脚本运行
|
|
|
});
|
|
|
|
|
|
// 点赞按钮的点击事件
|
|
|
window.likeButton.click(() => {
|
|
|
isLiking = !isLiking; // 切换点赞状态
|
|
|
if (isLiking) {
|
|
|
window.likeButton.setText("停止点赞");
|
|
|
startLiking(); // 开始点赞
|
|
|
} else {
|
|
|
window.likeButton.setText("开始点赞");
|
|
|
stopLiking(); // 停止点赞
|
|
|
}
|
|
|
});
|
|
|
|
|
|
// 发布帖子按钮事件
|
|
|
window.publishPostButton.click(() => {
|
|
|
let postContent = window.postContentInput.text(); // 获取帖子内容
|
|
|
let isImagePost = window.isImagePost.isChecked(); // 获取是否图文发布
|
|
|
if (postContent) {
|
|
|
threads.start(() => {
|
|
|
createPost(postContent, isImagePost); // 调用发帖函数
|
|
|
});
|
|
|
toast("正在发布帖子...");
|
|
|
} else {
|
|
|
toast("请输入发帖内容!");
|
|
|
}
|
|
|
});
|
|
|
|
|
|
// 滑动条控制点赞速度
|
|
|
window.speedControl.setOnSeekBarChangeListener({
|
|
|
onProgressChanged: function(seekBar, progress, fromUser) {
|
|
|
likeSpeed = 2500 - progress; // 调整点赞速度
|
|
|
toast("当前速度: " + likeSpeed + " 毫秒");
|
|
|
if (isLiking) {
|
|
|
stopLiking();
|
|
|
startLiking();
|
|
|
}
|
|
|
}
|
|
|
});
|
|
|
|
|
|
// 点赞功能
|
|
|
function startLiking() {
|
|
|
stopLiking(); // 确保之前的定时器被清除,避免重复
|
|
|
toast("开始点赞");
|
|
|
likingInterval = setInterval(() => {
|
|
|
likePost();
|
|
|
}, likeSpeed);
|
|
|
}
|
|
|
|
|
|
// 停止点赞功能
|
|
|
function stopLiking() {
|
|
|
toast("停止点赞");
|
|
|
if (typeof likingInterval !== 'undefined') {
|
|
|
clearInterval(likingInterval); // 清除定时器
|
|
|
likingInterval = undefined; // 重置 likingInterval
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 点赞操作
|
|
|
function likePost() {
|
|
|
console.log("执行点赞操作");
|
|
|
click(900, 1000);
|
|
|
sleep(50);
|
|
|
click(900, 1000);
|
|
|
sleep(50);
|
|
|
}
|
|
|
|
|
|
// 保持脚本运行
|
|
|
setInterval(() => {}, 1000);
|
|
|
|
|
|
// 打开直播功能
|
|
|
function startLiveOrSignIn() {
|
|
|
launchApp('小红书');
|
|
|
sleep(6000);
|
|
|
swipe(1080 / 2, 2400 / 4, 1080 / 2, 2400 * 3 / 4, 500);
|
|
|
sleep(1000);
|
|
|
click(273.5, 682);
|
|
|
sleep(5000);
|
|
|
}
|
|
|
|
|
|
// 自动直播或评论
|
|
|
function startLiveOrComment(enableComment, enableStaticComment, dynamicCommentText) {
|
|
|
while (true) {
|
|
|
if (enableComment && !iscomment) {
|
|
|
let commentBox = desc("评论输入框").clickable(true).findOne(5000);
|
|
|
if (commentBox) {
|
|
|
commentBox.click();
|
|
|
sleep(1000);
|
|
|
enableStaticComment = window.enableStaticComment.checked;
|
|
|
dynamicCommentText = getDynamicCommentText();
|
|
|
let commentText = enableStaticComment ? getStaticComment() : dynamicCommentText;
|
|
|
input(commentText);
|
|
|
sleep(500);
|
|
|
|
|
|
let sendButton = text("发送").findOne(3000);
|
|
|
if (sendButton) {
|
|
|
sendButton.click();
|
|
|
console.log("评论发送成功:" + commentText);
|
|
|
} else {
|
|
|
console.log("未找到发送按钮");
|
|
|
}
|
|
|
sleep(3000);
|
|
|
} else {
|
|
|
console.log("未找到评论框,无法发送评论");
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 获取静态评论
|
|
|
function getStaticComment() {
|
|
|
return staticComments[Math.floor(Math.random() * staticComments.length)];
|
|
|
}
|
|
|
|
|
|
// 开始评论按钮事件
|
|
|
window.startLiveButton.click(() => {
|
|
|
iscomment = !iscomment;
|
|
|
if (iscomment) {
|
|
|
window.startLiveButton.setText("开始评论");
|
|
|
} else {
|
|
|
window.startLiveButton.setText("停止评论");
|
|
|
}
|
|
|
let commentEnabled = window.enableComment.checked;
|
|
|
let useStaticComment = window.enableStaticComment.checked;
|
|
|
let liveCommentText = window.liveCommentInput.text();
|
|
|
|
|
|
threads.start(() => {
|
|
|
startLiveOrComment(commentEnabled, useStaticComment, liveCommentText);
|
|
|
});
|
|
|
|
|
|
if (commentEnabled && (useStaticComment || liveCommentText)) {
|
|
|
toast("开始看直播并发送评论");
|
|
|
} else {
|
|
|
toast("开始看直播,不发送评论");
|
|
|
}
|
|
|
});
|
|
|
|
|
|
// 动态评论输入
|
|
|
window.liveCommentInput.click(() => {
|
|
|
dialogs.rawInput("输入动态评论内容").then(input => {
|
|
|
if (input) {
|
|
|
window.liveCommentInput.setText(input);
|
|
|
}
|
|
|
});
|
|
|
});
|
|
|
|
|
|
// 获取动态评论内容
|
|
|
function getDynamicCommentText() {
|
|
|
return window.liveCommentInput.getText();
|
|
|
}
|
|
|
|
|
|
// 自动刷新和关注功能
|
|
|
let isAutoRefreshing = false;
|
|
|
let isAutoFollowing = false;
|
|
|
let autoRefreshThread;
|
|
|
let autoFollowThread;
|
|
|
|
|
|
// 自动关注按钮事件
|
|
|
window.follow.click(() => {
|
|
|
if (!isAutoFollowing) {
|
|
|
isAutoFollowing = true;
|
|
|
autoFollowThread = threads.start(() => {
|
|
|
while (isAutoFollowing) {
|
|
|
let followButton = id("fv").findOne(2000);
|
|
|
if (followButton) {
|
|
|
followButton.click(); // 点击关注按钮
|
|
|
console.log("已点击关注(通过ID)");
|
|
|
} else {
|
|
|
// 如果通过 ID 找不到按钮,尝试通过文本查找
|
|
|
followButton = text("关注").findOne(2000);
|
|
|
if (followButton) {
|
|
|
followButton.click(); // 点击关注按钮
|
|
|
console.log("已点击关注 (通过文本)");
|
|
|
} else {
|
|
|
console.log("未找到关注按钮 (通过ID和文本)");
|
|
|
}
|
|
|
}
|
|
|
sleep(1000); // 等待 1 秒再进行下一次操作
|
|
|
}
|
|
|
});
|
|
|
|
|
|
// 更新按钮文本
|
|
|
window.follow.setText("停止关注");
|
|
|
} else {
|
|
|
// 如果已经在自动关注状态,则停止自动关注
|
|
|
console.log("停止自动关注");
|
|
|
|
|
|
isAutoFollowing = false;
|
|
|
if (autoFollowThread) {
|
|
|
autoFollowThread.interrupt(); // 停止线程
|
|
|
}
|
|
|
|
|
|
// 恢复按钮文本
|
|
|
window.follow.setText("关注");
|
|
|
}
|
|
|
});
|
|
|
|
|
|
// 自动刷按钮事件
|
|
|
window.autoRefresh.click(() => {
|
|
|
if (!isAutoRefreshing) {
|
|
|
isAutoRefreshing = true;
|
|
|
console.log("开始自动刷");
|
|
|
|
|
|
// 创建一个新线程进行自动刷操作
|
|
|
autoRefreshThread = threads.start(function() {
|
|
|
while (isAutoRefreshing) {
|
|
|
swipe(deviceW / 3, deviceH / 2, deviceW / 2, deviceH / 4, 100); // 模拟向上滑动
|
|
|
console.log("切换到下一个视频");
|
|
|
sleep(5000); // 等待 5 秒再执行下一个滑动
|
|
|
}
|
|
|
});
|
|
|
|
|
|
// 更新按钮文本
|
|
|
window.autoRefresh.setText("停止自动刷");
|
|
|
} else {
|
|
|
// 如果已经在自动刷状态,则停止自动刷
|
|
|
isAutoRefreshing = false;
|
|
|
if (autoRefreshThread) {
|
|
|
autoRefreshThread.interrupt(); // 停止线程
|
|
|
}
|
|
|
|
|
|
console.log("停止自动刷");
|
|
|
window.autoRefresh.setText("自动刷"); // 恢复按钮文本
|
|
|
}
|
|
|
});
|
|
|
|
|
|
// 发帖功能实现
|
|
|
function createPost(content, isImagePost) {
|
|
|
click(539.5, 2338); // 点击发帖按钮位置
|
|
|
sleep(3000);
|
|
|
|
|
|
if (isImagePost) {
|
|
|
console.log("图文发帖");
|
|
|
click(307, 395); // 选择图片区域
|
|
|
let nextBox = desc("下一步").clickable(true).findOne(3000);
|
|
|
if (nextBox) {
|
|
|
nextBox.click();
|
|
|
}
|
|
|
sleep(1500);
|
|
|
click(926, 2323); // 点击继续按钮
|
|
|
sleep(2000);
|
|
|
click(540, 796); // 选择图片的描述框
|
|
|
sleep(1000);
|
|
|
setText(content); // 输入内容
|
|
|
sleep(1000);
|
|
|
click(964.5, 173.5); // 发布帖子
|
|
|
sleep(1000);
|
|
|
} else {
|
|
|
console.log("仅文字发帖");
|
|
|
click(341, 2336.5); // 选择文字发帖区域
|
|
|
sleep(1500);
|
|
|
click(374, 1088); // 点击输入框
|
|
|
sleep(1500);
|
|
|
setText(content); // 输入内容
|
|
|
sleep(1000);
|
|
|
let nextBox = desc("下一步").clickable(true).findOne(3000);
|
|
|
if (nextBox) {
|
|
|
nextBox.click();
|
|
|
}
|
|
|
sleep(9000);
|
|
|
nextBox = desc("下一步").clickable(true).findOne(3000);
|
|
|
nextBox.click();
|
|
|
sleep(2000);
|
|
|
click(675, 2271); // 发布帖子
|
|
|
}
|
|
|
console.log("发帖完成");
|
|
|
}
|
|
|
|
|
|
// 监听发帖内容输入点击事件
|
|
|
window.postContentInput.click(() => {
|
|
|
dialogs.rawInput("请输入发帖内容").then(input => {
|
|
|
if (input) {
|
|
|
window.postContentInput.setText(input); // 显示输入内容
|
|
|
}
|
|
|
});
|
|
|
});
|
|
|
``` |