├── FlibustaBot.js ├── README.md ├── ecosystem.config.js ├── package.json └── ru.templang /FlibustaBot.js: -------------------------------------------------------------------------------- 1 | 2 | const http = require('http'); 3 | const VK = require('VK-Promise'); 4 | const tor = require('tor-request'); 5 | const TempLang = require('templang'); 6 | 7 | const config = process.env; 8 | const vk = new VK(config.ACCESS_TOKEN); 9 | const lang = TempLang.fromFile(config.LANG_FILE); 10 | const callback = vk.init_callback_api(config.CALLBACK_API_CONFIRMATION_TOKEN); 11 | const FLIBUSTA_HOST = config.FLIBUSTA_HOST; 12 | const PROJECT_HOST = config.PROJECT_HOST 13 | const PROJECT_LINK = config.PROJECT_LINK 14 | const CALLBACK_API_PATH = config.CALLBACK_API_PATH; 15 | const port = config.PORT; 16 | 17 | const text = lang.static; 18 | const links = {}; 19 | const commands = [{ 20 | r: /^(epub|pdf|fb2|mobi|djvu) ([0-9]+)$/i, 21 | f: (msg, format, id) => { 22 | var link = "/" + Math.random().toString(16).substr(2) + 23 | Math.random().toString(16).substr(2); 24 | links[link] = FLIBUSTA_HOST + "/b/" + id + "/" + format.toLowerCase(); 25 | msg.send(lang.try('download', { link, PROJECT_HOST })); 26 | } 27 | }, { 28 | r: /^(справка|привет|бот|\?|help)$/i, 29 | f: (msg) => { 30 | msg.send(text.help); 31 | } 32 | }, { 33 | r: /^(спасибо|спс)/i, 34 | f: (msg) => { 35 | msg.send(text.sps); 36 | } 37 | }, { 38 | r: /^найти (?:(.+)\s-\s)?(.+?)$/i, 39 | f: (msg, autor, name) => { 40 | var url = FLIBUSTA_HOST + '/makebooklist?ab=ab1&t=' + encodeURI(name) + '&ln=' + encodeURI(autor || '') + '&sort=sd2'; 41 | search(url).then((books) => { 42 | return books.map((book) => { 43 | book.formats = book.formats.join(', '); 44 | return lang.try('book', book); 45 | }); 46 | }).then((books) => { 47 | if (!books.length) { 48 | return text.books_404; 49 | } 50 | 51 | return books.join('\n\n') + '\n\n' + text.download_help; 52 | }).catch((e) => { 53 | console.error(e); 54 | return text.error; 55 | }).then(msg.send); 56 | } 57 | }]; 58 | 59 | vk.on('message', (event, msg) => { 60 | event.ok(); 61 | if (msg.out) return; 62 | 63 | commands.forEach((command) => { 64 | if (!command.r.test(msg.body) || msg.ok) return; 65 | msg.ok = true; 66 | var args = msg.body.match(command.r) || []; 67 | args[0] = msg; 68 | command.f.apply(null, args); 69 | }); 70 | 71 | if (msg.ok !== true) { 72 | return msg.send(text.command_404); 73 | } 74 | }); 75 | 76 | http.Server(function onRequest(req, res) { 77 | if (req.url == CALLBACK_API_PATH) { 78 | return callback(req, res); 79 | } 80 | 81 | if (!links[req.url]) { 82 | res.writeHead(302, { 83 | Location: PROJECT_LINK 84 | }); 85 | return res.end(); 86 | } 87 | 88 | const download_req = tor.request({ 89 | url: links[req.url], 90 | encoding: null 91 | }).on('response', (response) => { 92 | var bad_status_code = response.statusCode !== 200; 93 | var bad_content_type = response.headers['content-type'] == 'text/html; charset=utf-8'; 94 | var bad_disposition = !response.headers['content-disposition']; 95 | 96 | if (bad_status_code || bad_content_type || bad_disposition) { 97 | res.end(text.download_page_error); 98 | download_req.end(); 99 | return; 100 | } 101 | 102 | res.writeHead(200, response.headers); 103 | download_req.pipe(res); 104 | }).on('error', (e) => { 105 | console.error(e); 106 | if (!res.finished) { 107 | res.end(text.download_page_server_error); 108 | } 109 | }); 110 | }).listen(port, function onListen() { 111 | console.info('listening on *:', port); 112 | }); 113 | 114 | 115 | process.on('uncaughtException', function onUncaughtException(e) { 116 | console.error('uncaughtException', e.stack); 117 | }); 118 | 119 | process.on('unhandledRejection', (err, p) => { 120 | console.error('unhandledRejection', { 121 | error: err, 122 | promis: p, 123 | stack: err.stack || (new Error().stack) 124 | }); 125 | }); 126 | 127 | 128 | function parseSearch(error, response, body) { 129 | if (error) { 130 | return { 131 | error: error 132 | }; 133 | } 134 | 135 | if (body.indexOf('class="genre"') == -1) { 136 | return []; 137 | } 138 | 139 | const books = []; 140 | body.split(/<.+?class="genre".+?>/).splice(0, 10).forEach((genre) => { 141 | genre = genre.split('

'); 142 | 143 | if (genre.length == 1) { 144 | return; 145 | } 146 | 147 | return genre[1].split('
').forEach((book_raw) => { 148 | const book = {}; 149 | 150 | book.id = book_raw.match(/href="\/b\/(.+?)">/); 151 | book.name = book_raw.match(/(.+?)<\/a>/) || [0, '']; 152 | book.author = book_raw.match(/(.+?)<\/a>/) || [0, '']; 153 | 154 | if (!book.id) return ''; 155 | 156 | var formats = (book_raw.match(/\/(epub|pdf|fb2|mobi|djvu)/g) || []).map((x) => x.replace('/', '')); 157 | 158 | books.push({ 159 | id: book.id[1], 160 | name: book.name[1], 161 | author: book.author[1], 162 | formats: formats 163 | }); 164 | }); 165 | }); 166 | 167 | return books; 168 | } 169 | 170 | function search(url) { 171 | return new Promise((resolve, reject) => { 172 | tor.request(url, (error, response, body) => { 173 | const res = parseSearch(error, response, body); 174 | if (res.error) { 175 | reject(res.error); 176 | } else { 177 | resolve(res); 178 | } 179 | 180 | body = null; 181 | error = null; 182 | response = null; 183 | }); 184 | }); 185 | } 186 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FlibustaBot 2 | 3 | Бот ВКонтакте позволяющий искать и скачивать книги на сайте проекта Flibusta. 4 | По умолчанию использует домен: http://flibustahezeous3.onion 5 | 6 | ## Запуск 7 | ``` 8 | # https://www.npmjs.com/package/tor-request 9 | apt-get install tor 10 | /usr/bin/tor --RunAsDaemon 1 11 | npm install tor-request VK-Promise templang 12 | # https://www.npmjs.com/package/pm2 13 | npm install pm2 -g 14 | # редактируете ecosystem.config.js на ваш вкус 15 | pm2 start ecosystem.config.js 16 | # далее настраиваете Callback API на PROJECT_HOST/callback_api 17 | ``` 18 | 19 | ## Зависимости 20 | - tor - для поднятия tor прокси сервера и общения с сайтом flibusta 21 | - nodejs - на нем работает бот 22 | - npm 23 | - tor-request - позволяет делать запросы в tor 24 | - VK-Promise - для общения с API ВКонтакте 25 | - templang - для парсинга templang переводов 26 | - pm2 - демон для nodejs (можно и без него обойтись) 27 | -------------------------------------------------------------------------------- /ecosystem.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | apps : [ 3 | { 4 | name: "FlibustaBot", 5 | script: "./FlibustaBot.js", 6 | env: { 7 | "LANG_FILE": "./ru.templang", 8 | "CALLBACK_API_CONFIRMATION_TOKEN": "aeece8b7", 9 | "CALLBACK_API_PATH": "/callback_api/", 10 | "FLIBUSTA_HOST": "http://flibustahezeous3.onion", 11 | "PROJECT_HOST": "http://fb.flyink.ru", 12 | "ACCESS_TOKEN": "", 13 | "PROJECT_LINK": "https://vk.me/f_bot", 14 | "PORT": 80 15 | } 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "flibusta_bot", 3 | "version": "1.0.0", 4 | "description": "Бот ВКонтакте позволяющий искать книги на сайте проекта Flibusta.", 5 | "main": "FlibustaBot.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/FlyInk13/FlibustaBot.git" 12 | }, 13 | "keywords": [ 14 | "Flibusta", 15 | "bot", 16 | "vk", 17 | "api", 18 | "tor" 19 | ], 20 | "dependencies": { 21 | "VK-Promise": "^0.5.1", 22 | "hipc": "*", 23 | "templang": "*", 24 | "tor-request": "*" 25 | }, 26 | "devDependencies": { 27 | "pm2": "*" 28 | }, 29 | "author": "Flyink13", 30 | "license": "ISC", 31 | "bugs": { 32 | "url": "https://github.com/FlyInk13/FlibustaBot/issues" 33 | }, 34 | "homepage": "https://github.com/FlyInk13/FlibustaBot#readme" 35 | } 36 | -------------------------------------------------------------------------------- /ru.templang: -------------------------------------------------------------------------------- 1 | @ Date 2 | @ static 3 | 4 | Date_DateTimeFormat: D JG Y H:i:s (UTC+0) 5 | Date_MountNamesGen 6 | января, февраля, марта, апреля, мая, июня, июля, августа, сентября, октября, ноября, декабря 7 | 8 | download 9 | Ваша одноразовая ссылка для скачивания: {PROJECT_HOST}{link} 10 | Если появилась ошибка, то попробуйте другой формат или другой ID. 11 | Если у вас iOS и вы видите белый экран, то скопируйте ссылку и откройте ее в браузере. 12 | book 13 | {name} 14 | Автор: {author} 15 | ID: {id} 16 | Доступные форматы: {formats} 17 | 18 | 19 | static_help 20 | Бот позволяет скачивать книги. 21 | Для поиска книг введите слово найти и название книги. 22 | Если необходимо найти определенного автора то сначала пишите его фамилию, а потом тире и назавние книги 23 | Для скачивания книги пишите формат и ее номер. 24 | \n 25 | Примеры команд: 26 | Найти Цветы для Элджернона 27 | найти Паустовский - Телеграмма 28 | epub 96290 29 | 30 | static_command_404 31 | Прости, я тебя не понял, для поиска книги напиши найти и название книги. 32 | 33 | static_sps 34 | Не за что ;-) 35 | 36 | static_books_404 37 | Не нашлось ни единой книги, удовлетворяющей вашим требованиям 38 | 39 | static_download_help 40 | Для скачивания введите её формат и id. 41 | Например: epub 12345. 42 | 43 | static_error 44 | Произошла ошибка 45 | 46 | static_download_page_error 47 | 48 | 49 | Книга не найдена. 50 | 51 | 52 | 53 | 54 | 55 | 56 |
57 | Ошибка! 404 Not Found.
58 | К сожалению, запрашиваемая Вами книга не найдена. 59 |
60 | 61 | 62 | static_download_page_server_error 63 | 64 | 65 | Произошла ошибка. 66 | 67 | 68 | 69 | 70 | 71 | 72 |
73 | Ошибка! 500 Not Found.
74 | К сожалению, произошла ошибка. 75 |
76 | 77 | --------------------------------------------------------------------------------