Skip to content

Commit

Permalink
Add: modular configurations type
Browse files Browse the repository at this point in the history
  • Loading branch information
DUB1401 committed Sep 23, 2023
1 parent 6c9fbb1 commit d2ad827
Show file tree
Hide file tree
Showing 7 changed files with 161 additions and 143 deletions.
12 changes: 8 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@ VK-Telegram Poster.pyproj
VK-Telegram Poster.sln
.vs

# Python cache.
__pycache__

# Work folders.
Config/*
Logs
Temp
Temp

# Don't ignore config example.
!Config/Example.json

# Python cache.
__pycache__
13 changes: 13 additions & 0 deletions Config/Example.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"token": "{TELEGRAM_BOT_TOKEN}",
"target": "{GROUP_OR_CHANNEL_ID}",
"clean-tags": true,
"parse-mode": null,
"disable-web-page-preview": true,
"blacklist": [],
"attachments": {
"doc": true,
"photo": true,
"video": true
}
}
40 changes: 19 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@

## Порядок установки и использования
1. Загрузить последний релиз. Распаковать.
2. Установить Python версии не старше 3.10. Если основная версия Python вашего сервера ниже, указать целевую платформу путём редактирования первой строки в файле _vtp.py_ (_#!/usr/bin/python__#!/usr/bin/python3.x_).
3. В среду исполнения установить следующие пакеты: [pyTelegramBotAPI](https://github.com/eternnoir/pyTelegramBotAPI), [fastapi](https://github.com/tiangolo/fastapi), [uvicorn](https://github.com/encode/uvicorn).
2. Установить Python версии не старше 3.10.
3. В среду исполнения установить следующие пакеты: [dublib](https://github.com/DUB1401/dublib), [pyTelegramBotAPI](https://github.com/eternnoir/pyTelegramBotAPI), [fastapi](https://github.com/tiangolo/fastapi), [uvicorn](https://github.com/encode/uvicorn).
```
pip install git+https://github.com/DUB1401/dublib#egg=dublib
pip install pyTelegramBotAPI
pip install fastapi
pip install uvicorn
Expand All @@ -18,36 +19,32 @@ pip install uvicorn
```
pip install -r requirements.txt
```
4. Настроить скрипт путём редактирования _Settings.json_. Для добавления пользовательского скрипта обработки постов можно внести изменения в файл _MessageEditor.py_.
5. При необходимости, например в случае использования скрипта на хостинге активного сайта, настроить переадресацию [nginx](https://nginx.org/) на свободный порт.
6. Провести валидацию сервера согласно данному [руководству](https://dev.vk.com/api/callback/getting-started#%D0%9F%D0%BE%D0%B4%D0%BA%D0%BB%D1%8E%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20Callback%20API). Код подтверждения перед верификацией занести в файл настроек _Settings.json_. По умолчанию скрипт слушает `{HOST}/vtp/vk-group-wall`.
7. Открыть директорию со скриптом в терминале. Можно использовать метод `cd` и прописать путь к папке, либо запустить терминал из проводника. Активировать автопостер командой `uvicorn vtp:App --host {IP} --port {PORT}`.
8. Для автоматического запуска службы рекомендуется провести инициализацию скрипта через [systemd](https://github.com/systemd/systemd) (пример [здесь](https://github.com/DUB1401/VK-Telegram-Poster/tree/main/systemd)) на Linux или путём добавления его в автозагрузку на Windows.
4. Создать в папке _Config_ файл конфигурации по предоставленному примеру (см. [здесь](#конфигурация-источника)).
5. Настроить автопостер путём редактирования _Settings.json_. Для добавления пользовательского скрипта обработки постов можно внести изменения в файл _MessageEditor.py_.
6. При необходимости, например в случае использования скрипта на хостинге активного сайта, настроить переадресацию [nginx](https://nginx.org/) на свободный порт.
7. Провести валидацию сервера согласно данному [руководству](https://dev.vk.com/api/callback/getting-started#%D0%9F%D0%BE%D0%B4%D0%BA%D0%BB%D1%8E%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20Callback%20API). Код подтверждения перед верификацией занести в файл настроек _Settings.json_.
8. Открыть директорию со скриптом в терминале. Можно использовать метод `cd` и прописать путь к папке, либо запустить терминал из проводника. Активировать автопостер командой `uvicorn vtp:App --host {IP} --port {PORT}`.
9. Для автоматического запуска службы рекомендуется провести инициализацию скрипта через [systemd](https://github.com/systemd/systemd) (пример [здесь](https://github.com/DUB1401/VK-Telegram-Poster/tree/main/systemd)) на Linux или путём добавления его в автозагрузку на Windows.

## Версии поставляемых бинарных файлов
| Файл | Версия | Источник |
|---------|-------------------------------|--------------------------------------------------------------------|
| yt-dlp | _2023.07.06_ | [ссылка](https://github.com/yt-dlp/yt-dlp/releases/tag/2023.07.06) |

# Settings.json
```JSON
"tokens": {
"vk-group-wall": "{TELEGRAM_BOT_TOKEN}"
}
```
Сюда необходимо занести токены ботов Telegram (можно узнать у [BotFather](https://t.me/BotFather)). Ключём должен являться уникальный источник, использующийся для прослушивания Callback-запросов и соответствующий таковому в поле `targets`.
# Конфигурация источника
Для добавления новой конфигурации создайте копию файла _Example.json_ и переименуйте её согласно источнику. Источник указывает конечную часть URI, использующегося для прослушивания Callback-запросов.

**Пример:** `{HOST}/vtp/{SOURCE}`
___
```JSON
"targets": {
"vk-group-wall": "{GROUP_OR_CHANNEL_ID}"
}
"token": ""
```
Сюда необходимо занести ID групп или каналов Telegram (можно получить у [Chat ID Bot](https://t.me/chat_id_echo_bot)). Ключём должен являться уникальный источник, использующийся для прослушивания Callback-запросов и соответствующий таковому в поле `tokens`.
Сюда необходимо занести токен бота Telegram (можно получить у [BotFather](https://t.me/BotFather)). Токен должен быть уникальным для каждого источника.
___
```JSON
"source": "vk-group-wall"
"target": ""
```
Указывает конечную часть URI, использующегося для прослушивания Callback-запросов. По умолчанию скрипт слушает `{HOST}/vtp/vk-group-wall`.
Сюда необходимо занести ID группы или канала Telegram (можно получить у [Chat ID Bot](https://t.me/chat_id_echo_bot)).
___
```JSON
"clean-tags": true
Expand Down Expand Up @@ -82,7 +79,8 @@ ___

> [!IMPORTANT]
> Для пересылки _video_ необходимо, чтобы в сообществе ВКонтакте для раздела «Видео» было установленно значение _Открытые_ или _Ограниченные_.
___
# Settings.json
```JSON
"confirmation-code": ""
```
Expand Down
14 changes: 0 additions & 14 deletions Settings.json
Original file line number Diff line number Diff line change
@@ -1,18 +1,4 @@
{
"tokens": {
"vk-group-wall": "{TELEGRAM_BOT_TOKEN}"
},
"targets": {
"vk-group-wall": "{GROUP_OR_CHANNEL_ID}"
},
"clean-tags": true,
"disable-web-page-preview": true,
"blacklist": [],
"attachments": {
"doc": true,
"photo": true,
"video": true
},
"confirmation-code": "",
"logging": false,
"debug": false
Expand Down
53 changes: 28 additions & 25 deletions Source/Callback.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from telebot.types import InputMediaDocument, InputMediaPhoto, InputMediaVideo
from Source.Configurator import Configurator
from MessageEditor import MessageEditor
from threading import Thread
from time import sleep
Expand Down Expand Up @@ -38,15 +39,15 @@ def __EscapeCharacters(self, Post: str) -> str:
return Post

# Получает URL вложения и загружает его.
def __GetAttachements(self, PostAttachements: dict) -> list:
def __GetAttachements(self, PostAttachements: dict, Source: str) -> list:
# Список вложений.
Attachements = list()
# Список поддерживаемых вложений.
SupportedTypes = list()

# Формирование списка включённых вложений.
for Type in self.__Settings["attachments"].keys():
if self.__Settings["attachments"][Type] == True:
for Type in self.__Configurations.getAttachments(Source).keys():
if self.__Configurations.getAttachments(Source)[Type] == True:
SupportedTypes.append(Type)

# Если нет папки для хранения вложений, то создать.
Expand Down Expand Up @@ -146,7 +147,7 @@ def __SenderThread(self):
InputMediaDocument(
open("Temp/" + self.__MessagesBufer[0]["attachments"][Index]["filename"], "rb"),
caption = self.__MessagesBufer[0]["text"] if Index == 0 else "",
parse_mode = self.__Settings["parse-mode"] if Index == 0 else None
parse_mode = self.__Configurations.getConfig(self.__MessagesBufer[0]["source"])["parse-mode"] if Index == 0 else None
)
)

Expand All @@ -157,7 +158,7 @@ def __SenderThread(self):
InputMediaPhoto(
open("Temp/" + self.__MessagesBufer[0]["attachments"][Index]["filename"], "rb"),
caption = self.__MessagesBufer[0]["text"] if Index == 0 else "",
parse_mode = self.__Settings["parse-mode"] if Index == 0 else None
parse_mode = self.__Configurations.getConfig(self.__MessagesBufer[0]["source"])["parse-mode"] if Index == 0 else None
)
)

Expand All @@ -168,7 +169,7 @@ def __SenderThread(self):
InputMediaVideo(
open("Temp/" + self.__MessagesBufer[0]["attachments"][Index]["filename"], "rb"),
caption = self.__MessagesBufer[0]["text"] if Index == 0 else "",
parse_mode = self.__Settings["parse-mode"] if Index == 0 else None
parse_mode = self.__Configurations.getConfig(self.__MessagesBufer[0]["source"])["parse-mode"] if Index == 0 else None
)
)

Expand All @@ -187,8 +188,8 @@ def __SenderThread(self):
self.__TelegramBots[self.__MessagesBufer[0]["source"]].send_message(
self.__MessagesBufer[0]["target"],
self.__MessagesBufer[0]["text"],
parse_mode = self.__Settings["parse-mode"],
disable_web_page_preview = self.__Settings["disable-web-page-preview"]
parse_mode = self.__Configurations.getConfig(self.__MessagesBufer[0]["source"])["parse-mode"],
disable_web_page_preview = self.__Configurations.getConfig(self.__MessagesBufer[0]["source"])["disable-web-page-preview"]
)

except telebot.apihelper.ApiTelegramException as ExceptionData:
Expand Down Expand Up @@ -222,59 +223,61 @@ def __SendMessage(self, PostObject: dict, Source: str):
# Объект сообщения.
MessageStruct = {
"source": Source,
"target": self.__Settings["targets"][Source],
"target": self.__Configurations.getConfig(Source)["target"],
"text": None,
"attachments": list()
}

# Экранировать символы при указанной разметке MarkdownV2.
if self.__Settings["parse-mode"] == "MarkdownV2":
if self.__Configurations.getConfig(Source)["parse-mode"] == "MarkdownV2":
PostObject["text"] = self.__EscapeCharacters(PostObject["text"])

# Обработка текста поста пользовательским скриптом.
PostObject["text"] = MessageEditor(PostObject["text"], Source)

# Для каждого запрещённого слова проверить соответствие словам поста.
for ForbiddenWord in self.__Configurations.getConfig(Source)["blacklist"]:
for Word in PostObject["text"].split():

# Если пост содержит запрещённое слово, то игнорировать его.
if ForbiddenWord.lower() == Word.lower():
HasBlacklistWords = True

# Если сообщение не игнорируется.
if PostObject["text"] != None and PostObject["text"] != "" and HasBlacklistWords == False:

# Если включена очистка тегов, то удалить упоминания из них.
if self.__Settings["clean-tags"] == True:
if self.__Configurations.getConfig(Source)["clean-tags"] == True:
PostObject["text"] = self.__CleanTags(PostObject["text"])

# Для каждого запрещённого слова проверить соответствие словам поста.
for ForbiddenWord in self.__Settings["blacklist"]:
for Word in PostObject["text"].split():

# Если пост содержит запрещённое слово, то игнорировать его.
if ForbiddenWord.lower() == Word.lower():
HasBlacklistWords = True

# Обрезка текста поста до максимально дозволенной длинны.
PostObject["text"] = PostObject["text"][:4096]
# Копирование текста из поста в сообщение.
MessageStruct["text"] = PostObject["text"]

# Если есть вложения.
if len(PostObject["attachments"]) > 0:
MessageStruct["attachments"] = self.__GetAttachements(PostObject["attachments"])
MessageStruct["attachments"] = self.__GetAttachements(PostObject["attachments"], Source)

# Помещение поста в очередь на отправку.
self.__MessagesBufer.append(MessageStruct)

else:
# Запись в лог отладочной информации: пост был проигнорирован.
logging.debug("Post " + str(PostObject["id"]) + " was ignored.")
logging.info("Post with ID " + str(PostObject["id"]) + " was ignored.")

# Активировать поток отправки, если не активен.
if self.__Sender.is_alive() == False:
self.__Sender = Thread(target = self.__SenderThread, name = "VK-Telegram Poster (sender)")
self.__Sender.start()

# Конструктор: задаёт глобальные настройки и тело Callback-запроса.
def __init__(self, Settings: dict):
def __init__(self, Settings: dict, ConfiguratorObject: Configurator):

#---> Генерация динамических свойств.
#==========================================================================================#
# Конфигурации.
self.__Configurations = ConfiguratorObject
# Экзмепляры обработчиков постов.
self.__PostsEditorsThreads = list()
# Очередь отложенных сообщений.
Expand All @@ -285,13 +288,13 @@ def __init__(self, Settings: dict):
self.__Settings = Settings.copy()
# Поток отправки сообщений.
self.__Sender = Thread(target = self.__SenderThread)

# Запуск потока обработки буфера сообщений.
self.__Sender.start()

# Инициализация экзепляров бота.
for Target in self.__Settings["tokens"].keys():
self.__TelegramBots[Target] = telebot.TeleBot(self.__Settings["tokens"][Target])
for Target in self.__Configurations.getConfigsNames():
self.__TelegramBots[Target] = telebot.TeleBot(self.__Configurations.getToken(Target))

# Добавляет сообщение в очередь отправки.
def AddMessageToBufer(self, CallbackRequest: dict, Source: str):
Expand Down
61 changes: 61 additions & 0 deletions Source/Configurator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
from dublib.Methods import ReadJSON

import os

# Менеджер конфигураций.
class Configurator:

# Читает конфигурации.
def __ReadConfigs(self) -> dict:
# Список файлов в директории конфигурации.
FilesList = os.listdir("Config")
# Фильтрация только файлов формата JSON.
FilesList = list(filter(lambda x: x.endswith(".json"), FilesList))
# Словарь конфигураций.
Configurations = dict()

# Удаление примера.
if "Example.json" in FilesList:
FilesList.remove("Example.json")

# Для каждого файла конфигурации.
for Filename in FilesList:
# Прочитать конфигурацию в словарь.
Configurations[Filename.replace(".json", "")] = ReadJSON("Config/" + Filename)

return Configurations

# Конструктор.
def __init__(self):

#---> Генерация динамических свойств.
#==========================================================================================#
# Словарь конфигураций.
self.__Configurations = self.__ReadConfigs()

# Возвращает настройки вложений.
def getAttachments(self, ConfigName: str) -> dict:
return self.__Configurations[ConfigName]["attachments"]

# Возвращает настройки в конфигурации.
def getConfig(self, ConfigName: str) -> dict:
return self.__Configurations[ConfigName]

# Возвращает список конфигураций.
def getConfigsNames(self) -> list[str]:
return list(self.__Configurations.keys())

# Возвращает токен для указанной конфигурации.
def getToken(self, ConfigName: str) -> str:
return self.__Configurations[ConfigName]["token"]

# Возвращает список токенов ботов.
def getTokens(self):
# Список токенов.
TokensList = list()

# Для каждого элемента записать токен.
for Config in self.__Configurations.values:
TokensList.append(Config["token"])

return TokensList
Loading

0 comments on commit d2ad827

Please sign in to comment.