├── 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 | ![扩展思路](images/future_solution.jpg) 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 | donate 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 | --------------------------------------------------------------------------------