Behavior set, multi-account, inline mod, and other little things
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
89c42edc25
commit
e85e2c03bd
@ -34,10 +34,6 @@ steps:
|
||||
- name: docker publish develop
|
||||
image: upagge/docker-buildx:latest
|
||||
environment:
|
||||
STRUCHKOV_DOCKER_REGISTRY_TOKEN:
|
||||
from_secret: STRUCHKOV_DOCKER_REGISTRY_TOKEN
|
||||
STRUCHKOV_DOCKER_IMAGE_NAME:
|
||||
from_secret: STRUCHKOV_DOCKER_IMAGE_NAME
|
||||
DOCKER_REGISTRY_TOKEN:
|
||||
from_secret: DOCKER_REGISTRY_TOKEN
|
||||
DOCKER_IMAGE_NAME:
|
||||
@ -48,10 +44,9 @@ steps:
|
||||
- name: dockersock
|
||||
path: /var/run
|
||||
commands:
|
||||
- echo "$STRUCHKOV_DOCKER_REGISTRY_TOKEN" | docker login git.struchkov.dev --username $DOCKER_REGISTRY_USER --password-stdin
|
||||
- echo "$DOCKER_REGISTRY_TOKEN" | docker login docker.io --username $DOCKER_REGISTRY_USER --password-stdin
|
||||
- docker buildx create --use
|
||||
- docker buildx build -f Dockerfile-develop --push --platform linux/amd64,linux/arm64/v8 -t "$DOCKER_IMAGE_NAME:develop" -t "git.struchkov.dev/$STRUCHKOV_DOCKER_IMAGE_NAME:develop" .
|
||||
- docker buildx build -f Dockerfile-develop --push --platform linux/amd64,linux/arm64/v8 -t "$DOCKER_IMAGE_NAME:develop" .
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
@ -203,6 +198,6 @@ volumes:
|
||||
path: /drone/volume/mkdocs_cache/chatgpt-telegram-bot/en
|
||||
---
|
||||
kind: signature
|
||||
hmac: 44ca201fe0ea4a7b739d9e170e0852171c5175c0128bfd3e77f7247f92db56a8
|
||||
hmac: b615ac14d736c1ff2354f10d96117108866cd0bc8d387f827ea063fbe66b418c
|
||||
|
||||
...
|
||||
|
10
pom.xml
10
pom.xml
@ -15,8 +15,8 @@
|
||||
<properties>
|
||||
<java.version>17</java.version>
|
||||
|
||||
<openai.api.version>0.0.1</openai.api.version>
|
||||
<godfather.version>0.0.53</godfather.version>
|
||||
<openai.api.version>0.0.2-SNAPSHOT</openai.api.version>
|
||||
<godfather.version>0.0.54-SNAPSHOT</godfather.version>
|
||||
|
||||
<!-- https://mvnrepository.com/artifact/org.apache.maven.plugins/maven-compiler-plugin -->
|
||||
<plugin.maven.compiler.ver>3.10.1</plugin.maven.compiler.ver>
|
||||
@ -61,6 +61,12 @@
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<version>31.1-jre</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<repositories>
|
||||
|
@ -4,10 +4,10 @@ import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class SpringBootTelegramBotExampleApplication {
|
||||
public class ChatGptTelegramBot {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SpringBootTelegramBotExampleApplication.class, args);
|
||||
SpringApplication.run(ChatGptTelegramBot.class, args);
|
||||
}
|
||||
|
||||
}
|
@ -5,13 +5,15 @@ import lombok.Setter;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Component
|
||||
@ConfigurationProperties("app")
|
||||
public class AppProperty {
|
||||
|
||||
private String telegramId;
|
||||
private List<String> telegramIds;
|
||||
private String version;
|
||||
|
||||
}
|
||||
|
@ -19,13 +19,21 @@ public class ErrorTelegramHandler implements ErrorHandler {
|
||||
private final TelegramSending sending;
|
||||
|
||||
@Override
|
||||
public void handle(Message message, Exception e) {
|
||||
public void handle(Message message, Throwable e) {
|
||||
log.error(e.getMessage(), e);
|
||||
final String errorMessage = escapeMarkdown(e.getMessage());
|
||||
|
||||
final String recipientTelegramId;
|
||||
if (appProperty.getTelegramIds().contains(message.getFromPersonId())) {
|
||||
recipientTelegramId = message.getFromPersonId();
|
||||
} else {
|
||||
recipientTelegramId = appProperty.getTelegramIds().get(0);
|
||||
}
|
||||
|
||||
sending.send(
|
||||
BoxAnswer.builder()
|
||||
.message("Error message:\n\n" + errorMessage)
|
||||
.recipientPersonId(appProperty.getTelegramId())
|
||||
.recipientPersonId(recipientTelegramId)
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
@ -0,0 +1,63 @@
|
||||
package dev.struchkov.example.bot.handler;
|
||||
|
||||
import dev.struchkov.example.bot.conf.AppProperty;
|
||||
import dev.struchkov.example.bot.service.InlineStorage;
|
||||
import dev.struchkov.godfather.main.domain.EventContainer;
|
||||
import dev.struchkov.godfather.simple.context.service.EventHandler;
|
||||
import dev.struchkov.godfather.telegram.simple.context.service.TelegramSending;
|
||||
import dev.struchkov.openai.context.GPTClient;
|
||||
import dev.struchkov.openai.domain.common.GptMessage;
|
||||
import dev.struchkov.openai.domain.model.gpt.GPT3Model;
|
||||
import dev.struchkov.openai.domain.request.GptRequest;
|
||||
import dev.struchkov.openai.domain.response.Choice;
|
||||
import dev.struchkov.openai.domain.response.GptResponse;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.telegram.telegrambots.meta.api.objects.inlinequery.ChosenInlineQuery;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import static dev.struchkov.godfather.simple.domain.BoxAnswer.boxAnswer;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class InlineChoseHandler implements EventHandler<ChosenInlineQuery> {
|
||||
|
||||
private final GPTClient gptClient;
|
||||
private final InlineStorage inlineStorage;
|
||||
private final AppProperty appProperty;
|
||||
private final TelegramSending telegramSending;
|
||||
|
||||
@Override
|
||||
public void handle(EventContainer<ChosenInlineQuery> event) {
|
||||
final ChosenInlineQuery chosenInlineQuery = event.getObject();
|
||||
final String personId = chosenInlineQuery.getFrom().getId().toString();
|
||||
|
||||
if (appProperty.getTelegramIds().contains(personId)) {
|
||||
final Optional<String> optRequest = inlineStorage.getQuery(chosenInlineQuery.getResultId());
|
||||
if (optRequest.isPresent()) {
|
||||
final String request = optRequest.get();
|
||||
final GptResponse gptResponse = gptClient.execute(
|
||||
GptRequest.builder()
|
||||
.model(GPT3Model.GPT_3_5_TURBO)
|
||||
.message(
|
||||
GptMessage.fromUser(request)
|
||||
)
|
||||
.build()
|
||||
);
|
||||
final List<Choice> choices = gptResponse.getChoices();
|
||||
final String answer = choices.get(choices.size() - 1).getMessage().getContent();
|
||||
telegramSending.replaceInlineMessage(chosenInlineQuery.getInlineMessageId(), boxAnswer(answer));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<ChosenInlineQuery> getEventClass() {
|
||||
return ChosenInlineQuery.class;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
package dev.struchkov.example.bot.handler;
|
||||
|
||||
import dev.struchkov.example.bot.conf.AppProperty;
|
||||
import dev.struchkov.example.bot.service.InlineStorage;
|
||||
import dev.struchkov.godfather.main.domain.EventContainer;
|
||||
import dev.struchkov.godfather.simple.context.service.EventHandler;
|
||||
import dev.struchkov.godfather.telegram.simple.context.service.TelegramConnect;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.telegram.telegrambots.meta.api.methods.AnswerInlineQuery;
|
||||
import org.telegram.telegrambots.meta.api.objects.inlinequery.InlineQuery;
|
||||
import org.telegram.telegrambots.meta.api.objects.inlinequery.inputmessagecontent.InputTextMessageContent;
|
||||
import org.telegram.telegrambots.meta.api.objects.inlinequery.result.InlineQueryResultArticle;
|
||||
import org.telegram.telegrambots.meta.api.objects.replykeyboard.InlineKeyboardMarkup;
|
||||
import org.telegram.telegrambots.meta.api.objects.replykeyboard.buttons.InlineKeyboardButton;
|
||||
import org.telegram.telegrambots.meta.exceptions.TelegramApiException;
|
||||
import org.telegram.telegrambots.meta.exceptions.TelegramApiValidationException;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class InlineQueryHandler implements EventHandler<InlineQuery> {
|
||||
|
||||
private final InlineStorage inlineStorage;
|
||||
private final AppProperty appProperty;
|
||||
private final TelegramConnect telegramConnect;
|
||||
|
||||
@Override
|
||||
public void handle(EventContainer<InlineQuery> event) {
|
||||
final InlineQuery inlineQuery = event.getObject();
|
||||
if (appProperty.getTelegramIds().contains(inlineQuery.getFrom().getId().toString())) {
|
||||
|
||||
final InputTextMessageContent buildMessageContent = InputTextMessageContent.builder()
|
||||
.messageText(inlineQuery.getQuery())
|
||||
.build();
|
||||
|
||||
try {
|
||||
buildMessageContent.validate();
|
||||
} catch (TelegramApiValidationException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
}
|
||||
|
||||
final String id = UUID.randomUUID().toString();
|
||||
inlineStorage.save(id, inlineQuery.getQuery());
|
||||
|
||||
final InlineQueryResultArticle result = InlineQueryResultArticle.builder()
|
||||
.id(id)
|
||||
.title("Your request to ChatGPT:")
|
||||
.description(inlineQuery.getQuery())
|
||||
.hideUrl(true)
|
||||
.thumbUrl("https://struchkov.dev/static/img/openai.jpeg")
|
||||
.replyMarkup(
|
||||
InlineKeyboardMarkup.builder()
|
||||
.keyboard(List.of(List.of(
|
||||
InlineKeyboardButton.builder()
|
||||
.text("Wait... The answer is generated...")
|
||||
.callbackData("inline_query")
|
||||
.build()
|
||||
)))
|
||||
.build()
|
||||
)
|
||||
.inputMessageContent(buildMessageContent).build();
|
||||
|
||||
try {
|
||||
result.validate();
|
||||
} catch (TelegramApiValidationException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
}
|
||||
|
||||
final AnswerInlineQuery answerInlineQuery = AnswerInlineQuery.builder()
|
||||
.inlineQueryId(inlineQuery.getId())
|
||||
.result(result)
|
||||
.build();
|
||||
|
||||
try {
|
||||
answerInlineQuery.validate();
|
||||
} catch (TelegramApiValidationException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
}
|
||||
|
||||
try {
|
||||
telegramConnect.getAbsSender().execute(answerInlineQuery);
|
||||
} catch (TelegramApiException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<InlineQuery> getEventClass() {
|
||||
return InlineQuery.class;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package dev.struchkov.example.bot.service;
|
||||
|
||||
import com.google.common.cache.Cache;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Service
|
||||
public class InlineStorage {
|
||||
|
||||
private final ScheduledExecutorService scheduledExecutor = Executors.newSingleThreadScheduledExecutor();
|
||||
|
||||
private final Cache<String, String> cache = CacheBuilder.newBuilder()
|
||||
.expireAfterWrite(5, TimeUnit.MINUTES)
|
||||
.build();
|
||||
|
||||
public InlineStorage() {
|
||||
scheduledExecutor.scheduleAtFixedRate(cache::cleanUp, 1, 1, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
public void save(String inlineId, String query) {
|
||||
cache.put(inlineId, query);
|
||||
}
|
||||
|
||||
public Optional<String> getQuery(String inlineId) {
|
||||
return Optional.ofNullable(cache.getIfPresent(inlineId));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
package dev.struchkov.example.bot.service;
|
||||
|
||||
import dev.struchkov.example.bot.conf.AppProperty;
|
||||
import dev.struchkov.openai.context.ChatGptService;
|
||||
import dev.struchkov.openai.domain.chat.ChatInfo;
|
||||
import dev.struchkov.openai.domain.chat.CreateChat;
|
||||
import dev.struchkov.openai.domain.chat.UpdateChat;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@Service
|
||||
public class PersonalChatService {
|
||||
|
||||
private final ChatGptService chatGptService;
|
||||
private final Map<String, String> chatDefaultContext = new ConcurrentHashMap<>();
|
||||
private final Map<String, ChatInfo> chatMap = new ConcurrentHashMap<>();
|
||||
|
||||
public PersonalChatService(
|
||||
AppProperty appProperty,
|
||||
ChatGptService chatGptService
|
||||
) {
|
||||
this.chatGptService = chatGptService;
|
||||
appProperty.getTelegramIds().forEach(
|
||||
telegramId -> {
|
||||
final ChatInfo newChat = this.chatGptService.createChat(
|
||||
CreateChat.builder().build()
|
||||
);
|
||||
chatMap.put(telegramId, newChat);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public ChatInfo getChatByPersonId(String personId) {
|
||||
return chatMap.get(personId);
|
||||
}
|
||||
|
||||
public ChatInfo recreateChat(String personId) {
|
||||
final ChatInfo chatInfo = chatMap.get(personId);
|
||||
chatGptService.closeChat(chatInfo.getChatId());
|
||||
final ChatInfo newChat = chatGptService.createChat(
|
||||
CreateChat.builder()
|
||||
.systemBehavior(chatDefaultContext.get(personId))
|
||||
.build()
|
||||
);
|
||||
chatMap.put(personId, newChat);
|
||||
return newChat;
|
||||
}
|
||||
|
||||
public void setBehavior(String fromPersonId, String behavior) {
|
||||
chatDefaultContext.put(fromPersonId, behavior);
|
||||
final ChatInfo chatInfo = chatMap.get(fromPersonId);
|
||||
chatGptService.updateChat(
|
||||
UpdateChat.builder()
|
||||
.chatId(chatInfo.getChatId())
|
||||
.systemBehavior(behavior)
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
||||
}
|
118
src/main/java/dev/struchkov/example/bot/unit/BehaviorUnit.java
Normal file
118
src/main/java/dev/struchkov/example/bot/unit/BehaviorUnit.java
Normal file
@ -0,0 +1,118 @@
|
||||
package dev.struchkov.example.bot.unit;
|
||||
|
||||
import dev.struchkov.example.bot.conf.AppProperty;
|
||||
import dev.struchkov.example.bot.service.PersonalChatService;
|
||||
import dev.struchkov.example.bot.util.Cmd;
|
||||
import dev.struchkov.example.bot.util.UnitName;
|
||||
import dev.struchkov.godfather.main.domain.annotation.Unit;
|
||||
import dev.struchkov.godfather.main.domain.content.Attachment;
|
||||
import dev.struchkov.godfather.main.domain.content.Mail;
|
||||
import dev.struchkov.godfather.simple.domain.BoxAnswer;
|
||||
import dev.struchkov.godfather.simple.domain.unit.AnswerText;
|
||||
import dev.struchkov.godfather.telegram.domain.attachment.CommandAttachment;
|
||||
import dev.struchkov.godfather.telegram.main.core.util.Attachments;
|
||||
import dev.struchkov.godfather.telegram.starter.PersonUnitConfiguration;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import static dev.struchkov.godfather.simple.domain.BoxAnswer.boxAnswer;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class BehaviorUnit implements PersonUnitConfiguration {
|
||||
|
||||
private final PersonalChatService personalChatService;
|
||||
private final AppProperty appProperty;
|
||||
|
||||
@Unit(value = UnitName.BEHAVIOR, global = true)
|
||||
public AnswerText<Mail> behavior() {
|
||||
return AnswerText.<Mail>builder()
|
||||
.triggerCheck(
|
||||
mail -> {
|
||||
if (appProperty.getTelegramIds().contains(mail.getFromPersonId())) {
|
||||
final List<Attachment> attachments = mail.getAttachments();
|
||||
final Optional<CommandAttachment> optCommand = Attachments.findFirstCommand(attachments);
|
||||
if (optCommand.isPresent()) {
|
||||
final CommandAttachment command = optCommand.get();
|
||||
return Cmd.BEHAVIOR.equals(command.getCommandType());
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
)
|
||||
.answer(mail -> {
|
||||
final CommandAttachment command = Attachments.findFirstCommand(mail.getAttachments()).orElseThrow();
|
||||
final Optional<String> optArg = command.getArg();
|
||||
if (optArg.isEmpty()) {
|
||||
return BoxAnswer.builder()
|
||||
.message(
|
||||
"""
|
||||
Allows you to set the ChatGPT behavior for chat. Remains active when the context is cleared.
|
||||
|
||||
If you want to set your own behavior, then send the command:
|
||||
|
||||
/behavior description_behavior
|
||||
|
||||
Or choose one of the available options:
|
||||
|
||||
1. Гопник
|
||||
Представь, что ты славянский гопник и общаешься со своими собутыльниками по подъезду. Используй побольше мата и блатного жаргона. Используй как можно больше «бля» и «ёпта». Отвечай в таком стиле всегда, какой бы вопрос не задали пользователи в этом чате.
|
||||
👉 /behavior1
|
||||
|
||||
2. Copilot
|
||||
Ты - помощник для программистов. На любой вопрос отвечай с примерами кода, если нужно и присылай советы по улучшению кода
|
||||
👉 /behavior2
|
||||
|
||||
3. Linux
|
||||
Я хочу, чтобы вы выступали в роли терминала Linux. Я буду вводить команды, а вы будете отвечать тем, что должен показать терминал. Я хочу, чтобы вы ответили выводом терминала только внутри одного уникального блока кода, и ничего больше. не пишите пояснений. не вводите команды, если я не поручу вам это сделать. Когда мне нужно будет сказать вам что-то на русском языке, я буду заключать текст в фигурные скобки {вот так}.
|
||||
👉 /behavior3
|
||||
|
||||
4. Тренера по взаимоотношениям
|
||||
Я хочу, чтобы вы выступили в роли тренера по взаимоотношениям. Я предоставлю некоторые подробности о двух людях, вовлеченных в конфликт, а ваша задача - предложить, как они могут решить проблемы, которые их разделяют. Это могут быть советы по технике общения или различные стратегии для улучшения понимания ими точек зрения друг друга. Первый запрос: "Мне нужна помощь в разрешении конфликтов между мной и моим парнем".
|
||||
👉 /behavior4
|
||||
|
||||
5. Наставник
|
||||
Вы наставник, который всегда отвечает в сократовском стиле. Вы *никогда* не даете ученику ответа, но всегда стараетесь задать правильный вопрос, чтобы помочь ему научиться думать самостоятельно. Вы всегда должны согласовывать свой вопрос с интересами и знаниями учащегося, разбивая проблему на более простые части, пока она не достигнет нужного для них уровня.
|
||||
👉 /behavior5
|
||||
|
||||
6. В двух словах
|
||||
Отвечай максимально коротко, даже если тебя просят ответить развернуто. Весь ответ должен уложиться в пару предложений.
|
||||
👉 /behavior6
|
||||
"""
|
||||
)
|
||||
.build();
|
||||
} else {
|
||||
final String behavior = optArg.get();
|
||||
personalChatService.setBehavior(mail.getFromPersonId(), behavior);
|
||||
return boxAnswer("\uD83D\uDC4C");
|
||||
}
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
// @Unit(value = UnitName.BEHAVIOUR, global = true)
|
||||
// public AnswerText<Mail> behaviour1() {
|
||||
// return AnswerText.<Mail>builder()
|
||||
// .triggerCheck(
|
||||
// mail -> {
|
||||
// if (appProperty.getTelegramIds().contains(mail.getFromPersonId())) {
|
||||
// final List<Attachment> attachments = mail.getAttachments();
|
||||
// final Optional<CommandAttachment> optCommand = Attachments.findFirstCommand(attachments);
|
||||
// if (optCommand.isPresent()) {
|
||||
// final CommandAttachment command = optCommand.get();
|
||||
// return Cmd.BEHAVIOUR_1.equals(command.getCommandType());
|
||||
// }
|
||||
// }
|
||||
// return false;
|
||||
// }
|
||||
// )
|
||||
// .answer(() -> {
|
||||
//
|
||||
// })
|
||||
// .build();
|
||||
// }
|
||||
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
package dev.struchkov.example.bot.unit;
|
||||
|
||||
import dev.struchkov.example.bot.conf.AppProperty;
|
||||
import dev.struchkov.example.bot.service.PersonalChatService;
|
||||
import dev.struchkov.example.bot.util.Cmd;
|
||||
import dev.struchkov.example.bot.util.UnitName;
|
||||
import dev.struchkov.godfather.main.domain.annotation.Unit;
|
||||
@ -8,6 +9,7 @@ import dev.struchkov.godfather.main.domain.content.Attachment;
|
||||
import dev.struchkov.godfather.main.domain.content.Mail;
|
||||
import dev.struchkov.godfather.main.domain.keyboard.button.SimpleButton;
|
||||
import dev.struchkov.godfather.simple.domain.BoxAnswer;
|
||||
import dev.struchkov.godfather.simple.domain.SentBox;
|
||||
import dev.struchkov.godfather.simple.domain.unit.AnswerText;
|
||||
import dev.struchkov.godfather.telegram.domain.ChatAction;
|
||||
import dev.struchkov.godfather.telegram.domain.ClientBotCommand;
|
||||
@ -24,65 +26,57 @@ import dev.struchkov.openai.context.ChatGptService;
|
||||
import dev.struchkov.openai.context.GPTClient;
|
||||
import dev.struchkov.openai.domain.chat.ChatInfo;
|
||||
import dev.struchkov.openai.domain.common.GptMessage;
|
||||
import dev.struchkov.openai.domain.message.AnswerChatMessage;
|
||||
import dev.struchkov.openai.domain.model.gpt.GPT3Model;
|
||||
import dev.struchkov.openai.domain.request.GptRequest;
|
||||
import dev.struchkov.openai.domain.response.Choice;
|
||||
import dev.struchkov.openai.domain.response.GptResponse;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.text.MessageFormat;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import static dev.struchkov.example.bot.util.UnitName.CLEAR_CONTEXT;
|
||||
import static dev.struchkov.example.bot.util.UnitName.GPT_UNIT;
|
||||
import static dev.struchkov.godfather.simple.domain.BoxAnswer.boxAnswer;
|
||||
import static dev.struchkov.godfather.simple.domain.BoxAnswer.replaceBoxAnswer;
|
||||
import static dev.struchkov.godfather.telegram.main.context.BoxAnswerPayload.DISABLE_WEB_PAGE_PREVIEW;
|
||||
import static dev.struchkov.godfather.telegram.main.context.BoxAnswerPayload.ENABLE_MARKDOWN;
|
||||
import static java.text.MessageFormat.format;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class PersonalChatGPTUnit implements PersonUnitConfiguration {
|
||||
|
||||
private ChatInfo chatInfo;
|
||||
|
||||
private final PersonalChatService personalChatService;
|
||||
private final TelegramSending telegramSending;
|
||||
private final TelegramService telegramService;
|
||||
private final AppProperty appProperty;
|
||||
private final GPTClient gptClient;
|
||||
private final ChatGptService chatGptService;
|
||||
|
||||
public PersonalChatGPTUnit(
|
||||
TelegramSending telegramSending,
|
||||
TelegramService telegramService,
|
||||
GPTClient gptClient,
|
||||
AppProperty appProperty,
|
||||
ChatGptService chatGptService
|
||||
) {
|
||||
this.telegramSending = telegramSending;
|
||||
this.telegramService = telegramService;
|
||||
this.appProperty = appProperty;
|
||||
this.gptClient = gptClient;
|
||||
this.chatGptService = chatGptService;
|
||||
this.chatInfo = chatGptService.createChat();
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void createCommands() {
|
||||
telegramService.addCommand(List.of(
|
||||
ClientBotCommand.builder()
|
||||
.key(Cmd.CLEAR_CONTEXT)
|
||||
.description("Clears the discussion context. Start a conversation from the beginning")
|
||||
.build(),
|
||||
|
||||
ClientBotCommand.builder()
|
||||
.key(Cmd.HELP)
|
||||
.description("help in use")
|
||||
.build(),
|
||||
|
||||
ClientBotCommand.builder()
|
||||
.key(Cmd.CLEAR_CONTEXT)
|
||||
.description("Clears the discussion context. Start a conversation from the beginning.")
|
||||
.build(),
|
||||
|
||||
ClientBotCommand.builder()
|
||||
.key(Cmd.BEHAVIOR)
|
||||
.description("Allows you to set the initial behavior of ChatGPT.")
|
||||
.build(),
|
||||
|
||||
ClientBotCommand.builder()
|
||||
.key(Cmd.SUPPORT_DEV)
|
||||
.description("Support project development")
|
||||
.description("Support project development.")
|
||||
.build()
|
||||
));
|
||||
}
|
||||
@ -90,7 +84,7 @@ public class PersonalChatGPTUnit implements PersonUnitConfiguration {
|
||||
@Unit(value = UnitName.ACCESS_ERROR, main = true)
|
||||
public AnswerText<Mail> accessError() {
|
||||
return AnswerText.<Mail>builder()
|
||||
.triggerCheck(mail -> !mail.getFromPersonId().equals(appProperty.getTelegramId()))
|
||||
.triggerCheck(mail -> !appProperty.getTelegramIds().contains(mail.getFromPersonId()))
|
||||
.answer(message -> {
|
||||
final StringBuilder messageText = new StringBuilder("\uD83D\uDEA8 *Attempted unauthorized access to the bot*")
|
||||
.append("\n-- -- -- -- --\n");
|
||||
@ -99,32 +93,32 @@ public class PersonalChatGPTUnit implements PersonUnitConfiguration {
|
||||
.ifPresent(username -> messageText.append("\uD83E\uDDB9\u200D♂️: @").append(username));
|
||||
|
||||
messageText.append("\n")
|
||||
.append("\uD83D\uDCAC: ").append(message.getText())
|
||||
.toString();
|
||||
.append("\uD83D\uDCAC: ").append(message.getText());
|
||||
|
||||
return BoxAnswer.builder()
|
||||
.recipientPersonId(appProperty.getTelegramId())
|
||||
.recipientPersonId(appProperty.getTelegramIds().get(0))
|
||||
.message(messageText.toString())
|
||||
.build();
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
@Unit(value = GPT_UNIT, main = true)
|
||||
@Unit(value = GPT_UNIT, global = true)
|
||||
public AnswerText<Mail> chatGpt() {
|
||||
return AnswerText.<Mail>builder()
|
||||
.triggerCheck(mail -> {
|
||||
if (mail.getFromPersonId().equals(appProperty.getTelegramId())) {
|
||||
if (appProperty.getTelegramIds().contains(mail.getFromPersonId())) {
|
||||
final Optional<CommandAttachment> firstCommand = Attachments.findFirstCommand(mail.getAttachments());
|
||||
return firstCommand.isEmpty();
|
||||
}
|
||||
return false;
|
||||
})
|
||||
.answer(message -> {
|
||||
final ChatInfo chatInfo = personalChatService.getChatByPersonId(message.getFromPersonId());
|
||||
final long countMessages = chatGptService.getCountMessages(chatInfo.getChatId());
|
||||
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
builder.append("Wait... Response is being generated...\nIt might take a long time ⏳");
|
||||
telegramService.executeAction(message.getFromPersonId(), ChatAction.TYPING);
|
||||
|
||||
if (countMessages > 40) {
|
||||
builder.append(Strings.escapeMarkdown("\n-- -- -- -- --\nWe recommend periodically clearing the conversation context (/clear_context). If this is not done, then the memory resources on your PC will run out."));
|
||||
@ -134,20 +128,27 @@ public class PersonalChatGPTUnit implements PersonUnitConfiguration {
|
||||
.recipientPersonId(message.getFromPersonId())
|
||||
.message(builder.toString())
|
||||
.build();
|
||||
telegramSending.send(answerWait);
|
||||
final String answerText = chatGptService.sendNewMessage(chatInfo.getChatId(), message.getText());
|
||||
return replaceBoxAnswer(answerText);
|
||||
final Optional<SentBox> optSentBox = telegramSending.send(answerWait);
|
||||
|
||||
telegramService.executeAction(message.getFromPersonId(), ChatAction.TYPING);
|
||||
|
||||
final AnswerChatMessage answer = chatGptService.sendNewMessage(chatInfo.getChatId(), message.getText());
|
||||
if (optSentBox.isPresent()) {
|
||||
final SentBox sentBox = optSentBox.get();
|
||||
telegramSending.replaceMessage(sentBox.getPersonId(), sentBox.getMessageId(), boxAnswer(format("\uD83D\uDC47 Answer received. Request cost: {0} tokens", answer.getUsage().getTotalTokens())));
|
||||
}
|
||||
return boxAnswer(answer.getMessage());
|
||||
})
|
||||
.priority(5)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Unit(value = CLEAR_CONTEXT, main = true)
|
||||
@Unit(value = CLEAR_CONTEXT, global = true)
|
||||
public AnswerText<Mail> clearContext() {
|
||||
return AnswerText.<Mail>builder()
|
||||
.triggerCheck(
|
||||
mail -> {
|
||||
if (mail.getFromPersonId().equals(appProperty.getTelegramId())) {
|
||||
if (appProperty.getTelegramIds().contains(mail.getFromPersonId())) {
|
||||
final List<Attachment> attachments = mail.getAttachments();
|
||||
final Optional<CommandAttachment> optCommand = Attachments.findFirstCommand(attachments);
|
||||
if (optCommand.isPresent()) {
|
||||
@ -159,19 +160,18 @@ public class PersonalChatGPTUnit implements PersonUnitConfiguration {
|
||||
}
|
||||
)
|
||||
.answer(message -> {
|
||||
chatGptService.closeChat(chatInfo.getChatId());
|
||||
chatInfo = chatGptService.createChat();
|
||||
personalChatService.recreateChat(message.getFromPersonId());
|
||||
return boxAnswer("\uD83E\uDDF9 Discussion context cleared successfully");
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
@Unit(value = UnitName.START, main = true)
|
||||
@Unit(value = UnitName.START, global = true)
|
||||
public AnswerText<Mail> startMessage() {
|
||||
return AnswerText.<Mail>builder()
|
||||
.triggerCheck(
|
||||
mail -> {
|
||||
if (mail.getFromPersonId().equals(appProperty.getTelegramId())) {
|
||||
if (appProperty.getTelegramIds().contains(mail.getFromPersonId())) {
|
||||
final List<Attachment> attachments = mail.getAttachments();
|
||||
final Optional<CommandAttachment> optCommand = Attachments.findFirstCommand(attachments);
|
||||
if (optCommand.isPresent()) {
|
||||
@ -184,7 +184,7 @@ public class PersonalChatGPTUnit implements PersonUnitConfiguration {
|
||||
)
|
||||
.answer(message -> {
|
||||
return BoxAnswer.builder()
|
||||
.message(MessageFormat.format(
|
||||
.message(format(
|
||||
"""
|
||||
Hello 👋
|
||||
Your personal ChatGPT bot has been successfully launched.
|
||||
@ -198,18 +198,19 @@ public class PersonalChatGPTUnit implements PersonUnitConfiguration {
|
||||
appProperty.getVersion()
|
||||
))
|
||||
.keyBoard(InlineKeyBoard.inlineKeyBoard(SimpleButton.simpleButton("❤️ Support Develop", "support")))
|
||||
.payload(DISABLE_WEB_PAGE_PREVIEW, true)
|
||||
.payload(DISABLE_WEB_PAGE_PREVIEW)
|
||||
.payload(ENABLE_MARKDOWN)
|
||||
.build();
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
@Unit(value = UnitName.PROMPT, main = true)
|
||||
@Unit(value = UnitName.PROMPT, global = true)
|
||||
public AnswerText<Mail> prompt() {
|
||||
return AnswerText.<Mail>builder()
|
||||
.triggerCheck(
|
||||
mail -> {
|
||||
if (mail.getFromPersonId().equals(appProperty.getTelegramId())) {
|
||||
if (appProperty.getTelegramIds().contains(mail.getFromPersonId())) {
|
||||
final List<Attachment> attachments = mail.getAttachments();
|
||||
final Optional<CommandAttachment> optCommand = Attachments.findFirstCommand(attachments);
|
||||
if (optCommand.isPresent()) {
|
||||
@ -221,19 +222,38 @@ public class PersonalChatGPTUnit implements PersonUnitConfiguration {
|
||||
}
|
||||
)
|
||||
.answer(
|
||||
mail -> {
|
||||
final CommandAttachment promptCommand = Attachments.findFirstCommand(mail.getAttachments()).get();
|
||||
message -> {
|
||||
final CommandAttachment promptCommand = Attachments.findFirstCommand(message.getAttachments()).get();
|
||||
final Optional<String> optPrompt = promptCommand.getArg();
|
||||
if (optPrompt.isPresent()) {
|
||||
final String prompt = optPrompt.get();
|
||||
|
||||
final BoxAnswer answerWait = BoxAnswer.builder()
|
||||
.recipientPersonId(message.getFromPersonId())
|
||||
.message("Wait... Response is being generated...\nIt might take a long time ⏳")
|
||||
.build();
|
||||
final Optional<SentBox> optSentBox = telegramSending.send(answerWait);
|
||||
|
||||
telegramService.executeAction(message.getFromPersonId(), ChatAction.TYPING);
|
||||
|
||||
final GptResponse gptResponse = gptClient.execute(
|
||||
GptRequest.builder()
|
||||
.model(GPT3Model.GPT_3_5_TURBO)
|
||||
.message(GptMessage.fromUser(prompt))
|
||||
.build()
|
||||
);
|
||||
|
||||
if (optSentBox.isPresent()) {
|
||||
final SentBox sentBox = optSentBox.get();
|
||||
telegramSending.replaceMessage(sentBox.getPersonId(), sentBox.getMessageId(), boxAnswer(format("\uD83D\uDC47 Answer received. Request cost: {0} tokens", gptResponse.getUsage().getTotalTokens())));
|
||||
}
|
||||
|
||||
final List<Choice> choices = gptResponse.getChoices();
|
||||
return boxAnswer(choices.get(choices.size() - 1).getMessage().getContent());
|
||||
final String answer = choices.get(choices.size() - 1).getMessage().getContent();
|
||||
return BoxAnswer.builder()
|
||||
.message(answer)
|
||||
.payload(ENABLE_MARKDOWN)
|
||||
.build();
|
||||
}
|
||||
return BoxAnswer.builder().build();
|
||||
}
|
||||
@ -241,12 +261,12 @@ public class PersonalChatGPTUnit implements PersonUnitConfiguration {
|
||||
.build();
|
||||
}
|
||||
|
||||
@Unit(value = UnitName.SUPPORT, main = true)
|
||||
@Unit(value = UnitName.SUPPORT, global = true)
|
||||
public AnswerText<Mail> support() {
|
||||
return AnswerText.<Mail>builder()
|
||||
.triggerCheck(
|
||||
mail -> {
|
||||
if (mail.getFromPersonId().equals(appProperty.getTelegramId())) {
|
||||
if (appProperty.getTelegramIds().contains(mail.getFromPersonId())) {
|
||||
final List<Attachment> attachments = mail.getAttachments();
|
||||
final Optional<CommandAttachment> optCommand = Attachments.findFirstCommand(attachments);
|
||||
if (optCommand.isPresent()) {
|
||||
@ -264,34 +284,37 @@ public class PersonalChatGPTUnit implements PersonUnitConfiguration {
|
||||
}
|
||||
)
|
||||
.answer(
|
||||
boxAnswer("""
|
||||
❤️ *Support Develop*
|
||||
() -> BoxAnswer.builder()
|
||||
.message("""
|
||||
❤️ *Support Develop*
|
||||
|
||||
Sponsorship makes a project sustainable because it pays for the time of the maintainers of that project, a very scarce resource that is spent on developing new features, fixing bugs, improving stability, solving problems, and general support. *The biggest bottleneck in Open Source is time.*
|
||||
Sponsorship makes a project sustainable because it pays for the time of the maintainers of that project, a very scarce resource that is spent on developing new features, fixing bugs, improving stability, solving problems, and general support. *The biggest bottleneck in Open Source is time.*
|
||||
|
||||
Bank card (Russia): [https://www.tinkoff.ru/cf/4iU6NB3uzqx](https://www.tinkoff.ru/cf/4iU6NB3uzqx)
|
||||
Bank card (Russia): [https://www.tinkoff.ru/cf/4iU6NB3uzqx](https://www.tinkoff.ru/cf/4iU6NB3uzqx)
|
||||
|
||||
TON: `struchkov-mark.ton`
|
||||
TON: `struchkov-mark.ton`
|
||||
|
||||
BTC:
|
||||
`bc1pt49vnp43c4mktk6309zlq3020dzd0p89gc8d90zzn4sgjvck56xs0t86vy`
|
||||
BTC:
|
||||
`bc1pt49vnp43c4mktk6309zlq3020dzd0p89gc8d90zzn4sgjvck56xs0t86vy`
|
||||
|
||||
ETH (USDT, DAI, USDC):
|
||||
`0x7668C802Bd71Be965671D4Bbb1AD90C7f7f32921`
|
||||
ETH (USDT, DAI, USDC):
|
||||
`0x7668C802Bd71Be965671D4Bbb1AD90C7f7f32921`
|
||||
|
||||
BNB (USDT, DAI, USDC):
|
||||
`0xDa41aC95f606850f2E01ba775e521Cd385AA7D03`
|
||||
""")
|
||||
BNB (USDT, DAI, USDC):
|
||||
`0xDa41aC95f606850f2E01ba775e521Cd385AA7D03`
|
||||
""")
|
||||
.payload(ENABLE_MARKDOWN)
|
||||
.build()
|
||||
)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Unit(value = UnitName.HELP, main = true)
|
||||
@Unit(value = UnitName.HELP, global = true)
|
||||
public AnswerText<Mail> help() {
|
||||
return AnswerText.<Mail>builder()
|
||||
.triggerCheck(
|
||||
mail -> {
|
||||
if (mail.getFromPersonId().equals(appProperty.getTelegramId())) {
|
||||
if (appProperty.getTelegramIds().contains(mail.getFromPersonId())) {
|
||||
final List<Attachment> attachments = mail.getAttachments();
|
||||
final Optional<CommandAttachment> optCommand = Attachments.findFirstCommand(attachments);
|
||||
if (optCommand.isPresent()) {
|
||||
@ -302,7 +325,7 @@ public class PersonalChatGPTUnit implements PersonUnitConfiguration {
|
||||
return false;
|
||||
}
|
||||
)
|
||||
.answer(boxAnswer(Strings.escapeMarkdown("""
|
||||
.answer(() -> boxAnswer("""
|
||||
All correspondence is conducted within one chat. This allows ChatGPT to understand the context of the questions. The context is 100 messages (questions and answers).
|
||||
|
||||
Available commands:
|
||||
@ -310,7 +333,7 @@ public class PersonalChatGPTUnit implements PersonUnitConfiguration {
|
||||
/clear_context - Clears the conversation context. In fact, it deletes the chat and creates a new one.
|
||||
|
||||
/prompt your_question - Allows you to ask a question outside the context of the main conversation.
|
||||
""")))
|
||||
"""))
|
||||
.build();
|
||||
}
|
||||
|
||||
|
@ -19,6 +19,7 @@ import java.text.MessageFormat;
|
||||
import java.util.Optional;
|
||||
|
||||
import static dev.struchkov.godfather.telegram.main.context.BoxAnswerPayload.DISABLE_WEB_PAGE_PREVIEW;
|
||||
import static dev.struchkov.godfather.telegram.main.context.BoxAnswerPayload.ENABLE_MARKDOWN;
|
||||
import static dev.struchkov.haiti.utils.Checker.checkNotBlank;
|
||||
|
||||
@Slf4j
|
||||
@ -34,26 +35,29 @@ public class StartNotify {
|
||||
|
||||
@PostConstruct
|
||||
public void sendStartNotify() {
|
||||
final BoxAnswer boxAnswer = BoxAnswer.builder()
|
||||
.message(MessageFormat.format(
|
||||
"""
|
||||
Hello 👋
|
||||
Your personal ChatGPT bot has been successfully launched.
|
||||
for (String telegramId : appProperty.getTelegramIds()) {
|
||||
final BoxAnswer boxAnswer = BoxAnswer.builder()
|
||||
.message(MessageFormat.format(
|
||||
"""
|
||||
Hello 👋
|
||||
Your personal ChatGPT bot has been successfully launched.
|
||||
|
||||
Use the help command to find out about the possibilities 🚀
|
||||
-- -- -- -- --
|
||||
🤘 Version: {0}
|
||||
👨💻 Developer: [Struchkov Mark](https://mark.struchkov.dev/)
|
||||
💊 Docs: https://docs.struchkov.dev/chatgpt-telegram-bot
|
||||
""",
|
||||
appProperty.getVersion()
|
||||
))
|
||||
.keyBoard(InlineKeyBoard.inlineKeyBoard(SimpleButton.simpleButton("❤️ Support Develop", "support")))
|
||||
.payload(DISABLE_WEB_PAGE_PREVIEW, true)
|
||||
.build();
|
||||
boxAnswer.setRecipientIfNull(appProperty.getTelegramId());
|
||||
sending.send(boxAnswer);
|
||||
sendNotice();
|
||||
Use the help command to find out about the possibilities 🚀
|
||||
-- -- -- -- --
|
||||
🤘 Version: {0}
|
||||
👨💻 Developer: [Struchkov Mark](https://mark.struchkov.dev/)
|
||||
💊 Docs: https://docs.struchkov.dev/chatgpt-telegram-bot
|
||||
""",
|
||||
appProperty.getVersion()
|
||||
))
|
||||
.keyBoard(InlineKeyBoard.inlineKeyBoard(SimpleButton.simpleButton("❤️ Support Develop", "support")))
|
||||
.payload(DISABLE_WEB_PAGE_PREVIEW, true)
|
||||
.payload(ENABLE_MARKDOWN)
|
||||
.build();
|
||||
boxAnswer.setRecipientIfNull(telegramId);
|
||||
sending.send(boxAnswer);
|
||||
sendNotice();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -67,16 +71,19 @@ public class StartNotify {
|
||||
if (response.code() == 200) {
|
||||
final String noticeMessage = response.body().string();
|
||||
if (checkNotBlank(noticeMessage)) {
|
||||
final BoxAnswer notice = BoxAnswer.builder()
|
||||
.message(noticeMessage)
|
||||
.recipientPersonId(appProperty.getTelegramId())
|
||||
.payload(DISABLE_WEB_PAGE_PREVIEW, true)
|
||||
.build();
|
||||
final Optional<SentBox> optSentBox = sending.send(notice);
|
||||
if (optSentBox.isPresent()) {
|
||||
final SentBox sentBox = optSentBox.get();
|
||||
final String messageId = sentBox.getMessageId();
|
||||
telegramService.pinMessage(appProperty.getTelegramId(), messageId);
|
||||
for (String telegramId : appProperty.getTelegramIds()) {
|
||||
final BoxAnswer notice = BoxAnswer.builder()
|
||||
.message(noticeMessage)
|
||||
.recipientPersonId(telegramId)
|
||||
.payload(DISABLE_WEB_PAGE_PREVIEW)
|
||||
.payload(ENABLE_MARKDOWN)
|
||||
.build();
|
||||
final Optional<SentBox> optSentBox = sending.send(notice);
|
||||
if (optSentBox.isPresent()) {
|
||||
final SentBox sentBox = optSentBox.get();
|
||||
final String messageId = sentBox.getMessageId();
|
||||
telegramService.pinMessage(telegramId, messageId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,4 +12,12 @@ public class Cmd {
|
||||
public static final String SUPPORT_DEV = "support";
|
||||
public static final String START = "start";
|
||||
|
||||
public static final String BEHAVIOR = "behavior";
|
||||
public static final String BEHAVIOR_1 = "behavior1";
|
||||
public static final String BEHAVIOR_2 = "behavior2";
|
||||
public static final String BEHAVIOR_3 = "behavior3";
|
||||
public static final String BEHAVIOR_4 = "behavior4";
|
||||
public static final String BEHAVIOR_5 = "behavior5";
|
||||
public static final String BEHAVIOR_6 = "behavior6";
|
||||
|
||||
}
|
||||
|
@ -12,6 +12,6 @@ public class UnitName {
|
||||
public static final String ACCESS_ERROR = "ACCESS_ERROR";
|
||||
public static final String SUPPORT = "SUPPORT";
|
||||
public static final String START = "START";
|
||||
|
||||
public static final String BEHAVIOR = "BEHAVIOR";
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
app:
|
||||
telegram-id: ${TELEGRAM_PERSON_ID}
|
||||
telegram-ids: ${TELEGRAM_PERSON_ID}
|
||||
version: 0.0.1
|
||||
telegram:
|
||||
bot:
|
||||
|
Loading…
Reference in New Issue
Block a user