diff --git a/Procfile b/Procfile
index f2d1f0b..9ebe8e8 100644
--- a/Procfile
+++ b/Procfile
@@ -1 +1 @@
-worker: node index.js
+worker: npm start
diff --git a/README.md b/README.md
index 460aca1..9c8d671 100644
--- a/README.md
+++ b/README.md
@@ -6,6 +6,8 @@
Перед началом работы настройте config.json!
+
+ Скрипт запускается командой npm start!
![CodeFactor](https://www.codefactor.io/repository/github/mrzillagold/vk2discord/badge)
@@ -19,7 +21,11 @@
"longpoll": false // Использовать Longpoll API. true = Вкл. / false = Выкл.
},
"discord": {
- "webhook_url": "https://discordapp.com/api/webhooks/", // Ваш WebHook URL.
+ "webhook_urls": [
+ "https://discordapp.com/api/webhooks/",
+ "https://discordapp.com/api/webhooks/",
+ ...
+ ], // Ссылки на Webhook, можно использовать несколько ссылок на разные каналы Discord.
"bot_name": "VK2DISCORD", // Имя вашего WebHook, выcвечиваетеся в качестве имени бота.
"color": "#aabbcc" // Цвет рамки сообщения Discord в формате HEX.
},
diff --git a/config.json b/config.json
index 24916a3..df5054c 100644
--- a/config.json
+++ b/config.json
@@ -7,9 +7,12 @@
"longpoll": false
},
"discord": {
- "webhook_url": "https://discordapp.com/api/webhooks/",
+ "webhook_urls": [
+ "https://discordapp.com/api/webhooks/",
+ "https://discordapp.com/api/webhooks/"
+ ],
"bot_name": "VK2DISCORD",
"color": "#aabbcc"
},
"interval": 30
-}
+}
\ No newline at end of file
diff --git a/index.js b/index.js
deleted file mode 100644
index 885e4d7..0000000
--- a/index.js
+++ /dev/null
@@ -1 +0,0 @@
-require("./modules/handler");
\ No newline at end of file
diff --git a/index.mjs b/index.mjs
new file mode 100644
index 0000000..7b1634f
--- /dev/null
+++ b/index.mjs
@@ -0,0 +1 @@
+import "./modules/handler";
\ No newline at end of file
diff --git a/modules/discord.mjs b/modules/discord.mjs
new file mode 100644
index 0000000..788f2fa
--- /dev/null
+++ b/modules/discord.mjs
@@ -0,0 +1,16 @@
+import webhook from "webhook-discord";
+
+import config from "../config";
+
+const name = config.discord.bot_name.slice(0, 32);
+const color = config.discord.color.match(/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/m) ? config.discord.color : "#aabbcc";
+const urls = config.discord.webhook_urls;
+/*const sendAll = config.discord.send_all;*/
+
+export {
+ webhook,
+ name,
+ color,
+ urls,
+ /*sendAll*/
+};
\ No newline at end of file
diff --git a/modules/functions.js b/modules/functions.js
deleted file mode 100644
index 1b24332..0000000
--- a/modules/functions.js
+++ /dev/null
@@ -1,60 +0,0 @@
-function parseLinks(text) {
- return `${text.replace(/(?:\[([^]+?)\|([^]+?)])/g, "[$2](https://vk.com/$1)")}\n\n`;
-}
-
-function checkKeywords(keywords, text) {
- if (keywords.length > 0) {
- return keywords.some(keyword => {
- return text.match(keyword, "gi");
- });
- } else {
- return true;
- }
-}
-
-async function getAttachments(attachments, webhookBuilder, longpoll) {
- let text = "";
-
- await attachments.forEach(item => {
- const type = item.type;
-
- switch (type) {
- case "photo":
- if (!webhookBuilder.data.attachments[0].image_url) webhookBuilder.setImage((longpoll ? item.sizes : item.photo.sizes).pop().url);
- break;
- case "video":
- text += `\n[:video_camera: Смотреть видео: ${(longpoll ? item : item.video).title}](https://vk.com/video${longpoll ? item.ownerId : item.video.owner_id}_${(longpoll ? item : item.video).id})`;
- break;
- case "link":
- text += `\n[:link: ${(longpoll ? item : item.link).button_text || "Ссылка"}: ${(longpoll ? item : item.link).title}](${(longpoll ? item : item.link).url})`;
- break;
- case "doc":
- text += `\n[:page_facing_up: Документ: ${(longpoll ? item : item.doc).title}](${(longpoll ? item : item.doc).url})`;
- break;
- case "audio":
- const artist = (longpoll ? item : item.audio).artist;
- const title = (longpoll ? item : item.audio).title;
-
- text += `\n[:musical_note: Музыка: ${artist} - ${title}](https://vk.com/search?c[section]=audio&c[q]=${encodeURI(artist.replace(/&/g, "и"))}%20-%20${encodeURI(title)}&c[performer]=1)`;
- break;
- case "poll":
- let answers = "";
-
- (longpoll ? item : item.poll).answers.forEach(item => answers += `\n• ${item.text}`);
- text += `\n[:bar_chart: Опрос: ${(longpoll ? item : item.poll).question}](https://vk.com/feed?w=poll${longpoll ? item.ownerId : item.poll.owner_id}_${(longpoll ? item : item.poll).id})`;
- break;
- }
- });
- return text;
-}
-
-function errorHandler(error) {
- console.log(`[!] Возникла ошибка: ${error}. Если не понимаете в чем причина, свяжитесь со мной: https://vk.com/id233731786`);
-}
-
-module.exports = {
- parseLinks,
- checkKeywords,
- getAttachments,
- errorHandler
-};
diff --git a/modules/handler.js b/modules/handler.js
deleted file mode 100644
index 01dd6d1..0000000
--- a/modules/handler.js
+++ /dev/null
@@ -1,75 +0,0 @@
-const { VK } = require("vk-io");
-const config = require("../config");
-const webhook = require("webhook-discord");
-
-const vk = new VK();
-const { updates, api } = vk;
-
-const token = config.vk.token;
-const longpoll = config.vk.longpoll;
-const groupId = config.vk.group_id;
-const interval = config.interval * 1000;
-
-const send = require("./send");
-
-const { errorHandler } = require("./functions");
-
-vk.setOptions({
- token,
- apiMode: "parallel"
-});
-
-
-if (!longpoll) {
- if (interval < 30000) console.log("[!] Не рекомендуем ставить интервал получения постов меньше 30 секунд, во избежания лимитов ВКонтакте!");
-
- setInterval(() => {
- const webhookBuilder = new webhook.MessageBuilder();
-
- const groupIdMatch = groupId.match(/^(?:public|group)([\d]+)$/);
- const userIdMatch = groupId.match(/^id([\d]+)$/);
- const id = groupIdMatch ? {owner_id: -groupIdMatch[1]} : userIdMatch ? {owner_id: userIdMatch[1]} : {domain: groupId};
-
- api.wall.get({
- ...id,
- count: 2,
- extended: 1,
- filter: config.vk.filter ? "owner" : "all",
- v: "5.103"
- })
- .then(data => {
-
- if (data.groups.length > 0 && groupIdMatch) {
- webhookBuilder.setFooter(data.groups[0].name, data.groups[0].photo_50);
- } else if (data.profiles.length > 0) {
- webhookBuilder.setFooter(`${data.profiles[0].first_name} ${data.profiles[0].last_name}`, data.profiles[0].photo_50);
- }
-
- const posts = data.items;
- const post1 = posts[0];
- const post2 = posts[1];
-
- if (posts.length > 0) {
- const postData = posts.length === 2 && post2.date > post1.date ? post2 : post1;
-
- send(webhookBuilder, postData, false);
- } else {
- console.log("[!] Не получено ни одной записи. Проверьте наличие записей в группе или измените значение фильтра в конфигурации.");
- }
-
- })
- .catch(err => errorHandler(err));
- }, interval);
-} else {
- updates.on("new_wall_post", context => {
- const webhookBuilder = new webhook.MessageBuilder();
-
- send(webhookBuilder, context.wall, true);
- });
-
- updates.start()
- .then(() => console.log("[Бот] Подключен к ВКонтакте!"))
- .catch(err => errorHandler(err));
-}
-
-console.log("[Бот] Запущен");
diff --git a/modules/handler.mjs b/modules/handler.mjs
new file mode 100644
index 0000000..710c22b
--- /dev/null
+++ b/modules/handler.mjs
@@ -0,0 +1,80 @@
+import { updates, api, longpoll, groupId, interval, filter } from "./vk";
+
+import { webhook } from "./discord";
+
+import { Sender } from "./sender";
+
+if (!longpoll) {
+ if (interval < 30000) console.log("[!] Не рекомендуем ставить интервал получения постов меньше 30 секунд, во избежания лимитов ВКонтакте!");
+
+ setInterval(() => {
+ const sender = new Sender();
+
+ const groupIdMatch = groupId.match(/^(?:public|group)([\d]+)$/);
+ const userIdMatch = groupId.match(/^id([\d]+)$/);
+ const id = groupIdMatch ?
+ {
+ owner_id: -groupIdMatch[1]
+ }
+ :
+ userIdMatch ?
+ {
+ owner_id: userIdMatch[1]
+ }
+ :
+ {
+ domain: groupId
+ };
+
+ api.wall.get({
+ ...id,
+ count: 2,
+ extended: 1,
+ filter: filter ? "owner" : "all",
+ v: "5.103"
+ })
+ .then(data => {
+ const builder = new webhook.MessageBuilder();
+
+ if (data.groups.length > 0 && groupIdMatch) { // Устанавливаем footer от типа отправителя записи
+ builder.setFooter(data.groups[0].name, data.groups[0].photo_50);
+ } else if (data.profiles.length > 0) {
+ builder.setFooter(`${data.profiles[0].first_name} ${data.profiles[0].last_name}`, data.profiles[0].photo_50);
+ }
+
+ const posts = data.items; // Проверяем наличие закрепа, если он есть берем свежую запись
+ const post1 = posts[0];
+ const post2 = posts[1];
+
+ if (posts.length > 0) {
+ const postData = posts.length === 2 && post2.date > post1.date ? post2 : post1;
+
+ sender.Post(builder, postData);
+ } else {
+ console.log("[!] Не получено ни одной записи. Проверьте наличие записей в группе или измените значение фильтра в конфигурации.");
+ }
+
+ })
+ .catch(err => errorHandler(err));
+ }, interval);
+} else {
+ updates.on("new_wall_post", context => {
+ const sender = new Sender();
+
+ sender.Post(new webhook.MessageBuilder(), context.wall)
+ });
+
+ updates.start()
+ .then(() => console.log("[VK2DISCORD] Подключен к ВКонтакте!"))
+ .catch(err => errorHandler(err));
+}
+
+console.log("[VK2DISCORD] Запущен.");
+
+function errorHandler(error) {
+ console.log(`[!] Возникла ошибка: ${error}. Если не понимаете в чем причина, свяжитесь со мной: https://vk.com/id233731786`);
+}
+
+export {
+ errorHandler
+};
diff --git a/modules/send.js b/modules/send.js
deleted file mode 100644
index e93fc92..0000000
--- a/modules/send.js
+++ /dev/null
@@ -1,75 +0,0 @@
-const fs = require("fs");
-const webhook = require("webhook-discord");
-
-const news = require("../news");
-const config = require("../config");
-
-const { getAttachments, parseLinks, checkKeywords, errorHandler } = require("./functions");
-
-const keywords = config.vk.keywords;
-const name = config.discord.bot_name;
-const color = config.discord.color.match(/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/m) ? config.discord.color : "#aabbcc";
-const url = config.discord.webhook_url;
-
-const discord = new webhook.Webhook(url);
-
-module.exports = async (webhookBuilder, postData, longpoll) => {
- webhookBuilder.setName(name.slice(0, 32))
- .setColor(color);
-
- const createdAt = longpoll ? postData.createdAt : postData.date;
-
- if (news.last_post !== createdAt && !(news.published_posts.includes(createdAt)) && checkKeywords(keywords, postData.text)) {
- if (longpoll && config.vk.filter && postData.authorId === postData.createdUserId) return;
-
- let postText = `[**Открыть пост ВКонтакте**](https://vk.com/wall${longpoll ? postData.authorId : postData.from_id}_${postData.id})\n\n`;
-
- if (postData.text) postText += parseLinks(postData.text);
-
- let attachments = "";
- if (postData.attachments) attachments += await getAttachments(postData.attachments, webhookBuilder, longpoll);
-
- const repost = longpoll ?
- postData.copyHistory ? postData.copyHistory[0] : null
- :
- postData.copy_history ? postData.copy_history[0] : null;
-
- let repostText = "";
- let reportAttachments = "";
- if (repost) {
- repostText += `\n>>> [**Репост записи**](https://vk.com/wall${repost.from_id}_${repost.id})\n\n`;
- if (repost.text) {
- repostText += parseLinks(repost.text);
- }
-
- if (repost.attachments) {
- reportAttachments += await getAttachments(repost.attachments, webhookBuilder, longpoll);
- }
- }
-
- const allPost = postText + attachments + repostText + reportAttachments;
-
- webhookBuilder.setDescription(allPost.length > 2048 ?
- (postText ? postText.slice(0, repostText ? 1021 - attachments.length : 2045 - attachments.length) + (postData.text ? "…\n\n" : "") : "")
- + attachments
- + (repostText ? repostText.slice(0, postText ? 1021 - reportAttachments.length : 2045 - reportAttachments.length) + (repost.text ? "…\n\n" : "") : "")
- + reportAttachments
- :
- allPost);
-
- discord.send(webhookBuilder)
- .then(() => {
- console.log("[!] Пост успешно опубликован в Discord канале.");
-
- news.last_post = createdAt;
- news.published_posts.unshift(createdAt);
-
- if (news.published_posts.length >= 30) news.published_posts.splice(-1, 1);
-
- fs.writeFileSync("./news.json", JSON.stringify(news, null, "\t"));
- })
- .catch(err => errorHandler(err));
- } else {
- console.log("[!] Новых записей нет или они не соответствуют ключевым словам!");
- }
-};
diff --git a/modules/sender.mjs b/modules/sender.mjs
new file mode 100644
index 0000000..908566f
--- /dev/null
+++ b/modules/sender.mjs
@@ -0,0 +1,177 @@
+import fs from "fs";
+
+import { webhook, name, color, urls/*, sendAll*/ } from "./discord";
+import { keywords, longpoll, filter } from "./vk";
+
+import { errorHandler } from "./handler";
+
+import news from "../news";
+
+export class Sender {
+ state = {
+ post: {
+ text: "",
+ attachments: ""
+ },
+ repost: {
+ text: "",
+ attachments: ""
+ },
+ webhookBuilders: []
+ };
+
+ async Post(builder, postData) {
+ const { post, repost, webhookBuilders } = this.state;
+
+ webhookBuilders.push(builder);
+
+ const createdAt = longpoll ? postData.createdAt : postData.date;
+
+ if (news.last_post !== createdAt && !(news.published_posts.includes(createdAt)) && this.CheckKeywords(postData.text)) { // Проверяем что пост не был опубликован и соответствует ключевым словам
+
+ if (longpoll && filter && postData.authorId === postData.createdUserId) return; // Фильтр на записи только от имени группы для LongPoll
+
+ post.text += `[**Открыть пост ВКонтакте**](https://vk.com/wall${longpoll ? postData.authorId : postData.from_id}_${postData.id})\n\n`;
+
+ if (postData.text) post.text += this.FixLinks(postData.text);
+
+ if (postData.attachments) post.attachments += await this.ParseAttachments(postData.attachments);
+
+ const Repost = longpoll ?
+ postData.copyHistory ? postData.copyHistory[0] : null
+ :
+ postData.copy_history ? postData.copy_history[0] : null;
+
+ if (Repost) {
+ repost.text += `\n\n>>> [**Репост записи**](https://vk.com/wall${longpoll ? Repost.authorId : Repost.from_id}_${Repost.id})\n\n`;
+
+ if (Repost.text) repost.text += this.FixLinks(Repost.text);
+
+ if (Repost.attachments) repost.attachments += await this.ParseAttachments(Repost.attachments);
+ }
+
+ this.Send(createdAt);
+ } else {
+ console.log("[!] Новых записей нет или они не соответствуют ключевым словам!");
+ }
+ }
+
+ async ParseAttachments(attachments) {
+ const { webhookBuilders } = this.state;
+ const builder = webhookBuilders[0];
+
+ let text = "";
+
+ await attachments.forEach(item => {
+ const type = item.type;
+
+ switch (type) {
+ case "photo":
+ if (!builder.data.attachments[0].image_url) {
+ builder.setImage((longpoll ? item.sizes : item.photo.sizes).pop().url);
+ }
+ break;
+ case "video":
+ const video = longpoll ? item : item.video;
+
+ text += `\n[:video_camera: Смотреть видео: ${video.title}](https://vk.com/video${longpoll ? video.ownerId : video.owner_id}_${video.id})`;
+ break;
+ case "link":
+ const link = longpoll ? item : item.link;
+
+ text += `\n[:link: ${link.button_text || "Ссылка"}: ${link.title}](${link.url})`;
+ break;
+ case "doc":
+ const doc = longpoll ? item : item.doc;
+ const ext = longpoll ? doc.typeName : doc.ext;
+
+ if (ext === "gif") {
+ builder.setImage(doc.url);
+ } else {
+ text += `\n[:page_facing_up: Документ: ${doc.title}](${doc.url})`;
+ }
+ break;
+ case "audio":
+ const audio = longpoll ? item : item.audio;
+
+ const artist = audio.artist;
+ const title = audio.title;
+
+ text += `\n[:musical_note: Музыка: ${artist} - ${title}](https://vk.com/search?c[section]=audio&c[q]=${encodeURI(artist.replace(/&/g, "и"))}%20-%20${encodeURI(title)}&c[performer]=1)`;
+ break;
+ case "poll":
+ const poll = longpoll ? item : item.poll;
+
+ text += `\n[:bar_chart: Опрос: ${poll.question}](https://vk.com/feed?w=poll${longpoll ? poll.ownerId : poll.owner_id}_${poll.id})`;
+ break;
+ }
+ });
+
+ /*if (sendAll) {
+ await this.ParsePhotos(attachments);
+ }*/
+
+ return text;
+ }
+
+ /*async ParsePhotos(attachments) {
+ const { webhookBuilders } = this.state;
+
+ const photos = attachments.filter(attachment => attachment.type === "photo");
+
+ await photos.forEach((item, index) => {
+ const builder = new webhook.MessageBuilder();
+
+ if (photos.length > 1 && index === 0) return;
+
+ builder.setImage((longpoll ? item.sizes : item.photo.sizes).pop().url);
+
+ webhookBuilders.push(builder);
+ });
+ }*/
+
+ CheckKeywords(text) {
+ if (keywords.length > 0) {
+ return keywords.some(keyword => {
+ return text.match(keyword, "gi");
+ });
+ } else {
+ return true;
+ }
+ }
+
+ FixLinks(text) {
+ return decodeURI(`${text.replace(/(?:\[(https:\/\/vk.com\/[^]+?)\|([^]+?)])/g, "[$2]($1)").replace(/(?:\[([^]+?)\|([^]+?)])/g, "[$2](https://vk.com/$1)")}\n\n`);
+ }
+
+ async Send(createdAt) {
+ const { post, repost, webhookBuilders } = this.state;
+ const builder = webhookBuilders[0].setName(name).setColor(color);
+
+ if (post.text.length + post.attachments.length + repost.text.length + repost.attachments.length > 2048) {
+ if (post.text) {
+ post.text = post.text.slice(0, repost.text ? 1021 - post.attachments.length : 2045 - post.attachments.length) + "…\n\n"
+ }
+
+ if (repost.text) {
+ repost.text = repost.text.slice(0, post.text ? 1021 - repost.attachments.length : 2045 - repost.attachments.length) + "…\n\n"
+ }
+ }
+
+ builder.setDescription(post.text + post.attachments + repost.text + repost.attachments);
+
+ urls.forEach((url, index) => {
+ new webhook.Webhook(url)
+ .send(builder)
+ .then(console.log(`[!] Пост успешно опубликован в Discord-канале #${index + 1}.`))
+ .catch((error) => errorHandler(error));
+ });
+
+ news.last_post = createdAt;
+ news.published_posts.unshift(createdAt);
+
+ if (news.published_posts.length >= 30) news.published_posts.splice(-1, 1);
+
+ fs.writeFileSync("./news.json", JSON.stringify(news, null, "\t"));
+ }
+}
\ No newline at end of file
diff --git a/modules/vk.mjs b/modules/vk.mjs
new file mode 100644
index 0000000..5fae629
--- /dev/null
+++ b/modules/vk.mjs
@@ -0,0 +1,34 @@
+import config from "../config";
+import VKIO from "vk-io";
+
+const { VK } = VKIO;
+const vk = new VK();
+
+const token = config.vk.token;
+const groupId = config.vk.group_id;
+const longpoll = config.vk.longpoll;
+
+const interval = config.interval * 1000;
+
+const filter = config.vk.filter;
+const keywords = config.vk.keywords;
+
+vk.setOptions({
+ token,
+ apiMode: "parallel" // Необходимо исользовать LongPoll версии 5.103
+});
+
+const { updates, api } = vk;
+
+export {
+ updates,
+ api,
+
+ longpoll,
+ groupId,
+
+ interval,
+
+ filter,
+ keywords
+};
\ No newline at end of file
diff --git a/news.json b/news.json
index 19dc713..bbad8b1 100644
--- a/news.json
+++ b/news.json
@@ -1,4 +1,4 @@
{
- "last_post": null,
+ "last_post": 0,
"published_posts": []
}
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 57009f9..a446853 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,9 +1,17 @@
{
"name": "vk2discord",
- "version": "1.5.0",
+ "version": "1.6.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
+ "debug": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+ "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+ "requires": {
+ "ms": "^2.1.1"
+ }
+ },
"fs": {
"version": "0.0.1-security",
"resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz",
@@ -14,6 +22,11 @@
"resolved": "https://registry.npmjs.org/middleware-io/-/middleware-io-2.4.0.tgz",
"integrity": "sha512-pzK7Eg5ZlDesrgThEce4jhl0TJfU6ZTH8TZRKUSnWiN2yMmTSw6/EHpqQOm/g/YzkE83yH9rC3PqN+oPHQox5w=="
},
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+ },
"node-fetch": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz",
diff --git a/package.json b/package.json
index 16cae07..b8220e3 100644
--- a/package.json
+++ b/package.json
@@ -1,16 +1,19 @@
{
"name": "vk2discord",
- "version": "1.5.0",
+ "version": "1.6.0",
"description": "Автоматическая публикация постов из VK.COM в канал Discord",
- "main": "index.js",
+ "main": "index.mjs",
"scripts": {
- "test": "echo \"Error: no test specified\" && exit 1"
+ "start": "node --experimental-modules --experimental-json-modules --es-module-specifier-resolution=node index.mjs"
},
"repository": {
"type": "git",
"url": "git+https://github.com/MrZillaGold/VK2Discord.git"
},
- "author": "MrZillaGold",
+ "author": {
+ "name": "MrZillaGold",
+ "url": "https://vk.com/mrzillagold"
+ },
"license": "SEE LICENSE IN LICENSE.txt",
"bugs": {
"url": "https://github.com/MrZillaGold/VK2Discord/issues"