├── logs └── .gitkeep ├── .gitignore ├── models ├── index.js ├── sequelize.js └── weibo.js ├── Dockerfile ├── index.js ├── routes ├── config.js ├── all.js ├── get.js └── negative.js ├── tools ├── logger.js └── redis.js ├── package.json └── README.md /logs/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | npm-debug.log 4 | Weibo2RSS.log* 5 | -------------------------------------------------------------------------------- /models/index.js: -------------------------------------------------------------------------------- 1 | var Sequelize = require('sequelize'); 2 | var sequelize = require('./sequelize').sequelize(); 3 | var Weibo = sequelize.import('./weibo'); 4 | 5 | sequelize.sync(); 6 | 7 | exports.Weibo = Weibo; 8 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:4.4-onbuild 2 | EXPOSE 1207 3 | RUN npm install -g forever 4 | RUN echo "Asia/Shanghai" > /etc/timezone 5 | RUN dpkg-reconfigure -f noninteractive tzdata 6 | ENTRYPOINT forever --spinSleepTime 1000 --minUptime 1000 index.js -------------------------------------------------------------------------------- /models/sequelize.js: -------------------------------------------------------------------------------- 1 | var Sequelize = require('sequelize'); 2 | var SQL_USER = require('../routes/config').SQL_USER; 3 | var SQL_PASSWORD = require('../routes/config').SQL_PASSWORD; 4 | 5 | exports.sequelize = function () { 6 | return new Sequelize('weibo', SQL_USER, SQL_PASSWORD, {'dialect': 'mysql',host: 'localhost', port:3306}); 7 | } 8 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var logger = require('./tools/logger'); 3 | 4 | logger.info(`🍻 Weibo2RSS start! Cheers!`); 5 | 6 | var app = express(); 7 | app.all('*', require('./routes/all')); 8 | app.get('/rss/:uid', require('./routes/get')); 9 | app.get('/negative/:uid', require('./routes/negative')); 10 | app.listen(1206); -------------------------------------------------------------------------------- /routes/config.js: -------------------------------------------------------------------------------- 1 | const SQL_USER = 'root'; 2 | const SQL_PASSWORD = ''; 3 | const EMAIL_USER = 'gx-deng@163.com'; 4 | const EMAIL_PASSWORD = ''; 5 | const EMAIL_HOST = 'smtp.163.com'; 6 | 7 | exports.SQL_USER = SQL_USER; 8 | exports.SQL_PASSWORD = SQL_PASSWORD; 9 | exports.EMAIL_USER = EMAIL_USER; 10 | exports.EMAIL_PASSWORD = EMAIL_PASSWORD; 11 | exports.EMAIL_HOST = EMAIL_HOST; 12 | -------------------------------------------------------------------------------- /tools/logger.js: -------------------------------------------------------------------------------- 1 | var log4js = require('log4js'); 2 | log4js.configure({ 3 | appenders: [ 4 | { 5 | type: "file", 6 | filename: 'logs/Weibo2RSS.log', 7 | maxLogSize: 20480, 8 | backups: 3, 9 | category: [ 'Weibo2RSS','console' ] 10 | }, 11 | { 12 | type: "console" 13 | } 14 | ], 15 | replaceConsole: true 16 | }); 17 | var logger = log4js.getLogger('Weibo2RSS'); 18 | logger.setLevel('INFO'); 19 | 20 | module.exports = logger; -------------------------------------------------------------------------------- /routes/all.js: -------------------------------------------------------------------------------- 1 | var https = require('https'); 2 | 3 | module.exports = function (req, res, next) { 4 | https.get(`https://api.prprpr.me/count/?id=DIYgod-Weibo2RSS&action=add`); 5 | res.header('Access-Control-Allow-Origin', '*'); 6 | res.header('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild'); 7 | res.header('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS'); 8 | 9 | if (req.method == 'OPTIONS') { 10 | res.send(200); 11 | } 12 | else { 13 | next(); 14 | } 15 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Weibo2RSS", 3 | "version": "0.0.1", 4 | "description": "使用RSS订阅喜欢的微博博主,更新微博邮件提醒,并使用咕咕机打印。", 5 | "main": "index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/airingursb/Weibo2RSS.git" 9 | }, 10 | "keywords": [ 11 | "Weibo", 12 | "RSS" 13 | ], 14 | "author": "DIYGod", 15 | "license": "SATA", 16 | "devDependencies": {}, 17 | "dependencies": { 18 | "cheerio": "^0.22.0", 19 | "emailjs": "^1.0.10", 20 | "express": "^4.14.0", 21 | "log4js": "^0.6.38", 22 | "mysql": "^2.13.0", 23 | "mysql2": "^1.3.6", 24 | "node-fetch": "^1.6.3", 25 | "redis": "^2.6.3", 26 | "sequelize": "^4.3.2" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tools/redis.js: -------------------------------------------------------------------------------- 1 | var logger = require('./logger'); 2 | var redis = require("redis"); 3 | var client; 4 | if (process.env.REDIS_PORT_6379_TCP_ADDR && process.env.REDIS_PORT_6379_TCP_PORT && process.env.REDIS_PASSWORD) { 5 | client = redis.createClient({ 6 | host: process.env.REDIS_PORT_6379_TCP_ADDR, 7 | port: process.env.REDIS_PORT_6379_TCP_PORT, 8 | password: process.env.REDIS_PASSWORD 9 | }); 10 | } 11 | else { 12 | client = redis.createClient(); 13 | } 14 | 15 | 16 | client.on("error", function (err) { 17 | logger.error('Redis Error ' + err); 18 | }); 19 | 20 | module.exports = { 21 | set: function (key, value) { 22 | client.set(key, value, redis.print); 23 | client.expire(key, 300); 24 | logger.info('Set redis: ' + key); 25 | }, 26 | client: client 27 | }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Weibo2RSS 2 | 3 | > 使用RSS订阅喜欢的微博博主,更新微博邮件提醒,并使用咕咕机打印。 4 | 5 | ## Install 6 | 7 | 硬件环境:RaspberryPi B+,MEMOBIRD 2 8 | 软件环境:Nodejs,MySQL 9 | 10 | 请在 `routes/config.js` 下填写您的数据库配置与邮箱配置。 11 | 12 | ## 介绍 13 | 14 | Demo:https://api.prprpr.me/weibo/rss/3306934123 15 | 16 | RSS 格式输出一个微博博主最新的 15 条微博,可以使用 RSS 阅读器来获取及时推送,配合 [IFTTT](https://ifttt.com/) 还可以实现更多好玩的功能。 17 | 18 | Demo:https://api.prprpr.me/weibo/negative/3306934123 19 | 20 | 使用 [Text2Emotion](https://github.com/DIYgod/Text2Emotion) 实现,仅输出消极情绪的微博,用来监控博主的消极情绪。 21 | 22 | ## 使用 23 | 24 | 使用 RSS 阅读器订阅:https://api.prprpr.me/weibo/rss/{微博博主的uid} 25 | 26 | 获取uid:进入博主的微博主页,控制台执行 27 | ```js 28 | /uid=(\d+)/. exec(document.querySelector('.opt_box .btn_bed').getAttribute('action-data'))[1] 29 | ``` 30 | 31 | ## 搭建 32 | 33 | 需要环境:Node.js 34 | 35 | 推荐使用 Docker 36 | 37 | ## LICENSE 38 | 39 | MIT © [DIYgod](http://github.com/DIYgod) 40 | 41 | MIT © [Airing](http://github.com/airingursb) -------------------------------------------------------------------------------- /models/weibo.js: -------------------------------------------------------------------------------- 1 | module.exports = function (sequelize, DataTypes) { 2 | return sequelize.define( 3 | 'weibo', 4 | { 5 | 'uid': { 6 | 'type': DataTypes.INTEGER, 7 | 'allowNull': false 8 | }, 9 | 'home': { 10 | 'type': DataTypes.STRING(125), 11 | 'allowNull': false 12 | }, 13 | 'name': { 14 | 'type': DataTypes.STRING(125), 15 | 'allowNull': true 16 | }, 17 | 'emotion': { 18 | 'type': DataTypes.DOUBLE, 19 | 'allowNull': true 20 | }, 21 | 'title': { 22 | 'type': DataTypes.TEXT, 23 | 'allowNull': true 24 | }, 25 | 'description': { 26 | 'type': DataTypes.TEXT, 27 | 'allowNull': true 28 | }, 29 | 'pubDate': { 30 | 'type': DataTypes.STRING(125), 31 | 'allowNull': true 32 | }, 33 | 'link': { 34 | 'type': DataTypes.STRING(125), 35 | 'allowNull': true 36 | } 37 | } 38 | ); 39 | } -------------------------------------------------------------------------------- /routes/get.js: -------------------------------------------------------------------------------- 1 | var fetch = require('node-fetch'); 2 | var cheerio = require('cheerio'); 3 | var url = require('url'); 4 | var logger = require('../tools/logger'); 5 | // var redis = require('../tools/redis'); 6 | 7 | function getTime (html) { 8 | var math; 9 | var date = new Date(); 10 | if (math = /(\d+)分钟前/.exec(html)) { 11 | date.setMinutes(date.getMinutes() - math[1]); 12 | return date.toUTCString(); 13 | } 14 | else if (math = /今天 (\d+):(\d+)/.exec(html)) { 15 | date = new Date(date.getFullYear(), date.getMonth(), date.getDate(), math[1], math[2]); 16 | return date.toUTCString(); 17 | } 18 | else if (math = /(\d+)月(\d+)日 (\d+):(\d+)/.exec(html)) { 19 | date = new Date(date.getFullYear(), math[1] - 1, parseInt(math[2]), math[3], math[4]); 20 | return date.toUTCString(); 21 | } 22 | return html; 23 | } 24 | 25 | module.exports = function (req, res) { 26 | res.header('Content-Type', 'application/xml; charset=utf-8'); 27 | 28 | var ip = req.headers['x-forwarded-for'] || 29 | req.connection.remoteAddress || 30 | req.socket.remoteAddress || 31 | req.connection.socket.remoteAddress; 32 | 33 | var query = url.parse(req.url,true).query; 34 | var debug = query.debug; 35 | 36 | var uid = req.params.uid; 37 | 38 | logger.info(`Weibo2RSS uid ${uid} form origin, IP: ${ip}`); 39 | 40 | fetch(`http://service.weibo.com/widget/widget_blog.php?uid=${uid}`).then( 41 | response => response.text() 42 | ).then((data) => { 43 | var $ = cheerio.load(data, { 44 | decodeEntities: false 45 | }); 46 | var wbs = []; 47 | var items = $('.wgtCell'); 48 | var wb, item, titleEle; 49 | items.map(function (index, ele) { 50 | wb = {}; 51 | item = $(this); 52 | titleEle = item.find('.wgtCell_txt'); 53 | wb.title = titleEle.text().replace(/^\s+|\s+$/g, ''); 54 | if (wb.title.length > 24) { 55 | wb.title = wb.title.slice(0, 24) + '...'; 56 | } 57 | wb.description = titleEle.html().replace(/^\s+|\s+$/g, '').replace(/thumbnail/, 'large'); 58 | wb.pubDate = getTime(item.find('.link_d').html()); 59 | wb.link = item.find('.wgtCell_tm a').attr('href'); 60 | wbs.push(wb); 61 | }); 62 | var name = $('.userNm').text(); 63 | 64 | var rss = 65 | ` 66 | 67 | 68 | ${name}的微博 69 | http://weibo.com/${uid}/ 70 | ${name}的微博RSS,使用 Weibo2RSS(https://github.com/DIYgod/Weibo2RSS) 构建 71 | zh-cn 72 | ${new Date().toUTCString()} 73 | 300` 74 | for (var i = 0; i < wbs.length; i++) { 75 | rss +=` 76 | 77 | <![CDATA[${wbs[i].title}]]> 78 | 79 | ${wbs[i].pubDate} 80 | ${wbs[i].link} 81 | ${wbs[i].link} 82 | ` 83 | } 84 | rss += ` 85 | 86 | ` 87 | res.send(rss); 88 | } 89 | ).catch( 90 | e => logger.error("Weibo2RSS Error: getting widget", e) 91 | ); 92 | }; -------------------------------------------------------------------------------- /routes/negative.js: -------------------------------------------------------------------------------- 1 | var fetch = require('node-fetch'); 2 | var cheerio = require('cheerio'); 3 | var url = require('url'); 4 | var logger = require('../tools/logger'); 5 | var WeiboModel = require('../models/index').Weibo; 6 | var email = require('emailjs/email'); 7 | var EMAIL_USER = require('./config').EMAIL_USER; 8 | var EMAIL_PASSWORD = require('./config').EMAIL_PASSWORD; 9 | var EMAIL_HOST = require('./config').EMAIL_HOST; 10 | 11 | var server = email.server.connect({ 12 | user: EMAIL_USER, 13 | password: EMAIL_PASSWORD, 14 | host: EMAIL_HOST, 15 | ssl: true 16 | }); 17 | 18 | function getTime (html) { 19 | var math; 20 | var date = new Date(); 21 | if (math = /(\d+)分钟前/.exec(html)) { 22 | date.setMinutes(date.getMinutes() - math[1]); 23 | return date.toUTCString(); 24 | } 25 | else if (math = /今天 (\d+):(\d+)/.exec(html)) { 26 | date = new Date(date.getFullYear(), date.getMonth(), date.getDate(), math[1], math[2]); 27 | return date.toUTCString(); 28 | } 29 | else if (math = /(\d+)月(\d+)日 (\d+):(\d+)/.exec(html)) { 30 | date = new Date(date.getFullYear(), math[1] - 1, parseInt(math[2]), math[3], math[4]); 31 | return date.toUTCString(); 32 | } 33 | return html; 34 | } 35 | 36 | module.exports = function (req, res) { 37 | res.header('Content-Type', 'application/xml; charset=utf-8'); 38 | 39 | var ip = req.headers['x-forwarded-for'] || 40 | req.connection.remoteAddress || 41 | req.socket.remoteAddress || 42 | req.connection.socket.remoteAddress; 43 | 44 | var query = url.parse(req.url,true).query; 45 | var debug = query.debug; 46 | 47 | var uid = req.params.uid; 48 | 49 | logger.info(`Weibo2RSS negative uid ${uid} form origin, IP: ${ip}`); 50 | 51 | fetch(`http://service.weibo.com/widget/widget_blog.php?uid=${uid}`).then( 52 | response => response.text() 53 | ).then((data) => { 54 | var $ = cheerio.load(data, { 55 | decodeEntities: false 56 | }); 57 | var wbs = []; 58 | var items = $('.wgtCell'); 59 | var name = $('.userNm').text(); 60 | var wb, item, titleEle; 61 | var emotionCound = 0; 62 | 63 | items.map(function (index, ele) { 64 | wb = {}; 65 | item = $(this); 66 | titleEle = item.find('.wgtCell_txt'); 67 | wb.title = titleEle.text().replace(/^\s+|\s+$/g, ''); 68 | if (wb.title.length > 24) { 69 | wb.title = wb.title.slice(0, 24) + '...'; 70 | } 71 | wb.description = titleEle.html().replace(/^\s+|\s+$/g, '').replace(/thumbnail/, 'large'); 72 | wb.pubDate = getTime(item.find('.link_d').html()); 73 | wb.link = item.find('.wgtCell_tm a').attr('href'); 74 | wbs.push(wb); 75 | }); 76 | for (let i = 0; i < wbs.length; i++) { 77 | fetch(`https://api.prprpr.me/emotion/wenzhi?password=DIYgod&text=${encodeURIComponent(wbs[i].description)}`).then( 78 | response => response.json() 79 | ).then((data) => { 80 | emotionCound++; 81 | wbs[i].emotion = data.positive - data.negative; 82 | 83 | if (emotionCound === wbs.length) { 84 | wbs.forEach(function(wb) { 85 | WeiboModel.find({ 86 | where: { 87 | link: wb.link 88 | } 89 | }).then(function(result) { 90 | console.log(result) 91 | if (result == null) { 92 | wb.uid = uid; 93 | wb.home = 'http://weibo.com/' + uid; 94 | wb.name = name; 95 | WeiboModel.create(wb).then(function() { 96 | var subject = wb.name + '发布了新微博,情绪值为:' + (wb.emotion * 100).toFixed(4) + '%,快去看看吧!' 97 | server.send({ 98 | text: wb.description, 99 | from: "gx-deng ", 100 | to: "airing <361411192@qq.com>", 101 | subject: subject 102 | }, function(err, message) { console.log(err || message); }); 103 | }); 104 | } 105 | }); 106 | }); 107 | var rss = 108 | ` 109 | 110 | 111 | ${name}的情绪微博 112 | http://weibo.com/${uid}/ 113 | ${name}的消极情绪微博RSS,使用 Weibo2RSS(https://github.com/DIYgod/Weibo2RSS) 构建 114 | zh-cn 115 | ${new Date().toUTCString()} 116 | 300` 117 | for (let j = 0; j < wbs.length; j++) { 118 | if (wbs[j].emotion) { 119 | rss +=` 120 | 121 | <![CDATA[你关注的博主@${name} 发布了情绪值为${(wbs[j].emotion * 100).toFixed(4)}%的疑似消极情绪微博,快去关心一下吧:「${wbs[j].title}」]]> 122 | 123 | ${wbs[j].pubDate} 124 | ${wbs[j].link} 125 | ${wbs[j].link} 126 | ` 127 | 128 | } 129 | } 130 | rss += ` 131 | 132 | ` 133 | res.send(rss); 134 | } 135 | }).catch( 136 | e => logger.error("Weibo2RSS negative Error: getting emotion", e) 137 | ); 138 | } 139 | } 140 | ).catch( 141 | e => logger.error("Weibo2RSS negative Error: getting widget", e) 142 | ); 143 | }; --------------------------------------------------------------------------------