├── 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 |
--------------------------------------------------------------------------------