Bing Rewards自动化运行

Denvo 树犹如此,人何以堪

前言&摸索历程

之前看到Bing Rewards能赚一些东西,每天差不多能赚个1块多钱,但是又要求我天天使用必应搜索,而且还是手机端和电脑端都要。我肯定不能每天这么勤勤恳恳地做啊,所以我就想着能不能整个什么东西让它自己来跑。

欸,正好,我之前有一台旧电脑,CPU是i3-3220,内存有8G,让它运行这个应该很不错。系统上,我起初安装的是Windows 8.1,奈何到2024年10月,我安装的安卓模拟器没什么好使的,于是我转向了Linux。我希望用Waydroid来代替一众安卓模拟器,于是我最开始安装了KUbuntu。Waydroid在KDE上的表现并没有我预想中的这么好,会有一些Bug,比如安卓容器内的鼠标位置和外面KDE的鼠标位置不重合,会有一定的偏差。后来我换用Ubuntu,心想GNOME配Wayland使用Waydroid应该很丝滑吧,事实上我错了。Waydroid这回倒是运行得很正常,倒是我写自动化脚本的时候捉急了。很多自动化的功能在Wayland下运行不正常!甚至连鼠标的位置都没有什么好的方法获取,遂作罢。

最后,我使用了Windows 10加上WSA的组合,用下来发现这才是最顺滑的。虽然一直在说Windows屎山,但是方便也是真方便啊。经过一番折腾,便有了以下结果。

准备

这套系统需要这些东西。东西如何配置将在后面的章节说明。

  • 一台能安装Windows 10和WSA且流畅不卡顿的电脑
  • 新版Edge浏览器和WSA(安卓安装Via浏览器)
  • ADB 安卓调试桥
  • Java 21
  • C++编译器(比如VS的MSVC)
  • 电脑开机卡(或者用Wake On Lan代替)
  • 显卡欺骗器(Windows 10有软件的虚拟显示器,大家可以试试)

预期

  1. 这套系统通过电脑开机卡,使电脑每天自动启动。你也可以通过WOL或自己每天辛苦一下开个机代替。
  2. 电脑中的Windows 10系统启动后自动启动我的Java程序,通过Java程序自动打开浏览器,完成Bing Rewards任务,并在结束任务后自动关机。

实战

安装系统和必要软件

首先安装Windows 10,这个应该没什么太大问题。新版Edge浏览器是后续更新后的Windows 10的默认浏览器,所以Edge不需要额外安装。

然后安装Java 21,安装Java的教程也很多,这个问题也不大。

其次是安装WSA,这个我推荐使用Github上有人制作的安装包,链接。选择自己对应的版本后下载,然后将下载的文件解压到一个文件夹,路径最好是全英文的。解压后,里面会有一个Run.bat的批处理文件。先在系统的控制面板->程序->启用或关闭Windows功能中启用虚拟机平台,然后重启Windows系统,最后运行刚才的Run.bat完成WSA的安装。

WSA安装好之后,在开始菜单中找到“适用于Android™的Windows子系统”,在那里面把开发人员选项打开,然后通过adb连接WSA,通过命令行cmd输入adb connect 127.0.0.1:58526。连接成功后,下载一个Via的安装包,然后通过命令adb install via.apk安装Via浏览器。

最后为Edge浏览器和Via浏览器安装上对应的挂机脚本,这个脚本不是我的,原作者提供了视频和教程,见B站

为Java编写JNI dll库

由于我是使用的Java来编写的,Windows API并不能直接在Java中访问,而我需要Windows API来对窗口进行一些调整。

编译后的JNI dll可以直接点击下载: dll下载

以下是dll的创建方法:

在VS中新建一个dll项目,然后在dllmain.cpp中这样写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include <iostream>
#include <vector>
#include <windows.h>
#include "jni.h"

BOOL APIENTRY DllMain(HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved) {
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

extern "C" {

//java函数WindowManager.moveWindow(long hwnd, int x, int y)的实现.移动一个窗口.
JNIEXPORT void JNICALL Java_main_WindowManager_moveWindow(JNIEnv* env, jobject obj, jlong hwnd, jint x, jint y) {
SetWindowPos((HWND)hwnd, NULL, x, y, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
}

//java函数WindowManager.resizeWindow(long hwnd, int width, int height)的实现.调整一个窗口的大小.
JNIEXPORT void JNICALL Java_main_WindowManager_resizeWindow(JNIEnv* env, jobject obj, jlong hwnd, jint width, jint height) {
SetWindowPos((HWND)hwnd, NULL, 0, 0, width, height, SWP_NOMOVE | SWP_NOZORDER);
}

//用于C++ WindowsAPI获取所有窗口句柄时的回调函数,仅在下面Java函数WindowManager.getAllWindow()的实现中使用.
BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam) {
std::vector* windowHandles = reinterpret_cast<std::vector*>(lParam);
windowHandles->push_back(reinterpret_cast(hwnd));
return TRUE;
}

//java函数WindowManager.getAllWindow()的实现.获取所有窗口的句柄并以long数组的形式返回.
JNIEXPORT jlongArray JNICALL Java_main_WindowManager_getAllWindow(JNIEnv* env, jobject obj) {
std::vector windowHandles;
//通过C++ WindowsAPI获取所有窗口.
EnumWindows(EnumWindowsProc, reinterpret_cast(&windowHandles));
//创建Java long数组.
jlongArray result = env->NewLongArray(windowHandles.size());
if (result == nullptr) {
return nullptr; //内存不足.
}
//将C++中的数据复制进Java数组.
env->SetLongArrayRegion(result, 0, windowHandles.size(), windowHandles.data());
return result;
}

//java函数WindowManager.getWindowTitle(long hwnd)的实现.获取窗口的标题.
JNIEXPORT jstring JNICALL Java_main_WindowManager_getWindowTitle(JNIEnv* env, jobject obj, jlong hwnd) {
wchar_t buffer[256];
//获取窗口标题.
int length = GetWindowTextW((HWND)hwnd, buffer, 256);

if (length > 0) {
//将C++字符串转为Java字符串.
return env->NewString((const jchar*)buffer, length);
}
else {
//获取标题失败.
return nullptr;
}
}

//java函数WindowManager.getNativeWindowPosition(long hwnd)的实现.获取窗口的位置.
JNIEXPORT jintArray JNICALL Java_main_WindowManager_getNativeWindowPosition(JNIEnv* env, jobject obj, jlong hwnd) {
RECT rect;
if (GetWindowRect((HWND)hwnd, &rect)) {
//创建Java int数组.
jintArray result = env->NewIntArray(2);
if (result == nullptr) {
return nullptr; //内存不足
}
//设置数组内容.
jint pos[2] = { rect.left, rect.top };
env->SetIntArrayRegion(result, 0, 2, pos);
return result;
}
else {
//获取位置失败.
return nullptr;
}
}

//java函数WindowManager.getNativeWindowSize(long hwnd)的实现.获取窗口的大小.
JNIEXPORT jintArray JNICALL Java_main_WindowManager_getNativeWindowSize(JNIEnv* env, jobject obj, jlong hwnd) {
RECT rect;
if (GetWindowRect((HWND)hwnd, &rect)) {
//创建Java int数组.
jintArray result = env->NewIntArray(2);
if (result == nullptr) {
return nullptr; //内存不足
}
//设置数组内容.
jint size[2] = { rect.right - rect.left, rect.bottom - rect.top };
env->SetIntArrayRegion(result, 0, 2, size);
return result;
}
else {
//获取大小失败.
return nullptr;
}
}

//java函数WindowManager.bringToFront(long hwnd)的实现.把窗口放到最前并聚焦(不是置顶).
JNIEXPORT void JNICALL Java_main_WindowManager_bringToFront(JNIEnv* env, jobject obj, jlong hwnd) {
// 获取当前线程ID
DWORD currentThreadId = GetCurrentThreadId();
// 获取前台窗口的线程ID
DWORD foregroundThreadId = GetWindowThreadProcessId(GetForegroundWindow(), NULL);
// 如果当前线程不是前台线程,附加到前台线程
if (currentThreadId != foregroundThreadId) {
AttachThreadInput(currentThreadId, foregroundThreadId, TRUE);
}
// 将窗口带到前台
if (!SetForegroundWindow((HWND)hwnd)) {
//懒得解决编码问题了,这里输出使用英文(下面的都是):
std::cerr << "Failed to bring window to front. Error: " << GetLastError() << std::endl;
}
// 恢复线程输入状态
if (currentThreadId != foregroundThreadId) {
AttachThreadInput(currentThreadId, foregroundThreadId, FALSE);
}
}

//java函数WindowManager.setWindowFront(long hwnd)的实现.置顶窗口.
JNIEXPORT void JNICALL Java_main_WindowManager_setWindowFront(JNIEnv* env, jobject obj, jlong hwnd) {
if (!SetWindowPos((HWND)hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE)) {
std::cerr << "Failed to set window front. Error: " << GetLastError() << std::endl;
}
}

//java函数WindowManager.unsetWindowFront(long hwnd)的实现.取消置顶窗口.
JNIEXPORT void JNICALL Java_main_WindowManager_unsetWindowFront(JNIEnv* env, jobject obj, jlong hwnd) {
if (!SetWindowPos((HWND)hwnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE)) {
std::cerr << "Failed to unset window front. Error: " << GetLastError() << std::endl;
}
}
}

在项目的属性中,在 C/C++ -> 代码生成 中设置运行库为多线程调试(/MTd),然后在 VC++目录 中的 包含目录 添加如下:

1
2
%JAVA_HOME%/include
%JAVA_HOME%/include/win32

其中%JAVA_HOME%是你安装的Java的目录,这样操作之后VS才能知道jni.h和一堆的JNI有关的东西是从哪里来的。

生成WindowManager,并保存好产生的WindowManager.dll,这就是我刚才通过网络分享的文件。

在Java中编写JNI实现

整个Java项目没有使用到任何依赖,但使用了Maven。这是Java项目的文件列表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
├─.idea   //IDEA的配置文件,不管
├─src
│ ├─main
│ │ ├─java
│ │ │ └─main
│ │ │ ├─Main.java //java项目的主要文件,业务代码放在这里
│ │ │ ├─Robot.java //为Main.java提供的一些方便的函数(本文的例子中里面的方法没有全部被使用)
│ │ │ └─WindowManager.java //这一步需要编写的java代码,里面的部分函数本文并未使用到,但你或许需要这些.
│ │ └─resources
│ │ └─WindowManager.dll //上一步中获得的dll文件,请复制到这里
│ └─test
│ └─java
├─.gitignore
└─pom.xml

这一步我们来编写WindowManager类来实现JNI方法。代码见下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
package main;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;

/**
* 通过JNI控制Windows的窗口(不跨平台).此类中关于窗口位置和窗口大小的函数并不是精确的!
* @author Denvo
* @version 0.0.1
*/
public class WindowManager {
public WindowManager() {
//确定dll文件是否存在.
File dllFile = new File(Main.JAR_PATH + "/WindowManager.dll");
if (!dllFile.exists()) {
try {
Files.copy(WindowManager.class.getResourceAsStream("/WindowManager.dll"), dllFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
//尝试加载dll库.
System.load(dllFile.getPath());
}

/**
* 移动一个窗口到指定的位置.
* @param hwnd 需要移动的窗口
* @param x 移动到的目标位置
* @param y 移动到的目标位置
*/
public native void moveWindow(long hwnd, int x, int y);

/**
* 控制一个窗口的大小.
* @param hwnd 需要控制的窗口
* @param width 目标宽度
* @param height 目标高度
*/
public native void resizeWindow(long hwnd, int width, int height);

/**
* 获取Windows上的所有窗口.
* @return 将所有窗口的句柄以long的类型返回
*/
public native long[] getAllWindow();

/**
* 获取一个窗口的标题.
* @param hwnd 目标窗口的句柄
* @return 窗口标题
*/
public native String getWindowTitle(long hwnd);

/**
* 获取窗口的坐标.
* @param hwnd 目标窗口
* @return 窗口的坐标
*/
public int[] getWindowPosition(long hwnd) {
int[] result = getNativeWindowPosition(hwnd);
//由于关于坐标和大小的函数的返回值貌似不是很精确,只能通过这种方式勉强弥补一下,说白了就是不要乱动这里的+8和下面的-16.
result[0] = result[0] + 8;
result[1] = result[1] + 8;
return result;
}

/**
* native代码,在getWindowPosition中调用.
* @param hwnd 窗口
* @return 窗口位置
*/
private native int[] getNativeWindowPosition(long hwnd);

/**
* 获取窗口的大小.
* @param hwnd 目标窗口
* @return 窗口的大小(宽度,高度)
*/
public int[] getWindowSize(long hwnd) {
int[] result = getNativeWindowSize(hwnd);
//原因见getWindowPosition(long hwnd)方法.
result[0] = result[0] - 16;
result[1] = result[1] - 16;
return result;
}

/**
* native代码,在getWindowSize中调用.
* @param hwnd 窗口
* @return 窗口大小
*/
private native int[] getNativeWindowSize(long hwnd);

/**
* 使一个窗口被放到最前端(不是置顶)并聚焦(获得焦点).
* 能使任务栏上的图标发出橙色闪烁,提示需要查看这个窗口.
* 只想使窗口放到最前建议使用bringWindowFront()方法.
* @param hwnd 目标窗口
*/
public native void bringToFront(long hwnd);

/**
* 置顶窗口.
* @param hwnd 目标窗口
*/
public native void setWindowFront(long hwnd);

/**
* 取消置顶窗口.
* @param hwnd 目标窗口
*/
public native void unsetWindowFront(long hwnd);

/**
* 使一个窗口被放到最前端(不是置顶)并聚焦(获得焦点).
* 一般情况使用此方法.如需使任务栏图标闪烁提示,请使用bringToFront()方法.
* @param hwnd 目标窗口
*/
public void bringWindowFront(long hwnd) {
setWindowFront(hwnd);
try {
Thread.sleep(10);
unsetWindowFront(hwnd);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}

编写一些方便的函数

我在Robot类中写了一些方便的函数,内容是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
package main;

import java.awt.*;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;

/**
* 操作鼠标和键盘自动化的类.此类中所有的方法都是单线程(注释中标注的除外),可能会导致线程堵塞!
* @author Denvo
* @version 0.0.1
*/
public class Robot {
private final java.awt.Robot robot;
{
try {
robot = new java.awt.Robot();
robot.setAutoDelay(66);
} catch (AWTException e) {
throw new RuntimeException(e);
}
}

/**
* 点击屏幕上某一个位置.
* @param x 需要点击的位置的坐标
* @param y 需要点击的位置的坐标
*/
public void click(int x, int y) {
robot.mouseMove(x, y);
try {
Thread.sleep(66);
robot.mousePress(InputEvent.BUTTON1_DOWN_MASK);
Thread.sleep(66);
robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK);
Thread.sleep(66);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}

/**
* 通过模拟键盘敲击的方式输入文字.只支持大小写字母、阿拉伯数字以及实体键盘上有的符号!
* @param str 需要输入的文字
*/
public void input(String str) {
char[] chars = str.toCharArray();
for (int i = 0; i < chars.length; i++) {
if (Character.isLowerCase(chars[i])) {
//判断字符是否是小写字母
pressKey(KeyEvent.getExtendedKeyCodeForChar(chars[i]));
} else if (Character.isUpperCase(chars[i])) {
//判断字符是否是大写字母
robot.keyPress(KeyEvent.VK_SHIFT);
pressKey(KeyEvent.getExtendedKeyCodeForChar(Character.toLowerCase(chars[i])));
robot.keyRelease(KeyEvent.VK_SHIFT);
} else if (Character.isDigit(chars[i])) {
//判断字符是否为阿拉伯数字
pressKey(KeyEvent.getExtendedKeyCodeForChar(chars[i]));
}else if (' ' == chars[i]) {
//判断是否是空格
pressKey(KeyEvent.VK_SPACE);
} else if (',' == chars[i]) {
//后续都是各种符号的判断
pressKey(KeyEvent.VK_COMMA);
} else if ('.' == chars[i]) {
pressKey(KeyEvent.VK_PERIOD);
}else if ('/' == chars[i]) {
pressKey(KeyEvent.VK_SLASH);
} else if (';' == chars[i]) {
pressKey(KeyEvent.VK_SEMICOLON);
} else if ('\'' == chars[i]) {
pressKey(KeyEvent.VK_QUOTE);
} else if ('[' == chars[i]) {
pressKey(KeyEvent.VK_OPEN_BRACKET);
} else if (']' == chars[i]) {
pressKey(KeyEvent.VK_CLOSE_BRACKET);
} else if ('\\' == chars[i]) {
pressKey(KeyEvent.VK_BACK_SLASH);
} else if ('`' == chars[i]) {
pressKey(KeyEvent.VK_BACK_QUOTE);
} else if ('-' == chars[i]) {
pressKey(KeyEvent.VK_MINUS);
} else if ('=' == chars[i]) {
pressKey(KeyEvent.VK_EQUALS);
} else if ('~' == chars[i]) {
robot.keyPress(KeyEvent.VK_SHIFT);
pressKey(KeyEvent.VK_BACK_QUOTE);
robot.keyRelease(KeyEvent.VK_SHIFT);
} else if ('!' == chars[i]) {
robot.keyPress(KeyEvent.VK_SHIFT);
pressKey(KeyEvent.VK_1);
robot.keyRelease(KeyEvent.VK_SHIFT);
} else if ('@' == chars[i]) {
robot.keyPress(KeyEvent.VK_SHIFT);
pressKey(KeyEvent.VK_2);
robot.keyRelease(KeyEvent.VK_SHIFT);
} else if ('#' == chars[i]) {
robot.keyPress(KeyEvent.VK_SHIFT);
pressKey(KeyEvent.VK_3);
robot.keyRelease(KeyEvent.VK_SHIFT);
} else if ('$' == chars[i]) {
robot.keyPress(KeyEvent.VK_SHIFT);
pressKey(KeyEvent.VK_4);
robot.keyRelease(KeyEvent.VK_SHIFT);
} else if ('%' == chars[i]) {
robot.keyPress(KeyEvent.VK_SHIFT);
pressKey(KeyEvent.VK_5);
robot.keyRelease(KeyEvent.VK_SHIFT);
} else if ('^' == chars[i]) {
robot.keyPress(KeyEvent.VK_SHIFT);
pressKey(KeyEvent.VK_6);
robot.keyRelease(KeyEvent.VK_SHIFT);
} else if ('&' == chars[i]) {
robot.keyPress(KeyEvent.VK_SHIFT);
pressKey(KeyEvent.VK_7);
robot.keyRelease(KeyEvent.VK_SHIFT);
} else if ('*' == chars[i]) {
robot.keyPress(KeyEvent.VK_SHIFT);
pressKey(KeyEvent.VK_8);
robot.keyRelease(KeyEvent.VK_SHIFT);
} else if ('(' == chars[i]) {
robot.keyPress(KeyEvent.VK_SHIFT);
pressKey(KeyEvent.VK_9);
robot.keyRelease(KeyEvent.VK_SHIFT);
} else if (')' == chars[i]) {
robot.keyPress(KeyEvent.VK_SHIFT);
pressKey(KeyEvent.VK_0);
robot.keyRelease(KeyEvent.VK_SHIFT);
} else if ('_' == chars[i]) {
robot.keyPress(KeyEvent.VK_SHIFT);
pressKey(KeyEvent.VK_MINUS);
robot.keyRelease(KeyEvent.VK_SHIFT);
} else if ('+' == chars[i]) {
robot.keyPress(KeyEvent.VK_SHIFT);
pressKey(KeyEvent.VK_EQUALS);
robot.keyRelease(KeyEvent.VK_SHIFT);
} else if ('{' == chars[i]) {
robot.keyPress(KeyEvent.VK_SHIFT);
pressKey(KeyEvent.VK_OPEN_BRACKET);
robot.keyRelease(KeyEvent.VK_SHIFT);
} else if ('}' == chars[i]) {
robot.keyPress(KeyEvent.VK_SHIFT);
pressKey(KeyEvent.VK_CLOSE_BRACKET);
robot.keyRelease(KeyEvent.VK_SHIFT);
} else if ('|' == chars[i]) {
robot.keyPress(KeyEvent.VK_SHIFT);
pressKey(KeyEvent.VK_BACK_SLASH);
robot.keyRelease(KeyEvent.VK_SHIFT);
} else if (':' == chars[i]) {
robot.keyPress(KeyEvent.VK_SHIFT);
pressKey(KeyEvent.VK_SEMICOLON);
robot.keyRelease(KeyEvent.VK_SHIFT);
} else if ('"' == chars[i]) {
robot.keyPress(KeyEvent.VK_SHIFT);
pressKey(KeyEvent.VK_QUOTE);
robot.keyRelease(KeyEvent.VK_SHIFT);
} else if ('' == chars[i]) {
robot.keyPress(KeyEvent.VK_SHIFT);
pressKey(KeyEvent.VK_PERIOD);
robot.keyRelease(KeyEvent.VK_SHIFT);
} else if ('?' == chars[i]) {
robot.keyPress(KeyEvent.VK_SHIFT);
pressKey(KeyEvent.VK_SLASH);
robot.keyRelease(KeyEvent.VK_SHIFT);
} else {
throw new RuntimeException("键盘上未找到对应的字符:" + chars[i]);
}
}
}

/**
* 按一次键盘上特定的键.
* @param keycode 要按下的按键的keycode
*/
public void pressKey(int keycode) {
robot.waitForIdle();
robot.keyPress(keycode);
try {
Thread.sleep(66);
robot.keyRelease(keycode);
Thread.sleep(66);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}

/**
* 执行一段命令行.运行的命令将在新建的虚拟线程内执行,所有输出都将通过System.out.println()方法打印.
* @param cmd 需要执行的命令行.
* @param workDir 如果需要工作目录,则输入目录的路径;传入null则使用jar文件的目录.
*/
public void run(String cmd, String workDir) {
String[] cmd_slipt = cmd.split(" ");
ProcessBuilder processBuilder;
String charset;
//对跨平台(Windows/Linux/MacOS)处理.虽然本程序只打算用于Windows.
if (System.getProperty("os.name").toLowerCase().contains("win")) {
processBuilder = new ProcessBuilder("cmd", "/c");
charset = "GBK";
} else {
processBuilder = new ProcessBuilder("/bin/sh", "-c");
charset = "UTF-8";
}
for (int i = 0; i < cmd_slipt.length; i++) {
processBuilder.command().add(cmd_slipt[i]);
}
//对工作目录的处理.
if (workDir == null) {
processBuilder.directory(new File(Main.JAR_PATH));
} else {
processBuilder.directory(new File(workDir));
}
//合并输出流.
processBuilder.redirectErrorStream(true);
//启动进程.
Thread.startVirtualThread(() -> {
try {
Process process = processBuilder.start();
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), charset));
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
System.out.println("命令执行结束.退出代码为 " + process.waitFor());
process.destroy();
} catch (IOException | InterruptedException e) {
throw new RuntimeException(e);
}
});
}
}

这个java项目并没有设计日志等功能,它只能勉强满足使用。

编写Java主类实现自动化

所有的自动化的步骤和逻辑在这里完成。这个每个人要根据自己的需求来改,这是我的,就是按着顺序点击各个地方,然后等待一段时间自动关机。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
package main;

import java.io.File;

/**
* AutoRunner的主类.
* @author Denvo
* @version 0.0.1
*/
public class Main {
public static final String JAR_PATH = new File(Main.class.getProtectionDomain().getCodeSource().getLocation().getFile()).getParent();
public static void main(String[] args) throws InterruptedException {
//前面如果需要处理什么内容...

Robot robot = new Robot();
WindowManager wm = new WindowManager();

Thread.sleep(3000);
//启动电脑端edge浏览器.
robot.run("\"C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe\" cn.bing.com", null);
Thread.sleep(8000);
long[] hwnds = wm.getAllWindow();
for (int i = 0; i < hwnds.length; i++) {
if (wm.getWindowTitle(hwnds[i]) != null && wm.getWindowTitle(hwnds[i]).endsWith("Edge")) {
wm.moveWindow(hwnds[i], 0, 0);
Thread.sleep(10);
wm.bringWindowFront(hwnds[i]);
Thread.sleep(10);
wm.resizeWindow(hwnds[i], 1424, 1039);
Thread.sleep(10);
wm.bringWindowFront(hwnds[i]);
Thread.sleep(1000);
break;
}
}
robot.click(1220, 64);
Thread.sleep(1000);
robot.click(990, 160);
//启动WSA的via浏览器.
robot.run("\"C:\\Users\\Denvo\\AppData\\Local\\Microsoft\\WindowsApps\\MicrosoftCorporationII.WindowsSubsystemForAndroid_8wekyb3d8bbwe\\WsaClient.exe\" /launch wsa://mark.via", null);
//等待1分钟进行加载.
Thread.sleep(60000);
hwnds = wm.getAllWindow();
for (int i = 0; i < hwnds.length; i++) {
if (wm.getWindowTitle(hwnds[i]) != null && wm.getWindowTitle(hwnds[i]).endsWith("Via")) {
wm.moveWindow(hwnds[i], 1427, 6);
Thread.sleep(10);
wm.bringWindowFront(hwnds[i]);
Thread.sleep(10);
wm.resizeWindow(hwnds[i], 485, 1018);
Thread.sleep(10);
wm.bringWindowFront(hwnds[i]);
Thread.sleep(1000);
break;
}
}
robot.click(1468, 70);
Thread.sleep(1000);
robot.click(1477, 322);
Thread.sleep(1000);
robot.click(1550, 493);
Thread.sleep(1000);
robot.click(1515, 695);
//等待一定时间后关闭窗口并关机.
Thread.sleep(1800000);
robot.click(1041, 22);
Thread.sleep(1000);
robot.click(1889, 29);
robot.run("shutdown -s -t 0", null);
}
}

打包jar文件并放到目标机器

我使用了Maven,我在Maven中这么配置pom.xml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<?xml version=”1.0″ encoding=”UTF-8″?>
<project xmlns=”http://maven.apache.org/POM/4.0.0″
xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
xsi:schemaLocation=”http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd”>
<modelVersion>4.0.0</modelVersion>
<groupId>xyz.denvo</groupId>
<artifactId>AutoRunner</artifactId>
<version>0.0.1</version>

<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
<mainClass>main.Main</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy</id>
<phase>install</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

通过Maven的package打包项目成jar文件后,直接把这个jar文件扔到我那台i3-3220的机器上,然后编写一个启动文件(批处理文件):

1
java -jar ./AutoRunner-0.0.1.jar

将这个启动文件放在与jar文件同目录下,然后创建这个启动文件的快捷方式,并将快捷方式放在C:\ProgramData\Microsoft\Windows\Start Menu\Programs\StartUp目录下实现开机启动。

附加选项

可以给电脑安装个ToDesk啊,向日葵啊,甚至Sunshine也行,到时候就可以通过远程查看这个系统运行是否正常。当然,不安装也是可以的。

结束

现在,重启我的i3-3220电脑,这套自动化Bing Rewards系统就启动了,我把这个挂一段时间,就可以兑换很多东西了,需求解决,nice!

  • 标题: Bing Rewards自动化运行
  • 作者: Denvo
  • 创建于 : 2025-01-31 23:35:19
  • 更新于 : 2026-01-25 17:29:40
  • 链接: https://www.denvoshome.xyz/posts/BingRewards-AutoRun/
  • 版权声明: 本文章采用 CC BY-SA 4.0 进行许可。
评论