Behavior set, multi-account, inline mod, and other little things
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Struchkov Mark 2023-03-26 22:13:37 +03:00
parent 89c42edc25
commit e85e2c03bd
Signed by: upagge
GPG Key ID: D3018BE7BA428CA6
15 changed files with 541 additions and 118 deletions

View File

@ -34,10 +34,6 @@ steps:
- name: docker publish develop - name: docker publish develop
image: upagge/docker-buildx:latest image: upagge/docker-buildx:latest
environment: environment:
STRUCHKOV_DOCKER_REGISTRY_TOKEN:
from_secret: STRUCHKOV_DOCKER_REGISTRY_TOKEN
STRUCHKOV_DOCKER_IMAGE_NAME:
from_secret: STRUCHKOV_DOCKER_IMAGE_NAME
DOCKER_REGISTRY_TOKEN: DOCKER_REGISTRY_TOKEN:
from_secret: DOCKER_REGISTRY_TOKEN from_secret: DOCKER_REGISTRY_TOKEN
DOCKER_IMAGE_NAME: DOCKER_IMAGE_NAME:
@ -48,10 +44,9 @@ steps:
- name: dockersock - name: dockersock
path: /var/run path: /var/run
commands: 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 - echo "$DOCKER_REGISTRY_TOKEN" | docker login docker.io --username $DOCKER_REGISTRY_USER --password-stdin
- docker buildx create --use - 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 kind: pipeline
@ -203,6 +198,6 @@ volumes:
path: /drone/volume/mkdocs_cache/chatgpt-telegram-bot/en path: /drone/volume/mkdocs_cache/chatgpt-telegram-bot/en
--- ---
kind: signature kind: signature
hmac: 44ca201fe0ea4a7b739d9e170e0852171c5175c0128bfd3e77f7247f92db56a8 hmac: b615ac14d736c1ff2354f10d96117108866cd0bc8d387f827ea063fbe66b418c
... ...

10
pom.xml
View File

@ -15,8 +15,8 @@
<properties> <properties>
<java.version>17</java.version> <java.version>17</java.version>
<openai.api.version>0.0.1</openai.api.version> <openai.api.version>0.0.2-SNAPSHOT</openai.api.version>
<godfather.version>0.0.53</godfather.version> <godfather.version>0.0.54-SNAPSHOT</godfather.version>
<!-- https://mvnrepository.com/artifact/org.apache.maven.plugins/maven-compiler-plugin --> <!-- https://mvnrepository.com/artifact/org.apache.maven.plugins/maven-compiler-plugin -->
<plugin.maven.compiler.ver>3.10.1</plugin.maven.compiler.ver> <plugin.maven.compiler.ver>3.10.1</plugin.maven.compiler.ver>
@ -61,6 +61,12 @@
<artifactId>lombok</artifactId> <artifactId>lombok</artifactId>
<optional>true</optional> <optional>true</optional>
</dependency> </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> </dependencies>
<repositories> <repositories>

View File

@ -4,10 +4,10 @@ import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication @SpringBootApplication
public class SpringBootTelegramBotExampleApplication { public class ChatGptTelegramBot {
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(SpringBootTelegramBotExampleApplication.class, args); SpringApplication.run(ChatGptTelegramBot.class, args);
} }
} }

View File

@ -5,13 +5,15 @@ import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.List;
@Getter @Getter
@Setter @Setter
@Component @Component
@ConfigurationProperties("app") @ConfigurationProperties("app")
public class AppProperty { public class AppProperty {
private String telegramId; private List<String> telegramIds;
private String version; private String version;
} }

View File

@ -19,13 +19,21 @@ public class ErrorTelegramHandler implements ErrorHandler {
private final TelegramSending sending; private final TelegramSending sending;
@Override @Override
public void handle(Message message, Exception e) { public void handle(Message message, Throwable e) {
log.error(e.getMessage(), e); log.error(e.getMessage(), e);
final String errorMessage = escapeMarkdown(e.getMessage()); 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( sending.send(
BoxAnswer.builder() BoxAnswer.builder()
.message("Error message:\n\n" + errorMessage) .message("Error message:\n\n" + errorMessage)
.recipientPersonId(appProperty.getTelegramId()) .recipientPersonId(recipientTelegramId)
.build() .build()
); );
} }

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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));
}
}

View File

@ -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()
);
}
}

View 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();
// }
}

View File

@ -1,6 +1,7 @@
package dev.struchkov.example.bot.unit; package dev.struchkov.example.bot.unit;
import dev.struchkov.example.bot.conf.AppProperty; 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.Cmd;
import dev.struchkov.example.bot.util.UnitName; import dev.struchkov.example.bot.util.UnitName;
import dev.struchkov.godfather.main.domain.annotation.Unit; 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.content.Mail;
import dev.struchkov.godfather.main.domain.keyboard.button.SimpleButton; import dev.struchkov.godfather.main.domain.keyboard.button.SimpleButton;
import dev.struchkov.godfather.simple.domain.BoxAnswer; 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.simple.domain.unit.AnswerText;
import dev.struchkov.godfather.telegram.domain.ChatAction; import dev.struchkov.godfather.telegram.domain.ChatAction;
import dev.struchkov.godfather.telegram.domain.ClientBotCommand; 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.context.GPTClient;
import dev.struchkov.openai.domain.chat.ChatInfo; import dev.struchkov.openai.domain.chat.ChatInfo;
import dev.struchkov.openai.domain.common.GptMessage; 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.model.gpt.GPT3Model;
import dev.struchkov.openai.domain.request.GptRequest; import dev.struchkov.openai.domain.request.GptRequest;
import dev.struchkov.openai.domain.response.Choice; import dev.struchkov.openai.domain.response.Choice;
import dev.struchkov.openai.domain.response.GptResponse; import dev.struchkov.openai.domain.response.GptResponse;
import jakarta.annotation.PostConstruct; import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.text.MessageFormat;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import static dev.struchkov.example.bot.util.UnitName.CLEAR_CONTEXT; import static dev.struchkov.example.bot.util.UnitName.CLEAR_CONTEXT;
import static dev.struchkov.example.bot.util.UnitName.GPT_UNIT; 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.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.DISABLE_WEB_PAGE_PREVIEW;
import static dev.struchkov.godfather.telegram.main.context.BoxAnswerPayload.ENABLE_MARKDOWN;
import static java.text.MessageFormat.format;
@Component @Component
@RequiredArgsConstructor
public class PersonalChatGPTUnit implements PersonUnitConfiguration { public class PersonalChatGPTUnit implements PersonUnitConfiguration {
private ChatInfo chatInfo; private final PersonalChatService personalChatService;
private final TelegramSending telegramSending; private final TelegramSending telegramSending;
private final TelegramService telegramService; private final TelegramService telegramService;
private final AppProperty appProperty; private final AppProperty appProperty;
private final GPTClient gptClient; private final GPTClient gptClient;
private final ChatGptService chatGptService; 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 @PostConstruct
public void createCommands() { public void createCommands() {
telegramService.addCommand(List.of( telegramService.addCommand(List.of(
ClientBotCommand.builder()
.key(Cmd.CLEAR_CONTEXT)
.description("Clears the discussion context. Start a conversation from the beginning")
.build(),
ClientBotCommand.builder() ClientBotCommand.builder()
.key(Cmd.HELP) .key(Cmd.HELP)
.description("help in use") .description("help in use")
.build(), .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() ClientBotCommand.builder()
.key(Cmd.SUPPORT_DEV) .key(Cmd.SUPPORT_DEV)
.description("Support project development") .description("Support project development.")
.build() .build()
)); ));
} }
@ -90,7 +84,7 @@ public class PersonalChatGPTUnit implements PersonUnitConfiguration {
@Unit(value = UnitName.ACCESS_ERROR, main = true) @Unit(value = UnitName.ACCESS_ERROR, main = true)
public AnswerText<Mail> accessError() { public AnswerText<Mail> accessError() {
return AnswerText.<Mail>builder() return AnswerText.<Mail>builder()
.triggerCheck(mail -> !mail.getFromPersonId().equals(appProperty.getTelegramId())) .triggerCheck(mail -> !appProperty.getTelegramIds().contains(mail.getFromPersonId()))
.answer(message -> { .answer(message -> {
final StringBuilder messageText = new StringBuilder("\uD83D\uDEA8 *Attempted unauthorized access to the bot*") final StringBuilder messageText = new StringBuilder("\uD83D\uDEA8 *Attempted unauthorized access to the bot*")
.append("\n-- -- -- -- --\n"); .append("\n-- -- -- -- --\n");
@ -99,32 +93,32 @@ public class PersonalChatGPTUnit implements PersonUnitConfiguration {
.ifPresent(username -> messageText.append("\uD83E\uDDB9\u200D♂: @").append(username)); .ifPresent(username -> messageText.append("\uD83E\uDDB9\u200D♂: @").append(username));
messageText.append("\n") messageText.append("\n")
.append("\uD83D\uDCAC: ").append(message.getText()) .append("\uD83D\uDCAC: ").append(message.getText());
.toString();
return BoxAnswer.builder() return BoxAnswer.builder()
.recipientPersonId(appProperty.getTelegramId()) .recipientPersonId(appProperty.getTelegramIds().get(0))
.message(messageText.toString()) .message(messageText.toString())
.build(); .build();
}) })
.build(); .build();
} }
@Unit(value = GPT_UNIT, main = true) @Unit(value = GPT_UNIT, global = true)
public AnswerText<Mail> chatGpt() { public AnswerText<Mail> chatGpt() {
return AnswerText.<Mail>builder() return AnswerText.<Mail>builder()
.triggerCheck(mail -> { .triggerCheck(mail -> {
if (mail.getFromPersonId().equals(appProperty.getTelegramId())) { if (appProperty.getTelegramIds().contains(mail.getFromPersonId())) {
final Optional<CommandAttachment> firstCommand = Attachments.findFirstCommand(mail.getAttachments()); final Optional<CommandAttachment> firstCommand = Attachments.findFirstCommand(mail.getAttachments());
return firstCommand.isEmpty(); return firstCommand.isEmpty();
} }
return false; return false;
}) })
.answer(message -> { .answer(message -> {
final ChatInfo chatInfo = personalChatService.getChatByPersonId(message.getFromPersonId());
final long countMessages = chatGptService.getCountMessages(chatInfo.getChatId()); final long countMessages = chatGptService.getCountMessages(chatInfo.getChatId());
final StringBuilder builder = new StringBuilder(); final StringBuilder builder = new StringBuilder();
builder.append("Wait... Response is being generated...\nIt might take a long time ⏳"); builder.append("Wait... Response is being generated...\nIt might take a long time ⏳");
telegramService.executeAction(message.getFromPersonId(), ChatAction.TYPING);
if (countMessages > 40) { 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.")); 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()) .recipientPersonId(message.getFromPersonId())
.message(builder.toString()) .message(builder.toString())
.build(); .build();
telegramSending.send(answerWait); final Optional<SentBox> optSentBox = telegramSending.send(answerWait);
final String answerText = chatGptService.sendNewMessage(chatInfo.getChatId(), message.getText());
return replaceBoxAnswer(answerText); 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) .priority(5)
.build(); .build();
} }
@Unit(value = CLEAR_CONTEXT, main = true) @Unit(value = CLEAR_CONTEXT, global = true)
public AnswerText<Mail> clearContext() { public AnswerText<Mail> clearContext() {
return AnswerText.<Mail>builder() return AnswerText.<Mail>builder()
.triggerCheck( .triggerCheck(
mail -> { mail -> {
if (mail.getFromPersonId().equals(appProperty.getTelegramId())) { if (appProperty.getTelegramIds().contains(mail.getFromPersonId())) {
final List<Attachment> attachments = mail.getAttachments(); final List<Attachment> attachments = mail.getAttachments();
final Optional<CommandAttachment> optCommand = Attachments.findFirstCommand(attachments); final Optional<CommandAttachment> optCommand = Attachments.findFirstCommand(attachments);
if (optCommand.isPresent()) { if (optCommand.isPresent()) {
@ -159,19 +160,18 @@ public class PersonalChatGPTUnit implements PersonUnitConfiguration {
} }
) )
.answer(message -> { .answer(message -> {
chatGptService.closeChat(chatInfo.getChatId()); personalChatService.recreateChat(message.getFromPersonId());
chatInfo = chatGptService.createChat();
return boxAnswer("\uD83E\uDDF9 Discussion context cleared successfully"); return boxAnswer("\uD83E\uDDF9 Discussion context cleared successfully");
}) })
.build(); .build();
} }
@Unit(value = UnitName.START, main = true) @Unit(value = UnitName.START, global = true)
public AnswerText<Mail> startMessage() { public AnswerText<Mail> startMessage() {
return AnswerText.<Mail>builder() return AnswerText.<Mail>builder()
.triggerCheck( .triggerCheck(
mail -> { mail -> {
if (mail.getFromPersonId().equals(appProperty.getTelegramId())) { if (appProperty.getTelegramIds().contains(mail.getFromPersonId())) {
final List<Attachment> attachments = mail.getAttachments(); final List<Attachment> attachments = mail.getAttachments();
final Optional<CommandAttachment> optCommand = Attachments.findFirstCommand(attachments); final Optional<CommandAttachment> optCommand = Attachments.findFirstCommand(attachments);
if (optCommand.isPresent()) { if (optCommand.isPresent()) {
@ -184,7 +184,7 @@ public class PersonalChatGPTUnit implements PersonUnitConfiguration {
) )
.answer(message -> { .answer(message -> {
return BoxAnswer.builder() return BoxAnswer.builder()
.message(MessageFormat.format( .message(format(
""" """
Hello 👋 Hello 👋
Your personal ChatGPT bot has been successfully launched. Your personal ChatGPT bot has been successfully launched.
@ -198,18 +198,19 @@ public class PersonalChatGPTUnit implements PersonUnitConfiguration {
appProperty.getVersion() appProperty.getVersion()
)) ))
.keyBoard(InlineKeyBoard.inlineKeyBoard(SimpleButton.simpleButton("❤️ Support Develop", "support"))) .keyBoard(InlineKeyBoard.inlineKeyBoard(SimpleButton.simpleButton("❤️ Support Develop", "support")))
.payload(DISABLE_WEB_PAGE_PREVIEW, true) .payload(DISABLE_WEB_PAGE_PREVIEW)
.payload(ENABLE_MARKDOWN)
.build(); .build();
}) })
.build(); .build();
} }
@Unit(value = UnitName.PROMPT, main = true) @Unit(value = UnitName.PROMPT, global = true)
public AnswerText<Mail> prompt() { public AnswerText<Mail> prompt() {
return AnswerText.<Mail>builder() return AnswerText.<Mail>builder()
.triggerCheck( .triggerCheck(
mail -> { mail -> {
if (mail.getFromPersonId().equals(appProperty.getTelegramId())) { if (appProperty.getTelegramIds().contains(mail.getFromPersonId())) {
final List<Attachment> attachments = mail.getAttachments(); final List<Attachment> attachments = mail.getAttachments();
final Optional<CommandAttachment> optCommand = Attachments.findFirstCommand(attachments); final Optional<CommandAttachment> optCommand = Attachments.findFirstCommand(attachments);
if (optCommand.isPresent()) { if (optCommand.isPresent()) {
@ -221,19 +222,38 @@ public class PersonalChatGPTUnit implements PersonUnitConfiguration {
} }
) )
.answer( .answer(
mail -> { message -> {
final CommandAttachment promptCommand = Attachments.findFirstCommand(mail.getAttachments()).get(); final CommandAttachment promptCommand = Attachments.findFirstCommand(message.getAttachments()).get();
final Optional<String> optPrompt = promptCommand.getArg(); final Optional<String> optPrompt = promptCommand.getArg();
if (optPrompt.isPresent()) { if (optPrompt.isPresent()) {
final String prompt = optPrompt.get(); 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( final GptResponse gptResponse = gptClient.execute(
GptRequest.builder() GptRequest.builder()
.model(GPT3Model.GPT_3_5_TURBO) .model(GPT3Model.GPT_3_5_TURBO)
.message(GptMessage.fromUser(prompt)) .message(GptMessage.fromUser(prompt))
.build() .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(); 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(); return BoxAnswer.builder().build();
} }
@ -241,12 +261,12 @@ public class PersonalChatGPTUnit implements PersonUnitConfiguration {
.build(); .build();
} }
@Unit(value = UnitName.SUPPORT, main = true) @Unit(value = UnitName.SUPPORT, global = true)
public AnswerText<Mail> support() { public AnswerText<Mail> support() {
return AnswerText.<Mail>builder() return AnswerText.<Mail>builder()
.triggerCheck( .triggerCheck(
mail -> { mail -> {
if (mail.getFromPersonId().equals(appProperty.getTelegramId())) { if (appProperty.getTelegramIds().contains(mail.getFromPersonId())) {
final List<Attachment> attachments = mail.getAttachments(); final List<Attachment> attachments = mail.getAttachments();
final Optional<CommandAttachment> optCommand = Attachments.findFirstCommand(attachments); final Optional<CommandAttachment> optCommand = Attachments.findFirstCommand(attachments);
if (optCommand.isPresent()) { if (optCommand.isPresent()) {
@ -264,34 +284,37 @@ public class PersonalChatGPTUnit implements PersonUnitConfiguration {
} }
) )
.answer( .answer(
boxAnswer(""" () -> BoxAnswer.builder()
*Support Develop* .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: BTC:
`bc1pt49vnp43c4mktk6309zlq3020dzd0p89gc8d90zzn4sgjvck56xs0t86vy` `bc1pt49vnp43c4mktk6309zlq3020dzd0p89gc8d90zzn4sgjvck56xs0t86vy`
ETH (USDT, DAI, USDC): ETH (USDT, DAI, USDC):
`0x7668C802Bd71Be965671D4Bbb1AD90C7f7f32921` `0x7668C802Bd71Be965671D4Bbb1AD90C7f7f32921`
BNB (USDT, DAI, USDC): BNB (USDT, DAI, USDC):
`0xDa41aC95f606850f2E01ba775e521Cd385AA7D03` `0xDa41aC95f606850f2E01ba775e521Cd385AA7D03`
""") """)
.payload(ENABLE_MARKDOWN)
.build()
) )
.build(); .build();
} }
@Unit(value = UnitName.HELP, main = true) @Unit(value = UnitName.HELP, global = true)
public AnswerText<Mail> help() { public AnswerText<Mail> help() {
return AnswerText.<Mail>builder() return AnswerText.<Mail>builder()
.triggerCheck( .triggerCheck(
mail -> { mail -> {
if (mail.getFromPersonId().equals(appProperty.getTelegramId())) { if (appProperty.getTelegramIds().contains(mail.getFromPersonId())) {
final List<Attachment> attachments = mail.getAttachments(); final List<Attachment> attachments = mail.getAttachments();
final Optional<CommandAttachment> optCommand = Attachments.findFirstCommand(attachments); final Optional<CommandAttachment> optCommand = Attachments.findFirstCommand(attachments);
if (optCommand.isPresent()) { if (optCommand.isPresent()) {
@ -302,7 +325,7 @@ public class PersonalChatGPTUnit implements PersonUnitConfiguration {
return false; 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). 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: 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. /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. /prompt your_question - Allows you to ask a question outside the context of the main conversation.
"""))) """))
.build(); .build();
} }

View File

@ -19,6 +19,7 @@ import java.text.MessageFormat;
import java.util.Optional; 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.DISABLE_WEB_PAGE_PREVIEW;
import static dev.struchkov.godfather.telegram.main.context.BoxAnswerPayload.ENABLE_MARKDOWN;
import static dev.struchkov.haiti.utils.Checker.checkNotBlank; import static dev.struchkov.haiti.utils.Checker.checkNotBlank;
@Slf4j @Slf4j
@ -34,26 +35,29 @@ public class StartNotify {
@PostConstruct @PostConstruct
public void sendStartNotify() { public void sendStartNotify() {
final BoxAnswer boxAnswer = BoxAnswer.builder() for (String telegramId : appProperty.getTelegramIds()) {
.message(MessageFormat.format( final BoxAnswer boxAnswer = BoxAnswer.builder()
""" .message(MessageFormat.format(
Hello 👋 """
Your personal ChatGPT bot has been successfully launched. Hello 👋
Your personal ChatGPT bot has been successfully launched.
Use the help command to find out about the possibilities 🚀 Use the help command to find out about the possibilities 🚀
-- -- -- -- -- -- -- -- -- --
🤘 Version: {0} 🤘 Version: {0}
👨💻 Developer: [Struchkov Mark](https://mark.struchkov.dev/) 👨💻 Developer: [Struchkov Mark](https://mark.struchkov.dev/)
💊 Docs: https://docs.struchkov.dev/chatgpt-telegram-bot 💊 Docs: https://docs.struchkov.dev/chatgpt-telegram-bot
""", """,
appProperty.getVersion() appProperty.getVersion()
)) ))
.keyBoard(InlineKeyBoard.inlineKeyBoard(SimpleButton.simpleButton("❤️ Support Develop", "support"))) .keyBoard(InlineKeyBoard.inlineKeyBoard(SimpleButton.simpleButton("❤️ Support Develop", "support")))
.payload(DISABLE_WEB_PAGE_PREVIEW, true) .payload(DISABLE_WEB_PAGE_PREVIEW, true)
.build(); .payload(ENABLE_MARKDOWN)
boxAnswer.setRecipientIfNull(appProperty.getTelegramId()); .build();
sending.send(boxAnswer); boxAnswer.setRecipientIfNull(telegramId);
sendNotice(); sending.send(boxAnswer);
sendNotice();
}
} }
/** /**
@ -67,16 +71,19 @@ public class StartNotify {
if (response.code() == 200) { if (response.code() == 200) {
final String noticeMessage = response.body().string(); final String noticeMessage = response.body().string();
if (checkNotBlank(noticeMessage)) { if (checkNotBlank(noticeMessage)) {
final BoxAnswer notice = BoxAnswer.builder() for (String telegramId : appProperty.getTelegramIds()) {
.message(noticeMessage) final BoxAnswer notice = BoxAnswer.builder()
.recipientPersonId(appProperty.getTelegramId()) .message(noticeMessage)
.payload(DISABLE_WEB_PAGE_PREVIEW, true) .recipientPersonId(telegramId)
.build(); .payload(DISABLE_WEB_PAGE_PREVIEW)
final Optional<SentBox> optSentBox = sending.send(notice); .payload(ENABLE_MARKDOWN)
if (optSentBox.isPresent()) { .build();
final SentBox sentBox = optSentBox.get(); final Optional<SentBox> optSentBox = sending.send(notice);
final String messageId = sentBox.getMessageId(); if (optSentBox.isPresent()) {
telegramService.pinMessage(appProperty.getTelegramId(), messageId); final SentBox sentBox = optSentBox.get();
final String messageId = sentBox.getMessageId();
telegramService.pinMessage(telegramId, messageId);
}
} }
} }
} }

View File

@ -12,4 +12,12 @@ public class Cmd {
public static final String SUPPORT_DEV = "support"; public static final String SUPPORT_DEV = "support";
public static final String START = "start"; 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";
} }

View File

@ -12,6 +12,6 @@ public class UnitName {
public static final String ACCESS_ERROR = "ACCESS_ERROR"; public static final String ACCESS_ERROR = "ACCESS_ERROR";
public static final String SUPPORT = "SUPPORT"; public static final String SUPPORT = "SUPPORT";
public static final String START = "START"; public static final String START = "START";
public static final String BEHAVIOR = "BEHAVIOR";
} }

View File

@ -1,5 +1,5 @@
app: app:
telegram-id: ${TELEGRAM_PERSON_ID} telegram-ids: ${TELEGRAM_PERSON_ID}
version: 0.0.1 version: 0.0.1
telegram: telegram:
bot: bot: