├── README.md
├── images
├── donate.jpg
├── future_solution.jpg
├── install_guide_01.jpg
├── install_guide_02.jpg
├── install_guide_03.jpg
└── install_guide_04.jpg
└── wechat2Rss.js
/README.md:
--------------------------------------------------------------------------------
1 | # Wechat2RssScript
2 | > 一个自动提取微信公众号文章链接,并转化成RSS文件的自动化脚本
3 |
4 | ## 声明
5 | * 首先感谢[Auto.js](https://github.com/clearw5/Auto.js)和[AutoX](https://github.com/kkevsekk1/AutoX)。正是由于它们提供的基础服务,才使此脚本成为可能。
6 | * 本人非全职自由开发者,只是工作之余开发此脚本,后期维护和更新不一定及时,请见谅。
7 | * 本人运行此脚本的平台只有一台Nokia X6的备用机,针对其他种类手机,某些运行参数可以在脚本中调整,详见[脚本参数配置](#脚本参数配置)。如果调整参数后,仍然有问题,请收集运行日志,截图和录屏,提交issue,本人将抽空跟进。请勿催促,谢谢。
8 |
9 | ## 主要功能
10 | * 获取指定公众号文章的链接,并生成RSS的XML文件,便于RSS客户端订阅
11 |
12 | ### 功能限制
13 | * 当前脚本只适用于安卓平台,因为安卓手机的多样性,脚本无法适配所有手机的解锁动作,因此脚本只专注于公众号文章链接提取功能。
14 | * 当前测试成功的微信版本是*8.0.34*
15 | * 由于当前[AutoX](https://github.com/kkevsekk1/AutoX)提供的库功能有限,所以当前生成的RSS文件只包含了公众号文章的链接,无法包含全文内容。全文解析功能可以参考[扩展思路](#扩展思路)。
16 |
17 | ## 安装与使用
18 |
19 | ### 准备工作
20 | * 从[AutoX Release](https://github.com/kkevsekk1/AutoX/releases)中下载适合自己手机的AutoX APP,并安装。
21 | * 如需要设置AutoX定时运行脚本,需要将AutoX加入允许后台运行的列表(一般在电池管理配置菜单中)
22 | * 在AutoX中开启无障碍服务
23 |
24 |
25 | ### 脚本参数配置
26 | 可根据手机的具体情况调整一下参数
27 |
28 | ```javascript
29 | var interestingUps = [
30 | "drpei",
31 | "包邮区", "渤海小吏", "饭统戴老板",
32 | "集思录", "老和山下的小学僧", "卢克文工作室",
33 | "兽楼处", "睡前人间", "睡前消息编辑部", "天机奇谈", "铁头功社",
34 | "西西弗评论", "远川科技评论", "远川投资评论", "远川研究所"];
35 | var timeWaitForWakeupMobile = 1000;
36 | var timeBeforeBack = 1000;
37 | var timeWaitForBack = 2000;
38 | var timeWaitForClick = 3000;
39 | var longWait = 5000;
40 | var shortWait = 500;
41 | var onlyNewMessage = false;
42 | var deleteSubscriberChat = false;
43 | var targetXmlFilePath = "/sdcard/Wechat2Rss/Wechat2Rss.xml";
44 | ```
45 |
46 | | 变量 | 说明 |
47 | | :---- | :---- |
48 | | interestingUps | 关注的公众号名字,用于过滤广告 |
49 | | onlyNewMessage | 控制是否在有新公众号文章的时候来执行实际功能 |
50 | | deleteSubscriberChat | 控制是否在获取链接后删除公众号标签 |
51 | | targetXmlFilePath | 生成RSS XML文件的目标地址 |
52 | | timeWaitForWakeupMobile | 等待唤醒手机的时延 |
53 | | timeBeforeBack | 执行返回操作前的等待时延 |
54 | | timeWaitForBack | 执行返回操作后的等待时延 |
55 | | timeWaitForClick | 执行点击操作后的等待时延 |
56 | | longWait | 长时延 |
57 | | shortWait | 短时延 |
58 |
59 |
60 | ### 脚本导入与试运行
61 | * 点击右上角"+"按钮,点击导入,选择需要导入的脚本
62 |
63 | * 脚本导入成功后,点击脚本行中的三角图标,试运行
64 |
65 |
66 | ### 设置定时任务
67 | 如果脚本导入并试运行成功,则可以设置定时任务
68 |
69 |
70 | > Note: 一般设置定时任务需要解决屏幕解锁问题,或者选用一个备用机设置成无锁屏
71 |
72 |
73 | ## 扩展思路
74 | 
75 |
76 | 由于AutoX现有库的限制,要在脚本中实现全文解析会比较麻烦,所以只是生成了一个包含公众号文章链接的本地XML文件,用于RSS订阅。现有脚本限制主要在两个方面:
77 |
78 | * XML文件为手机本地文件。
79 | * XML文件中只包含文章链接,没有全文内容。
80 |
81 | 解决思路
82 | * 配备一个公网的云主机,用于接受来自手机的POST请求,解析全文,过滤广告,serve RSS全文文件
83 |
84 |
85 | ## 联系作者
86 | * 程序由个人独立开发,能力有限,难免出现一些Bug,欢迎大家反馈Bug和提出优化建议
87 | * 邮件: oxotoxo@foxmail.com
88 | * TG group: https://t.me/+c4OjVlN3pzE0ZmJl
89 |
90 | ## 捐助作者
91 | 此程序完全免费,如果你觉得这个程序对你有所帮助,可以通过扫面下方二维码(微信)
92 | 进行捐赠,金额请随意,谢谢你的理解和支持!
93 |
94 |
--------------------------------------------------------------------------------
/images/donate.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zjuerkzhang/Wechat2RssScript/f9ffa347ad0be57fb91843952b5a7813926d2d81/images/donate.jpg
--------------------------------------------------------------------------------
/images/future_solution.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zjuerkzhang/Wechat2RssScript/f9ffa347ad0be57fb91843952b5a7813926d2d81/images/future_solution.jpg
--------------------------------------------------------------------------------
/images/install_guide_01.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zjuerkzhang/Wechat2RssScript/f9ffa347ad0be57fb91843952b5a7813926d2d81/images/install_guide_01.jpg
--------------------------------------------------------------------------------
/images/install_guide_02.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zjuerkzhang/Wechat2RssScript/f9ffa347ad0be57fb91843952b5a7813926d2d81/images/install_guide_02.jpg
--------------------------------------------------------------------------------
/images/install_guide_03.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zjuerkzhang/Wechat2RssScript/f9ffa347ad0be57fb91843952b5a7813926d2d81/images/install_guide_03.jpg
--------------------------------------------------------------------------------
/images/install_guide_04.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zjuerkzhang/Wechat2RssScript/f9ffa347ad0be57fb91843952b5a7813926d2d81/images/install_guide_04.jpg
--------------------------------------------------------------------------------
/wechat2Rss.js:
--------------------------------------------------------------------------------
1 | function main(){
2 | var interestingUps = [
3 | "drpei",
4 | "包邮区", "渤海小吏", "饭统戴老板",
5 | "集思录", "老和山下的小学僧", "卢克文工作室",
6 | "兽楼处", "睡前人间", "睡前消息编辑部", "天机奇谈", "铁头功社",
7 | "西西弗评论", "远川科技评论", "远川投资评论", "远川研究所"];
8 | var timeWaitForWakeupMobile = 1000;
9 | var timeBeforeBack = 1000;
10 | var timeWaitForBack = 2000;
11 | var timeWaitForClick = 3000;
12 | var longWait = 5000;
13 | var shortWait = 500;
14 | var onlyNewMessage = false;
15 | var deleteSubscriberChat = false;
16 | var targetXmlFilePath = "/sdcard/Wechat2Rss/Wechat2Rss.xml";
17 |
18 | device.wakeUpIfNeeded();
19 | sleep(timeWaitForWakeupMobile);
20 |
21 | if (!text("订阅号消息").exists())
22 | {
23 | app.launchApp("微信");
24 | sleep(longWait);
25 | while(!text("微信").exists()){
26 | console.log("没有'微信'字样,模拟返回按键");
27 | back();
28 | sleep(timeWaitForBack);
29 | }
30 | }
31 | else
32 | {
33 | var t = text("订阅号消息").findOnce();
34 | //console.log(t );
35 | if (t != null && t.id().indexOf("text1") >= 0)
36 | {
37 | back();
38 | sleep(timeWaitForBack);
39 | }
40 | }
41 |
42 | sleep(shortWait);
43 | var ret = goToChatTab();
44 | if (!ret)
45 | {
46 | return;
47 | }
48 | sleep(shortWait);
49 | if (onlyNewMessage && !hasNewMessage())
50 | {
51 | console.log("没有新文章");
52 | home();
53 | return;
54 | }
55 |
56 |
57 | var retGoToSub = goToSubscriberAccountSession();
58 | if (!retGoToSub)
59 | {
60 | home();
61 | return;
62 | }
63 | sleep(longWait); // 等待订阅公众号同步
64 | sleep(longWait);
65 | var lv = selector().className("ListView").findOnce();
66 | //console.log(lv);
67 | var lls = lv.children();
68 | var articleTitles = {};
69 | for (var i = 0; i 0 )
75 | {
76 | console.log("发现'以下是更早消息',停止搜索");
77 | if (onlyNewMessage)
78 | {
79 | break;
80 | }
81 | else
82 | {
83 | continue;
84 | }
85 | }
86 | if (noNewElems.length > 0)
87 | {
88 | console.log("发现'已无更多订阅消息',停止搜索");
89 | deleteSubscriberChat = true;
90 | break;
91 | }
92 | var upName = "";
93 | var clickElems = lls[i].find(clickable());
94 | for (var j = 0; j=0 )
102 | {
103 | isUpHeaderElem = true;
104 | upName = tvs[k].text();
105 | console.log("发现感兴趣公众号: " + upName);
106 | break;
107 | }
108 | }
109 | if (!isUpHeaderElem && tvs.length > 0)
110 | {
111 | console.log("公众号 [" + upName + "] 的文章: " + tvs[0].text());
112 | if (articleTitles[upName] == undefined)
113 | {
114 | articleTitles[upName] = [tvs[0].text()]
115 | }
116 | else
117 | {
118 | articleTitles[upName].push(tvs[0].text());
119 | }
120 | //clickElems[j].click();
121 | //sleep(timeWaitForClick);
122 | //back();
123 | }
124 | }
125 |
126 | }
127 | console.log("---------------------");
128 | for (var key in articleTitles)
129 | {
130 | console.log(" + 订阅号: " + key);
131 | for (var i = 0; i < articleTitles[key].length; i++)
132 | {
133 | console.log( " - " + String(i+1) + ": " + articleTitles[key][i]);
134 | }
135 | }
136 | console.log("---------------------");
137 |
138 | var titleLinkMap = [];
139 | for (var key in articleTitles)
140 | {
141 | for (var i = 0; i < articleTitles[key].length; i++)
142 | {
143 | var processStr = "--> 处理文章 [" + String(i+1) + "/" + String(articleTitles[key].length) + "]: ";
144 | console.log(processStr + "'" + articleTitles[key][i] + "'")
145 | if (articleTitles[key][i].indexOf("余下") == 0 || articleTitles[key][i].indexOf("展开") == 0 )
146 | {
147 | continue;
148 | }
149 | var link = getArticleLink(articleTitles[key][i]);
150 | if (link != "")
151 | {
152 |
153 | titleLinkMap.push([articleTitles[key][i], link]);
154 | }
155 | }
156 | var ret = clickByTextClickableElemsInSteps([key, "删除", "删除"], ["long", "", ""], ["hfq", "", ""]);
157 | }
158 |
159 | if (titleLinkMap.length > 0)
160 | {
161 | var rssXmlContent = generateRssXmlFile(titleLinkMap);
162 | //console.log(rssXmlContent);
163 | sleep(shortWait);
164 | }
165 |
166 | back();
167 | sleep(timeWaitForBack);
168 | if (deleteSubscriberChat)
169 | {
170 | var ret = clickByTextClickableElemsInSteps(["订阅号消息", "删除该聊天", "删除"], ["long", "", ""], ["", "", ""]);
171 | }
172 | home();
173 |
174 | function findTextElemByTextAndId(textStr, idStr)
175 | {
176 | if (idStr == "")
177 | {
178 | var textElem = text(textStr).findOnce();
179 | if (!textElem)
180 | {
181 | console.log("没有找到text元素 [" + texts[i] + "]");
182 | }
183 | return textElem;
184 | }
185 | else
186 | {
187 | var textElems = text(textStr).find();
188 | for (var i = 0; i < textElems.length; i++)
189 | {
190 | if (textElems[i].id().indexOf(idStr) >= 0)
191 | {
192 | return textElems[i];
193 | }
194 | }
195 | return null;
196 | }
197 | }
198 |
199 |
200 | function clickByTextClickableElemsInSteps(texts, clickTypes, filterCondition)
201 | {
202 | for (var i = 0; i < texts.length; i++)
203 | {
204 | var textElem = findTextElemByTextAndId(texts[i], filterCondition[i]);
205 | if (!textElem)
206 | {
207 | console.log("没有找到text元素 [" + texts[i] + "]");
208 | return false;
209 | }
210 |
211 | var clickableElem = getTheToppestClickableElem(textElem);
212 | if (!clickableElem)
213 | {
214 | console.log("没有找到text元素 [" + texts[i] + "] 对应的可点击元素");
215 | return false;
216 | }
217 | if (clickTypes[i] == 'long')
218 | {
219 | clickableElem.longClick();
220 | }
221 | else
222 | {
223 | clickableElem.click();
224 | }
225 | sleep(timeWaitForClick);
226 | }
227 | return true;
228 | }
229 |
230 |
231 |
232 | function generateRssItem(title, link)
233 | {
234 | return '- \
235 | ' + title + '\
236 | ' + link + '\
237 | ' + title + '\
238 |
';
239 | }
240 |
241 | function generateRssXmlFile(titleLinkMap)
242 | {
243 | var xmlStr = '\
244 | \
245 | \
246 | 微信公众号精选\
247 | https://www.testing.com\
248 | 微信公众号精选';
249 | for (var i = 0; i < titleLinkMap.length; i++)
250 | {
251 | var rssItemStr = generateRssItem(titleLinkMap[i][0], titleLinkMap[i][1]);
252 | xmlStr = xmlStr + rssItemStr;
253 | }
254 | xmlStr = xmlStr + '';
255 |
256 | var targetFilePath = targetXmlFilePath;
257 | files.ensureDir(targetFilePath);
258 | files.write(targetFilePath, xmlStr);
259 | return xmlStr;
260 | }
261 |
262 | function getArticleLink(articleTitle)
263 | {
264 | if (articleTitle.indexOf("余下") == 0)
265 | {
266 | return "";
267 | }
268 | text(articleTitle).waitFor();
269 | var titleElem = text(articleTitle).findOnce();
270 | //console.log(articleTitle);
271 | //console.log(titleElem);
272 | var clickArticle = getTheToppestClickableElem(titleElem);
273 | if (!clickArticle)
274 | {
275 | console.log("无法找到可点击的文章项,文章标题: " + articleTitle);
276 | return "";
277 | }
278 | clickArticle.click();
279 | sleep(timeWaitForClick);
280 | id("eo").waitFor();
281 | //sleep(longWait);
282 | var threeDotsImg = id("eo").findOnce();
283 | if (!threeDotsImg)
284 | {
285 | console.log("无法找到文章的三点菜单,文章标题: " + articleTitle);
286 | back();
287 | sleep(timeWaitForBack);
288 | return "";
289 | }
290 | threeDotsImg.click();
291 | sleep(timeWaitForClick);
292 | //text("复制链接").waitFor();
293 | var copyText = text("复制链接").findOnce();
294 | if (!copyText)
295 | {
296 | console.log("无法找到三点菜单中的复制链接按钮,文章标题: " + articleTitle);
297 | back();
298 | sleep(timeWaitForBack);
299 | back();
300 | sleep(timeWaitForBack);
301 | return "";
302 | }
303 | var copyClickElem = getTheToppestClickableElem(copyText);
304 | copyClickElem.click();
305 | link = getClip();
306 | console.log(articleTitle + " ==> " + link);
307 | sleep(timeBeforeBack);
308 | back();
309 | return link;
310 | }
311 |
312 | function goToChatTab()
313 | {
314 | var textElems = text("微信").find();
315 | console.log("微信 textElems.length = " + String(textElems.length));
316 | var wxTextElem = null;
317 | if (textElems.length == 1)
318 | {
319 | wxTextElem = textElems[0];
320 | }
321 | else{
322 | for (var i = 0; i< textElems.length; i++)
323 | {
324 | //console.log(textElems[i].id());
325 | if (textElems[i].id().indexOf("f2s") >= 0)
326 | {
327 | wxTextElem = textElems[i];
328 | break;
329 | }
330 | }
331 | }
332 | if (!wxTextElem)
333 | {
334 | console.log("没有找到'微信'标签");
335 | return false;
336 | }
337 | var tabElem = getTheToppestClickableElem(wxTextElem);
338 | if (!tabElem)
339 | {
340 | console.log("没有找到带'微信'可以点击的元素");
341 | return false;
342 | }
343 | tabElem.click();
344 | return true;
345 | }
346 |
347 | function hasNewMessage()
348 | {
349 | var imgViews = selector().className("ImageView").find();
350 | //console.log("红点 imgViews.length = " + String(imgViews.length));
351 | for (var i=0; i < imgViews.length; i++)
352 | {
353 | //console.log(imgViews[i].id());
354 | if (imgViews[i].id() && imgViews[i].id().indexOf("a2f") >= 0)
355 | {
356 | console.log("有新消息");
357 | return true;
358 | }
359 | }
360 | console.log("没有新消息");
361 | return false;
362 | }
363 |
364 |
365 |
366 | function goToSubscriberAccountSession()
367 | {
368 | var textElem = text("订阅号消息").findOnce();
369 | if (!textElem)
370 | {
371 | return false;
372 | }
373 | var a = getTheToppestClickableElem(textElem);
374 | //console.log(a);
375 | a.click();
376 | sleep(longWait); // wait for subscriber page update
377 | return true;
378 | }
379 |
380 | function getTheToppestClickableElem(startElem)
381 | {
382 | if (!startElem)
383 | {
384 | return startElem;
385 | }
386 | var retElem = startElem;
387 | while (!retElem.clickable())
388 | {
389 | retElem = retElem.parent();
390 | }
391 | return retElem;
392 | }
393 | }
394 |
395 | toast("开始执行脚本");
396 | console.log("开始执行脚本")
397 | main();
398 | console.log("脚本执行完成")
399 | toast("脚本执行完成");
400 | //toast("开始运行脚本");
401 | //engines.execScript("Wechat2Rss", "main();\n" + main.toString(),{
402 | // loopTimes: 0,
403 | // interval: 1800000
404 | //});
405 |
406 |
--------------------------------------------------------------------------------