Compare commits
7 Commits
d80d4e3b94
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a9742134b2 | ||
|
|
a8b772a5ea | ||
|
|
ca295b66f9 | ||
|
|
9fdfd91e1d | ||
|
|
7607ec4205 | ||
|
|
b8615d3b90 | ||
|
|
15f77a945a |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -41,3 +41,7 @@ bin/
|
||||
### Mac OS ###
|
||||
.DS_Store
|
||||
/core/src/main/java/hdvtdev/telegram/Count.java
|
||||
/src/
|
||||
/test/
|
||||
/.idea/
|
||||
/gradle/
|
||||
|
||||
4
.idea/gradle.xml
generated
4
.idea/gradle.xml
generated
@@ -8,8 +8,12 @@
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
<option value="$PROJECT_DIR$/annotation-processor" />
|
||||
<option value="$PROJECT_DIR$/core" />
|
||||
<option value="$PROJECT_DIR$/event-handlers" />
|
||||
<option value="$PROJECT_DIR$/event-handlers-annotations" />
|
||||
<option value="$PROJECT_DIR$/longpolling-okhttp" />
|
||||
<option value="$PROJECT_DIR$/test" />
|
||||
</set>
|
||||
</option>
|
||||
</GradleProjectSettings>
|
||||
|
||||
1
.idea/misc.xml
generated
1
.idea/misc.xml
generated
@@ -1,4 +1,3 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="FrameworkDetectionExcludesConfiguration">
|
||||
|
||||
141
README.md
141
README.md
@@ -1,10 +1,143 @@
|
||||
|
||||
|
||||
### English Version
|
||||
|
||||
# TeleJ
|
||||
|
||||
An elegant Java library for creating Telegram bots, featuring a framework module to reduce boilerplate and speed up development.
|
||||
|
||||
## ⚠️ Project Status ⚠️
|
||||
|
||||
**This project is currently under active development.** APIs may change, and features are still being added. It is not yet recommended for production use.
|
||||
|
||||
## About The Project
|
||||
|
||||
TeleJ is a library designed to simplify and accelerate the process of creating Telegram bots in Java. It provides a convenient API and a modular structure that helps you focus on your bot's logic rather than on repetitive code.
|
||||
|
||||
## Key Features
|
||||
|
||||
* **Simple Setup:** Get started with your bot quickly.
|
||||
* **Modular Architecture:** Includes a `core` module, an `event-handlers` framework module, and other components that can be included as needed.
|
||||
* **Modern Approach:** Utilizes modern Java features to create clean and readable code.
|
||||
* **Extensible:** Easily extendable to add custom functionality.
|
||||
|
||||
|
||||
### Usage Example
|
||||
|
||||
Below is a simple example of how to create and run a bot using TeleJ.
|
||||
|
||||
```java
|
||||
public class MyAwesomeBot {
|
||||
|
||||
private static final TelegramBot telegramBot = new OkHttpTelegramBot("token");
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
|
||||
telegramBot.start(new UpdateConsumer() {
|
||||
@Override
|
||||
public void onUpdate(Update update) {
|
||||
|
||||
if (update.hasMessage()) {
|
||||
Message message = update.message();
|
||||
if (message.hasText()) {
|
||||
if (message.text().equalsIgnoreCase("ping!")) {
|
||||
telegramBot.execute(new SendMessage.Builder(
|
||||
message.chatId(), "pong!").replyToMessage(message.messageId())
|
||||
.build());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Project Structure
|
||||
|
||||
The project consists of several modules:
|
||||
|
||||
* `core`: The core of the library with all the basic functions for interacting with the Telegram Bot API.
|
||||
* `event-handlers`: A framework module designed to reduce boilerplate and speed up development by simplifying the handling of events and updates from Telegram.
|
||||
* `annotation-processor`: An annotation processor to simplify code writing.
|
||||
* `longpolling-okhttp`: An implementation for receiving updates via Long Polling using OkHttp.
|
||||
|
||||
## License
|
||||
|
||||
This project is distributed under the MIT License.
|
||||
|
||||
***
|
||||
|
||||
### Русская версия
|
||||
|
||||
# TeleJ
|
||||
|
||||
Изящная библиотека на Java для создания ботов в Telegram, с модулем фреймворка для ускорения разработки и уменьшения количества шаблонного кода.
|
||||
|
||||
## ⚠️ Статус проекта ⚠️
|
||||
|
||||
**Этот проект находится в стадии активной разработки.** API может изменяться, а новые функции все еще добавляются. Пока не рекомендуется использовать его в продакшене.
|
||||
|
||||
## О проекте
|
||||
|
||||
TeleJ — это библиотека, разработанная для упрощения и ускорения процесса создания ботов для Telegram на языке Java. Она предоставляет удобный API и модульную структуру, которая помогает сосредоточиться на логике вашего бота, а не на повторяющемся коде.
|
||||
|
||||
## Основные возможности
|
||||
|
||||
* **Простая настройка:** Быстрое начало работы с вашим ботом.
|
||||
* **Модульная архитектура:** Включает основной модуль (`core`), модуль фреймворка (`event-handlers`) и другие компоненты, которые можно подключать по мере необходимости.
|
||||
* **Современный подход:** Использует современные возможности Java для создания чистого и читаемого кода.
|
||||
* **Расширяемость:** Легко расширяется для добавления пользовательского функционала.
|
||||
|
||||
|
||||
|
||||
### Пример использования
|
||||
|
||||
Total lines of code:
|
||||
Ниже приведен простой пример того, как создать и запустить бота с использованием TeleJ.
|
||||
|
||||
```java
|
||||
public class MyAwesomeBot {
|
||||
|
||||
private static final TelegramBot telegramBot = new OkHttpTelegramBot("token");
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
|
||||
<p>
|
||||
<span style="background-color: blue; color: white; padding: 2px 4px;">08 April 2025 22:07:16 code: 6094 docs: 173</span>
|
||||
<p>
|
||||
telegramBot.start(new UpdateConsumer() {
|
||||
@Override
|
||||
public void onUpdate(Update update) {
|
||||
|
||||
if (update.hasMessage()) {
|
||||
Message message = update.message();
|
||||
if (message.hasText()) {
|
||||
if (message.text().equalsIgnoreCase("ping!")) {
|
||||
telegramBot.execute(new SendMessage.Builder(
|
||||
message.chatId(), "pong!").replyToMessage(message.messageId())
|
||||
.build());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Структура проекта
|
||||
|
||||
Проект состоит из нескольких модулей:
|
||||
|
||||
* `core`: Ядро библиотеки со всеми основными функциями для взаимодействия с Telegram Bot API.
|
||||
* `event-handlers`: Модуль фреймворка, предназначенный для уменьшения бойлерплейта (шаблонного кода) и ускорения разработки за счет упрощения обработки событий и обновлений от Telegram.
|
||||
* `annotation-processor`: Обработчик аннотаций для упрощения написания кода.
|
||||
* `longpolling-okhttp`: Реализация получения обновлений через Long Polling с использованием OkHttp.
|
||||
|
||||
|
||||
## Лицензия
|
||||
|
||||
Этот проект распространяется под лицензией MIT License.
|
||||
20
annotation-processor/build.gradle
Normal file
20
annotation-processor/build.gradle
Normal file
@@ -0,0 +1,20 @@
|
||||
plugins {
|
||||
id 'java'
|
||||
}
|
||||
|
||||
group = 'com.github.hdvtdev'
|
||||
version = '1.0.0'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.google.auto.service:auto-service:1.1.1'
|
||||
implementation project(':core')
|
||||
implementation 'org.ow2.asm:asm:9.9'
|
||||
annotationProcessor 'com.google.auto.service:auto-service:1.1.1'
|
||||
//implementation 'com.fasterxml.jackson.core:jackson-annotations:2.18.3'
|
||||
implementation project(":event-handlers-annotations")
|
||||
}
|
||||
|
||||
172
annotation-processor/src/main/java/EventHandlersProcessor.java
Normal file
172
annotation-processor/src/main/java/EventHandlersProcessor.java
Normal file
@@ -0,0 +1,172 @@
|
||||
import com.google.auto.service.AutoService;
|
||||
|
||||
import javax.annotation.processing.*;
|
||||
import javax.lang.model.SourceVersion;
|
||||
import javax.lang.model.element.*;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
||||
import hdvtdev.telegram.handler.annotations.*;
|
||||
|
||||
@AutoService(Processor.class)
|
||||
@SupportedAnnotationTypes({
|
||||
"hdvtdev.telegram.handler.annotations.OnChosenInlineResult",
|
||||
"hdvtdev.telegram.handler.annotations.OnChatMemberUpdated",
|
||||
"hdvtdev.telegram.handler.annotations.OnChatBoostRemoved",
|
||||
"hdvtdev.telegram.handler.annotations.OnPreCheckoutQuery",
|
||||
"hdvtdev.telegram.handler.annotations.OnBusinessConnection",
|
||||
"hdvtdev.telegram.handler.annotations.OnPaidMediaPurchased",
|
||||
"hdvtdev.telegram.handler.annotations.OnUpdate",
|
||||
"hdvtdev.telegram.handler.annotations.BotCommand",
|
||||
"hdvtdev.telegram.handler.annotations.OnPoll",
|
||||
"hdvtdev.telegram.handler.annotations.OnMessage",
|
||||
"hdvtdev.telegram.handler.annotations.OnChatJoinRequest",
|
||||
"hdvtdev.telegram.handler.annotations.OnChatBoostUpdated",
|
||||
"hdvtdev.telegram.handler.annotations.OnCallbackQuery",
|
||||
"hdvtdev.telegram.handler.annotations.OnShippingQuery",
|
||||
"hdvtdev.telegram.handler.annotations.OnInlineQuery",
|
||||
"hdvtdev.telegram.handler.annotations.OnPollAnswer"})
|
||||
@SupportedSourceVersion(SourceVersion.RELEASE_21)
|
||||
public class EventHandlersProcessor extends AbstractProcessor {
|
||||
|
||||
private final StringBuilder sb = new StringBuilder();
|
||||
|
||||
private static final String PACKAGE = "hdvtdev.telegram.handler.annotations.";
|
||||
private static final String HANDLER_PACKAGE = "hdvtdev.telegram.handler.";
|
||||
|
||||
private static final Set<String> SUPPORTED_ANNOTATIONS = Set.of(
|
||||
OnChosenInlineResult.class,
|
||||
OnChatMemberUpdated.class,
|
||||
OnChatBoostRemoved.class,
|
||||
OnPreCheckoutQuery.class,
|
||||
OnBusinessConnection.class,
|
||||
OnPaidMediaPurchased.class,
|
||||
OnUpdate.class,
|
||||
OnPoll.class,
|
||||
OnMessage.class,
|
||||
OnChatJoinRequest.class,
|
||||
OnChatBoostUpdated.class,
|
||||
OnCallbackQuery.class,
|
||||
OnShippingQuery.class,
|
||||
OnInlineQuery.class,
|
||||
OnPollAnswer.class
|
||||
).stream().map(Class::getName).collect(Collectors.toSet());
|
||||
|
||||
private static final Map<String, String> SUPPORTED_PARAMETERS = Map.ofEntries(
|
||||
Map.entry("Update", "hdvtdev.telegram.core.objects.Update"),
|
||||
Map.entry("InlineQuery", "hdvtdev.telegram.core.objects.InlineQuery"),
|
||||
Map.entry("ChatMemberUpdated", "hdvtdev.telegram.core.objects.chat.ChatMemberUpdated"),
|
||||
Map.entry("ChatBoostRemoved", "hdvtdev.telegram.core.objects.chatboost.ChatBoostRemoved"),
|
||||
Map.entry("PreCheckoutQuery", "hdvtdev.telegram.core.objects.payment.PreCheckoutQuery"),
|
||||
Map.entry("BusinessConnection", "hdvtdev.telegram.core.objects.business.BusinessConnection"),
|
||||
Map.entry("PaidMediaPurchased", "hdvtdev.telegram.core.objects.media.paidmedia.PaidMediaPurchased"),
|
||||
Map.entry("Poll", "hdvtdev.telegram.core.objects.poll.Poll"),
|
||||
Map.entry("Message", "hdvtdev.telegram.core.objects.Message"),
|
||||
Map.entry("ChatJoinRequest", "hdvtdev.telegram.core.objects.chat.ChatJoinRequest"),
|
||||
Map.entry("ChatBoostUpdated", "hdvtdev.telegram.core.objects.chatboost.ChatBoostUpdated"),
|
||||
Map.entry("CallbackQuery", "hdvtdev.telegram.core.objects.callback.CallbackQuery"),
|
||||
Map.entry("ShippingQuery", "hdvtdev.telegram.core.objects.payment.ShippingQuery"),
|
||||
Map.entry("PollAnswer", "hdvtdev.telegram.core.objects.poll.PollAnswer"),
|
||||
Map.entry("ChosenInlineResult", "hdvtdev.telegram.core.objects.ChosenInlineResult")
|
||||
);
|
||||
|
||||
public static String getFullParameterName(String simpleName) {
|
||||
return SUPPORTED_PARAMETERS.get(simpleName);
|
||||
}
|
||||
|
||||
private Messager messager;
|
||||
|
||||
@Override
|
||||
public synchronized void init(ProcessingEnvironment processingEnv) {
|
||||
super.init(processingEnv);
|
||||
messager = processingEnv.getMessager();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
|
||||
messager.printNote("Called");
|
||||
for (Element element : roundEnvironment.getRootElements()) {
|
||||
for (Element enclosedElement : element.getEnclosedElements()) {
|
||||
if (enclosedElement.getKind() == ElementKind.METHOD) {
|
||||
processMethod((ExecutableElement) enclosedElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (roundEnvironment.processingOver()) {
|
||||
write();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void write() {
|
||||
|
||||
}
|
||||
|
||||
private void processMethod(ExecutableElement methodElement) {
|
||||
String methodName = methodElement.getSimpleName().toString();
|
||||
|
||||
List<AnnotationMirror> foundAnnotations = new ArrayList<>();
|
||||
for (AnnotationMirror mirror : methodElement.getAnnotationMirrors()) {
|
||||
String mirrorAnnotationName = Util.getAnnotationMirrorName(mirror);
|
||||
if (SUPPORTED_ANNOTATIONS.contains(PACKAGE + mirrorAnnotationName)) {
|
||||
foundAnnotations.add(mirror);
|
||||
}
|
||||
}
|
||||
|
||||
if (foundAnnotations.size() > 1) {
|
||||
List<String> annotationNames = new ArrayList<>();
|
||||
for (AnnotationMirror mirror : foundAnnotations) {
|
||||
annotationNames.add("@" + Util.getAnnotationMirrorName(mirror));
|
||||
}
|
||||
|
||||
messager.printError(String.format("Method %s cannot have multiple mutually exclusive annotations. Found: %s. Please leave only one.",
|
||||
methodName, String.join(", ", annotationNames)), methodElement);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (foundAnnotations.size() == 1) {
|
||||
AnnotationMirror annotation = foundAnnotations.getFirst();
|
||||
String annotationSimpleName = Util.getAnnotationMirrorName(annotation);
|
||||
|
||||
var params = methodElement.getParameters();
|
||||
if (params.isEmpty()) {
|
||||
messager.printError(String.format("Method %s, annotated with @%s, must have at least one parameter.",
|
||||
methodName, annotationSimpleName), methodElement);
|
||||
return;
|
||||
}
|
||||
|
||||
String firstParameterType = Util.getParameterClassName(params.getFirst().asType());
|
||||
String requiredParameter = Util.checkParameter(annotationSimpleName, firstParameterType);
|
||||
|
||||
if (!requiredParameter.isEmpty()) {
|
||||
messager.printError(String.format("Incorrect parameter type for method %s. Annotation @%s requires %s, but found %s.",
|
||||
methodName, annotationSimpleName, requiredParameter, firstParameterType), params.getFirst());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!annotationSimpleName.equals("BotCommand")) {
|
||||
String paramName = params.getFirst().getSimpleName().toString();
|
||||
Set<String> filters = new HashSet<>();
|
||||
|
||||
AnnotationValue vv = Util.getAnnotationValue(annotation, "filters");
|
||||
@SuppressWarnings("unchecked")
|
||||
List<? extends AnnotationValue> enumValues = vv == null ? List.of() : (List<? extends AnnotationValue>) vv.getValue();
|
||||
for (AnnotationValue v : enumValues) {
|
||||
if (v.getValue() instanceof VariableElement enumConstant) {
|
||||
filters.add(enumConstant.getSimpleName().toString());
|
||||
}
|
||||
}
|
||||
sb.append(Generator.generateVoid(methodName, String.format("%s %s", firstParameterType, paramName),
|
||||
Generator.generateFilterBlock(filters, paramName)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
42
annotation-processor/src/main/java/Generator.java
Normal file
42
annotation-processor/src/main/java/Generator.java
Normal file
@@ -0,0 +1,42 @@
|
||||
import java.util.Set;
|
||||
|
||||
public final class Generator {
|
||||
|
||||
private Generator() {
|
||||
|
||||
}
|
||||
|
||||
public static String generateVoid(String name, String obj, String block) {
|
||||
return String.format("""
|
||||
public static void %s(%s) {
|
||||
%s
|
||||
}
|
||||
""", name, obj, block);
|
||||
}
|
||||
|
||||
public static String generateFilterBlock(Set<String> filters, String objName) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (String filter : filters) {
|
||||
boolean skip = true;
|
||||
sb.append("if (");
|
||||
sb.append(objName);
|
||||
sb.append(".");
|
||||
for (String part : filter.split("_")) {
|
||||
part = part.toLowerCase();
|
||||
if (skip) {
|
||||
sb.append(part);
|
||||
skip = false;
|
||||
} else {
|
||||
char[] chars = part.toCharArray();
|
||||
chars[0] = Character.toUpperCase(chars[0]);
|
||||
sb.append(chars);
|
||||
}
|
||||
}
|
||||
sb.append("()) {");
|
||||
sb.append("\n /* some code */ \n}\n");
|
||||
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
||||
112
annotation-processor/src/main/java/HandlerWriter.java
Normal file
112
annotation-processor/src/main/java/HandlerWriter.java
Normal file
@@ -0,0 +1,112 @@
|
||||
import org.objectweb.asm.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class HandlerWriter {
|
||||
|
||||
public static void write(String body) throws IOException {
|
||||
|
||||
Consumer<String> consumer = (String s) -> {};
|
||||
|
||||
String classFilePath = "models/Handlers.class"; // Путь к вашему файлу
|
||||
Path path = Paths.get(classFilePath);
|
||||
|
||||
// 1. Читаем исходный класс в байты
|
||||
byte[] originalBytecode = Files.readAllBytes(path);
|
||||
|
||||
// 2. Инициализируем ASM
|
||||
ClassReader classReader = new ClassReader(originalBytecode);
|
||||
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
|
||||
|
||||
// 3. Создаем наш визитор, который найдет и заменит метод
|
||||
ClassVisitor classVisitor = new MethodReplacer(classWriter);
|
||||
|
||||
// 4. Запускаем процесс
|
||||
classReader.accept(classVisitor, ClassReader.EXPAND_FRAMES);
|
||||
|
||||
// 5. Получаем измененный байт-код
|
||||
byte[] modifiedBytecode = classWriter.toByteArray();
|
||||
|
||||
// 6. Перезаписываем исходный файл
|
||||
Files.write(path, modifiedBytecode);
|
||||
|
||||
System.out.println("Метод invoke в файле " + classFilePath + " был успешно заменен.");
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
private static class MethodReplacer extends ClassVisitor {
|
||||
|
||||
public MethodReplacer(ClassVisitor classVisitor) {
|
||||
super(Opcodes.ASM9, classVisitor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
|
||||
String targetDescriptor = "(L" + EventHandlersProcessor.getFullParameterName("Update").replace('.', '/') +
|
||||
";)V";
|
||||
|
||||
if ("invoke".equals(name) && targetDescriptor.equals(descriptor)) {
|
||||
System.out.println("Найден метод 'invoke'. Заменяем его тело...");
|
||||
// Найден нужный метод. Не передаем его дальше (старое тело удаляется).
|
||||
// Вместо этого создаем новый MethodVisitor для генерации нового тела.
|
||||
MethodVisitor mv = cv.visitMethod(access, name, descriptor, signature, exceptions);
|
||||
generateNewBody(mv);
|
||||
// Возвращаем null, так как мы уже создали метод через cv.visitMethod
|
||||
return null;
|
||||
}
|
||||
|
||||
// Для всех остальных методов - просто передаем их дальше без изменений
|
||||
return super.visitMethod(access, name, descriptor, signature, exceptions);
|
||||
}
|
||||
|
||||
private void generateNewBody(MethodVisitor mv) {
|
||||
mv.visitCode();
|
||||
|
||||
mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
|
||||
mv.visitLdcInsn("Hello from the new invoke method!");
|
||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
|
||||
|
||||
// Код для: Integer updateId = update.getUpdateId();
|
||||
// `update` - это первый аргумент, поэтому он в локальной переменной 1 (0 это `this`)
|
||||
mv.visitVarInsn(Opcodes.ALOAD, 1);
|
||||
// Предполагаем, что getUpdateId() возвращает Integer. Если он возвращает int, нужно использовать valueOf.
|
||||
// Для примера, пусть он возвращает Integer.
|
||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, EventHandlersProcessor.getFullParameterName("Update"),
|
||||
"getUpdateId",
|
||||
"()Ljava/lang/Long;", false);
|
||||
// Сохраняем результат в локальную переменную 2
|
||||
mv.visitVarInsn(Opcodes.ASTORE, 2);
|
||||
|
||||
// Код для: System.out.println("Update ID: " + updateId);
|
||||
mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
|
||||
// Создаем новый StringBuilder для конкатенации строк
|
||||
mv.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder");
|
||||
mv.visitInsn(Opcodes.DUP);
|
||||
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);
|
||||
mv.visitLdcInsn("Update ID: ");
|
||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
|
||||
// Загружаем updateId из локальной переменной 2
|
||||
mv.visitVarInsn(Opcodes.ALOAD, 2);
|
||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/Object;)Ljava/lang/StringBuilder;", false);
|
||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
|
||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
|
||||
|
||||
|
||||
// Завершаем метод (поскольку он void, используем RETURN)
|
||||
mv.visitInsn(Opcodes.RETURN);
|
||||
|
||||
// Указываем максимальный размер стека и количество локальных переменных.
|
||||
// ASM может вычислить это за нас, если использовать ClassWriter.COMPUTE_FRAMES
|
||||
mv.visitMaxs(0, 0); // Значения игнорируются при COMPUTE_FRAMES
|
||||
mv.visitEnd();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
63
annotation-processor/src/main/java/Util.java
Normal file
63
annotation-processor/src/main/java/Util.java
Normal file
@@ -0,0 +1,63 @@
|
||||
import javax.lang.model.element.AnnotationMirror;
|
||||
import javax.lang.model.element.AnnotationValue;
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.ExecutableElement;
|
||||
import javax.lang.model.type.DeclaredType;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
import java.util.Map;
|
||||
|
||||
public final class Util {
|
||||
|
||||
private Util() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static String checkParameter(String simpleAnnotationName, String simpleParameterName) {
|
||||
String reqParameter = switch (simpleAnnotationName) {
|
||||
case "OnMessage", "BotCommand" -> "Message";
|
||||
case "OnBusinessConnection" -> "BusinessConnection";
|
||||
case "OnCallbackQuery" -> "CallbackQuery";
|
||||
case "OnChatBoostRemoved" -> "ChatBoostRemoved";
|
||||
case "OnChatBoostUpdated" -> "ChatBoostUpdated";
|
||||
case "OnChatJoinRequest" -> "ChatJoinRequest";
|
||||
case "OnChatMemberUpdated" -> "ChatMemberUpdated";
|
||||
case "OnChosenInlineResult" -> "ChosenInlineResult";
|
||||
case "OnInlineQuery" -> "InlineQuery";
|
||||
case "OnPaidMediaPurchased" -> "PaidMediaPurchased";
|
||||
case "OnPoll" -> "Poll";
|
||||
case "OnPollAnswer" -> "PollAnswer";
|
||||
case "OnPreCheckoutQuery" -> "PreCheckoutQuery";
|
||||
case "OnShippingQuery" -> "ShippingQuery";
|
||||
case "OnUpdate" -> "Update";
|
||||
default -> throw new IllegalStateException("Unexpected value: " + simpleAnnotationName);
|
||||
};
|
||||
return reqParameter.equals(simpleParameterName) ? "" : reqParameter;
|
||||
}
|
||||
|
||||
public static String getParameterClassName(TypeMirror typeMirror) {
|
||||
String methodParameterName = "unknown (probably primitive type)";
|
||||
if (typeMirror instanceof DeclaredType declaredType) {
|
||||
Element typeElement = declaredType.asElement();
|
||||
methodParameterName = typeElement.getSimpleName().toString();
|
||||
}
|
||||
return methodParameterName;
|
||||
}
|
||||
|
||||
|
||||
public static String getAnnotationMirrorName(AnnotationMirror mirror) {
|
||||
return mirror.getAnnotationType().asElement().getSimpleName().toString();
|
||||
}
|
||||
|
||||
public static AnnotationValue getAnnotationValue(AnnotationMirror annotationMirror, String attributeName) {
|
||||
Map<? extends ExecutableElement, ? extends AnnotationValue> elementValues = annotationMirror.getElementValues();
|
||||
for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : elementValues.entrySet()) {
|
||||
if (entry.getKey().getSimpleName().toString().equals(attributeName)) {
|
||||
return entry.getValue();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
10
build.gradle
10
build.gradle
@@ -1,5 +1,6 @@
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'application'
|
||||
}
|
||||
|
||||
group = 'com.github.hdvtdev'
|
||||
@@ -11,7 +12,16 @@ repositories {
|
||||
|
||||
dependencies {
|
||||
implementation(project(":core"))
|
||||
annotationProcessor(project(":annotation-processor"))
|
||||
implementation(project(":longpolling-okhttp"))
|
||||
implementation(project(":event-handlers"))
|
||||
implementation(project(":event-handlers-annotations"))
|
||||
|
||||
|
||||
}
|
||||
|
||||
application {
|
||||
mainClass = "Main"
|
||||
}
|
||||
|
||||
tasks.register('fat', Jar) {
|
||||
|
||||
@@ -11,6 +11,12 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.fasterxml.jackson.core:jackson-databind:2.18.3'
|
||||
implementation platform('com.fasterxml.jackson:jackson-bom:2.18.3')
|
||||
|
||||
implementation 'com.fasterxml.jackson.core:jackson-core'
|
||||
implementation 'com.fasterxml.jackson.core:jackson-annotations'
|
||||
implementation 'com.fasterxml.jackson.core:jackson-databind'
|
||||
|
||||
implementation 'org.jetbrains:annotations:26.0.2-1'
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package hdvtdev.telegram.core;
|
||||
|
||||
import hdvtdev.telegram.core.objects.Update;
|
||||
|
||||
public interface HandlersModule {
|
||||
|
||||
void dispatch(String botId, Update update);
|
||||
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package hdvtdev.telegram.core;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public record InvokeMethod(Method method, Class<?> parameterType) {
|
||||
|
||||
}
|
||||
8
core/src/main/java/hdvtdev/telegram/core/JsonModule.java
Normal file
8
core/src/main/java/hdvtdev/telegram/core/JsonModule.java
Normal file
@@ -0,0 +1,8 @@
|
||||
package hdvtdev.telegram.core;
|
||||
|
||||
public interface JsonModule {
|
||||
|
||||
<T> T fromJson(String json, Class<T> type);
|
||||
String toJson(Object object);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package hdvtdev.telegram.core.annotations;
|
||||
|
||||
public @interface TelegramAPI {
|
||||
|
||||
String since();
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package hdvtdev.telegram.core.objects;
|
||||
|
||||
public interface GeneralObject {
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
module core {
|
||||
requires com.fasterxml.jackson.databind;
|
||||
requires org.jetbrains.annotations;
|
||||
|
||||
exports hdvtdev.telegram.core.exceptions;
|
||||
exports hdvtdev.telegram.core.objects.command;
|
||||
exports hdvtdev.telegram.core.annotaions;
|
||||
exports hdvtdev.telegram.core.methods;
|
||||
exports hdvtdev.telegram.core;
|
||||
exports hdvtdev.telegram.core.objects;
|
||||
|
||||
15
event-handlers-annotations/build.gradle
Normal file
15
event-handlers-annotations/build.gradle
Normal file
@@ -0,0 +1,15 @@
|
||||
plugins {
|
||||
id 'java'
|
||||
}
|
||||
|
||||
group = 'com.github.hdvtdev'
|
||||
version = '1.0.0'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package hdvtdev.telegram.handler.annotations;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface BotCommand {
|
||||
|
||||
String name() default "";
|
||||
String description();
|
||||
|
||||
String botId() default "primary";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package hdvtdev.telegram.handler.annotations;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface OnBusinessConnection {
|
||||
|
||||
String botId() default "primary";
|
||||
|
||||
|
||||
Filter[] filters() default {};
|
||||
|
||||
enum Filter {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package hdvtdev.telegram.handler.annotations;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface OnCallbackQuery {
|
||||
|
||||
String botId() default "primary";
|
||||
|
||||
|
||||
Filter[] filters() default {};
|
||||
|
||||
enum Filter {
|
||||
HAS_MESSAGE,
|
||||
HAS_INLINE_MESSAGE_ID,
|
||||
HAS_CHAT_INSTANCE,
|
||||
HAS_DATA,
|
||||
HAS_GAME_SHORT_NAME
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package hdvtdev.telegram.handler.annotations;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface OnChatBoostRemoved {
|
||||
|
||||
String botId() default "primary";
|
||||
|
||||
|
||||
Filter[] filters() default {};
|
||||
|
||||
enum Filter {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package hdvtdev.telegram.handler.annotations;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface OnChatBoostUpdated {
|
||||
|
||||
String botId() default "primary";
|
||||
|
||||
|
||||
Filter[] filters() default {};
|
||||
|
||||
enum Filter {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package hdvtdev.telegram.handler.annotations;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface OnChatJoinRequest {
|
||||
|
||||
String botId() default "primary";
|
||||
|
||||
|
||||
Filter[] filters() default {};
|
||||
|
||||
enum Filter {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package hdvtdev.telegram.handler.annotations;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface OnChatMemberUpdated {
|
||||
|
||||
String botId() default "primary";
|
||||
|
||||
|
||||
Filter[] filters() default {};
|
||||
|
||||
enum Filter {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package hdvtdev.telegram.handler.annotations;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface OnChosenInlineResult {
|
||||
|
||||
String botId() default "primary";
|
||||
|
||||
|
||||
Filter[] filters() default {};
|
||||
|
||||
enum Filter {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package hdvtdev.telegram.handler.annotations;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface OnInlineQuery {
|
||||
|
||||
String botId() default "primary";
|
||||
|
||||
|
||||
Filter[] filters() default {};
|
||||
|
||||
enum Filter {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package hdvtdev.telegram.handler.annotations;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface OnMessage {
|
||||
|
||||
String botId() default "primary";
|
||||
|
||||
|
||||
Filter[] filters() default {};
|
||||
enum Filter {
|
||||
HAS_MESSAGE_THREAD_ID,
|
||||
HAS_SENDER_CHAT,
|
||||
HAS_SENDER_BOOST_COUNT,
|
||||
HAS_SENDER_BUSINESS_BOT,
|
||||
HAS_BUSINESS_CONNECTION_ID,
|
||||
HAS_FORWARD_ORIGIN,
|
||||
HAS_REPLY_TO_MESSAGE,
|
||||
HAS_EXTERNAL_REPLY,
|
||||
HAS_QUOTE,
|
||||
HAS_REPLY_TO_STORY,
|
||||
HAS_VIA_BOT,
|
||||
HAS_EDIT_DATE,
|
||||
HAS_MEDIA_GROUP_ID,
|
||||
HAS_AUTHOR_SIGNATURE,
|
||||
HAS_TEXT,
|
||||
HAS_ENTITIES,
|
||||
HAS_LINK_PREVIEW_OPTIONS,
|
||||
HAS_EFFECT_ID,
|
||||
HAS_ANIMATION,
|
||||
HAS_AUDIO,
|
||||
HAS_DOCUMENT,
|
||||
HAS_PAID_MEDIA_INFO,
|
||||
HAS_PHOTO,
|
||||
HAS_STICKER,
|
||||
HAS_STORY,
|
||||
HAS_VIDEO,
|
||||
HAS_VIDEO_NOTE,
|
||||
HAS_VOICE,
|
||||
HAS_CAPTION,
|
||||
HAS_CAPTION_ENTITIES,
|
||||
HAS_CONTACT,
|
||||
HAS_DICE,
|
||||
HAS_GAME,
|
||||
HAS_POLL,
|
||||
HAS_VENUE,
|
||||
HAS_LOCATION,
|
||||
HAS_NEW_CHAT_MEMBERS,
|
||||
HAS_LEFT_CHAT_MEMBER,
|
||||
HAS_NEW_CHAT_TITLE,
|
||||
HAS_NEW_CHAT_PHOTO,
|
||||
HAS_MESSAGE_AUTO_DELETE_TIMER_CHANGED,
|
||||
HAS_MIGRATE_TO_CHAT_ID,
|
||||
HAS_MIGRATE_FROM_CHAT_ID
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package hdvtdev.telegram.handler.annotations;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface OnPaidMediaPurchased {
|
||||
|
||||
String botId() default "primary";
|
||||
|
||||
|
||||
Filter[] filters() default {};
|
||||
|
||||
enum Filter {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package hdvtdev.telegram.handler.annotations;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface OnPoll {
|
||||
|
||||
String botId() default "primary";
|
||||
|
||||
|
||||
Filter[] filters() default {};
|
||||
|
||||
enum Filter {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package hdvtdev.telegram.handler.annotations;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface OnPollAnswer {
|
||||
|
||||
String botId() default "primary";
|
||||
|
||||
|
||||
Filter[] filters() default {};
|
||||
|
||||
enum Filter {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package hdvtdev.telegram.handler.annotations;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface OnPreCheckoutQuery {
|
||||
|
||||
String botId() default "primary";
|
||||
|
||||
|
||||
Filter[] filters() default {};
|
||||
|
||||
enum Filter {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package hdvtdev.telegram.handler.annotations;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface OnShippingQuery {
|
||||
|
||||
String botId() default "primary";
|
||||
|
||||
|
||||
Filter[] filters() default {};
|
||||
|
||||
enum Filter {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package hdvtdev.telegram.handler.annotations;
|
||||
|
||||
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface OnUpdate {
|
||||
|
||||
String botId() default "primary";
|
||||
|
||||
Filter[] filters() default {};
|
||||
|
||||
enum Filter {
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package hdvtdev.telegram.handler.annotations;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface TelegramBotInstance {
|
||||
String id() default "primary";
|
||||
|
||||
boolean primary();
|
||||
|
||||
}
|
||||
18
event-handlers/build.gradle
Normal file
18
event-handlers/build.gradle
Normal file
@@ -0,0 +1,18 @@
|
||||
plugins {
|
||||
id 'java'
|
||||
}
|
||||
|
||||
group = 'hdvtdev'
|
||||
version = '1.0.0'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(':core')
|
||||
//annotationProcessor 'com.fasterxml.jackson.core:jackson-annotations:2.18.3'
|
||||
annotationProcessor project(':annotation-processor')
|
||||
implementation project(":event-handlers-annotations")
|
||||
implementation 'org.jetbrains:annotations:26.0.2-1'
|
||||
}
|
||||
18
event-handlers/src/main/java/models/Handlers.java
Normal file
18
event-handlers/src/main/java/models/Handlers.java
Normal file
@@ -0,0 +1,18 @@
|
||||
package models;
|
||||
|
||||
import hdvtdev.telegram.core.HandlersModule;
|
||||
import hdvtdev.telegram.core.objects.Update;
|
||||
|
||||
public final class Handlers implements HandlersModule {
|
||||
|
||||
private Handlers() {
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void dispatch(String botId, Update update) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -10,15 +10,12 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.fasterxml.jackson.core:jackson-databind:2.18.3'
|
||||
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
|
||||
implementation platform('com.fasterxml.jackson:jackson-bom:2.18.3')
|
||||
|
||||
implementation 'com.fasterxml.jackson.core:jackson-core'
|
||||
implementation 'com.fasterxml.jackson.core:jackson-databind'
|
||||
|
||||
implementation 'com.squareup.okhttp3:okhttp:5.2.1'
|
||||
implementation(project(":core"))
|
||||
}
|
||||
|
||||
tasks.register('fat') {
|
||||
jar {
|
||||
from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } }
|
||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,10 +4,9 @@ import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import hdvtdev.telegram.core.InvokeMethod;
|
||||
import hdvtdev.telegram.core.HandlersModule;
|
||||
import hdvtdev.telegram.core.TelegramBot;
|
||||
import hdvtdev.telegram.core.UpdateConsumer;
|
||||
import hdvtdev.telegram.core.annotaions.Jsonable;
|
||||
import hdvtdev.telegram.core.exceptions.TelegramApiException;
|
||||
import hdvtdev.telegram.core.exceptions.TelegramApiNetworkException;
|
||||
import hdvtdev.telegram.core.exceptions.TelegramMethodParsingException;
|
||||
@@ -22,16 +21,13 @@ import okhttp3.*;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
|
||||
public class OkHttpTelegramBot implements TelegramBot {
|
||||
@@ -40,24 +36,10 @@ public class OkHttpTelegramBot implements TelegramBot {
|
||||
private final String TELEGRAM_FILE_API_URL;
|
||||
private final ObjectMapper json;
|
||||
|
||||
static {
|
||||
try {
|
||||
HttpURLConnection connection = (HttpURLConnection) URI.create("https://api.telegram.org").toURL().openConnection();
|
||||
connection.setRequestMethod("HEAD");
|
||||
connection.setConnectTimeout(5000);
|
||||
connection.setReadTimeout(5000);
|
||||
int responseCode = connection.getResponseCode();
|
||||
private ScheduledExecutorService scheduler;
|
||||
|
||||
if (responseCode != 200) {
|
||||
throw new TelegramApiNetworkException("Telegram API is unreachable. Response code: " + responseCode);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new TelegramApiNetworkException("Error checking Telegram API connectivity.", e);
|
||||
}
|
||||
}
|
||||
|
||||
private ExecutorService thread;
|
||||
private AtomicLong lastUpdateId;
|
||||
private final Consumer<Update> updateExecutor;
|
||||
private final AtomicLong lastUpdateId = new AtomicLong(0);
|
||||
private int updateLimit = 10;
|
||||
private int updateTimeout = 25;
|
||||
private final OkHttpClient client = buildOkHttpClient();
|
||||
@@ -68,6 +50,7 @@ public class OkHttpTelegramBot implements TelegramBot {
|
||||
dispatcher.setMaxRequestsPerHost(100);
|
||||
|
||||
return new OkHttpClient.Builder()
|
||||
|
||||
.dispatcher(dispatcher)
|
||||
.connectionPool(new ConnectionPool(
|
||||
100,
|
||||
@@ -78,14 +61,37 @@ public class OkHttpTelegramBot implements TelegramBot {
|
||||
.writeTimeout(updateTimeout, TimeUnit.SECONDS)
|
||||
.connectTimeout(updateTimeout, TimeUnit.SECONDS)
|
||||
.retryOnConnectionFailure(true)
|
||||
.addInterceptor(chain -> {
|
||||
Request request = chain.request();
|
||||
|
||||
int retryDelay = 1000;
|
||||
int maxRetries = 5;
|
||||
|
||||
for (int attempt = 1; attempt <= maxRetries; attempt++) {
|
||||
|
||||
try {
|
||||
return chain.proceed(request);
|
||||
} catch (IOException e) {
|
||||
if (attempt == maxRetries) throw e;
|
||||
try {
|
||||
TimeUnit.MILLISECONDS.sleep(retryDelay);
|
||||
} catch (InterruptedException ex) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw e;
|
||||
}
|
||||
retryDelay *= 2;
|
||||
}
|
||||
}
|
||||
throw new TelegramApiNetworkException("Network is unreachable");
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
private UpdateConsumer updateConsumer;
|
||||
private boolean enableHandlers = false;
|
||||
|
||||
public OkHttpTelegramBot(String token) {
|
||||
this.json = new ObjectMapper();
|
||||
this.updateExecutor = (Update update) -> CompletableFuture.runAsync(() -> updateConsumer.onUpdate(update));
|
||||
this.TELEGRAM_API_URL = "https://api.telegram.org/bot" + token + "/";
|
||||
this.TELEGRAM_FILE_API_URL = "https://api.telegram.org/file/bot" + token + "/";
|
||||
}
|
||||
@@ -93,54 +99,69 @@ public class OkHttpTelegramBot implements TelegramBot {
|
||||
private OkHttpTelegramBot(Builder builder) {
|
||||
updateLimit = builder.updateLimit;
|
||||
updateTimeout = builder.updateTimeout;
|
||||
enableHandlers = builder.enableHandlers;
|
||||
json = builder.objectMapper == null ? new ObjectMapper() : builder.objectMapper;
|
||||
/*
|
||||
if (false) {
|
||||
Class<? extends UpdateConsumer> updateConsumerClass = builder.updateConsumer == null ? UpdateConsumer.class : builder.updateConsumer.getClass();
|
||||
Map<Class<?>, Map<String, InvokeMethod>> handlers = builder.enableScan ? ClassFinder.getClasses() : ClassFinder.localScan(updateConsumerClass);
|
||||
this.messageHandlers = Collections.unmodifiableMap(handlers.get(TextMessageHandler.class));
|
||||
this.callbackQueryHandlers = Collections.unmodifiableMap(handlers.get(CallbackQueryHandler.class));
|
||||
}
|
||||
ExecutorService pool = builder.pool;
|
||||
|
||||
*/
|
||||
this.updateExecutor = pool == null ? (Update update) -> CompletableFuture.runAsync(() -> updateConsumer.onUpdate(update))
|
||||
: (Update update) -> pool.execute(() -> updateConsumer.onUpdate(update));
|
||||
this.TELEGRAM_API_URL = "https://api.telegram.org/bot" + builder.token + "/";
|
||||
this.TELEGRAM_FILE_API_URL = "https://api.telegram.org/file/bot" + builder.token + "/";
|
||||
if (builder.updateConsumer != null) setUpdateConsumer(builder.updateConsumer);
|
||||
if (builder.updateConsumer != null) start(builder.updateConsumer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables a long polling update consumer. If {@link #enableHandlers} is {@code true},
|
||||
* the specified handlers will be invoked for each received update.
|
||||
*
|
||||
* @param updateConsumer class that implements {@code UpdateConsumer}
|
||||
* @throws IllegalStateException if an {@code UpdateConsumer} is already defined
|
||||
* @see #enableHandlers
|
||||
* @since 0.0.1
|
||||
*/
|
||||
private void setUpdateConsumer(UpdateConsumer updateConsumer) throws IllegalStateException {
|
||||
if (thread != null) throw new IllegalStateException("Update Consumer is already defined. You must first stop the previous");
|
||||
this.updateConsumer = updateConsumer;
|
||||
this.lastUpdateId = new AtomicLong(0);
|
||||
thread = Executors.newSingleThreadExecutor();
|
||||
thread.execute(this::getUpdates);
|
||||
@Override
|
||||
public void start(UpdateConsumer updateConsumer) throws IllegalStateException {
|
||||
if (scheduler != null && !scheduler.isShutdown()) {
|
||||
throw new IllegalStateException("Long polling is already running. You must stop it first.");
|
||||
}
|
||||
|
||||
|
||||
|
||||
boolean moduleEnabled = true;
|
||||
|
||||
try {
|
||||
Class<?> handlersModule = Class.forName("Handlers");
|
||||
HandlersModule module = (HandlersModule) handlersModule.getDeclaredConstructor().newInstance();
|
||||
//this.updateConsumer = module.enable(updateConsumer);
|
||||
} catch (ClassNotFoundException | IllegalAccessException | InvocationTargetException | IllegalArgumentException | NoSuchMethodException | InstantiationException e) {
|
||||
moduleEnabled = false;
|
||||
}
|
||||
|
||||
if (!moduleEnabled) this.updateConsumer = updateConsumer;
|
||||
scheduler = Executors.newSingleThreadScheduledExecutor();
|
||||
scheduler.scheduleWithFixedDelay(this::getUpdates, 0, 1, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
|
||||
public void stop() {
|
||||
if (scheduler == null || scheduler.isShutdown()) {
|
||||
return;
|
||||
}
|
||||
scheduler.shutdown();
|
||||
try {
|
||||
if (!scheduler.awaitTermination(5, TimeUnit.SECONDS)) {
|
||||
scheduler.shutdownNow();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
scheduler.shutdownNow();
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
private void getUpdates() {
|
||||
|
||||
List<Update> updates = List.of(awaitExecute(new GetUpdates(lastUpdateId.get() + 1, updateLimit, updateTimeout)));
|
||||
try {
|
||||
if (!updates.isEmpty()) {
|
||||
if (updateConsumer != null) CompletableFuture.runAsync(() -> updateConsumer.onUpdates(updates));
|
||||
lastUpdateId.set(updates.getLast().updateId());
|
||||
if (!updates.isEmpty()) {
|
||||
for (Update update : updates) {
|
||||
updateExecutor.accept(update);
|
||||
}
|
||||
} finally {
|
||||
if (!thread.isShutdown()) getUpdates();
|
||||
lastUpdateId.set(updates.getLast().updateId());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
this.thread.close();
|
||||
stop();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -151,7 +172,7 @@ public class OkHttpTelegramBot implements TelegramBot {
|
||||
Request.Builder request = new Request.Builder()
|
||||
.url(TELEGRAM_API_URL + telegramApiMethod.getMethodName());
|
||||
if (body == null) {
|
||||
if (telegramApiMethod.getClass().isAnnotationPresent(Jsonable.class)) {
|
||||
if (telegramApiMethod.isJsonable()) {
|
||||
try {
|
||||
request.post(RequestBody.create(json.writeValueAsString(telegramApiMethod), MediaType.get("application/json; charset=utf-8")));
|
||||
} catch (JsonProcessingException e) {
|
||||
@@ -168,24 +189,24 @@ public class OkHttpTelegramBot implements TelegramBot {
|
||||
}
|
||||
|
||||
try (Response response = client.newCall(request.build()).execute()) {
|
||||
String responseBody = Objects.requireNonNull(response.body()).string();
|
||||
ResponseBody responseBody = response.body();
|
||||
String responseBodyString = responseBody.string();
|
||||
if (response.isSuccessful()) {
|
||||
JsonNode rootNode = json.readTree(responseBody);
|
||||
JsonNode rootNode = json.readTree(responseBodyString);
|
||||
JsonNode resultNode = rootNode.path("result");
|
||||
return json.treeToValue(resultNode, telegramApiMethod.getResponseClass());
|
||||
} else {
|
||||
throw new TelegramApiException(json.readValue(responseBody, TelegramApiException.ErrorResponse.class));
|
||||
throw new TelegramApiException(json.readValue(responseBodyString, TelegramApiException.ErrorResponse.class));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new TelegramApiNetworkException(e);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
private File getFile(TelegramFile telegramFile, Path targetDirectory) {
|
||||
try (Response response = client.newCall(new Request.Builder().url(TELEGRAM_FILE_API_URL + telegramFile.filePath()).build()).execute()) {
|
||||
ResponseBody responseBody = Objects.requireNonNull(response.body());
|
||||
ResponseBody responseBody = response.body();
|
||||
if (!response.isSuccessful())
|
||||
throw new TelegramApiException(json.readValue(responseBody.string(), TelegramApiException.ErrorResponse.class));
|
||||
Path filePath = Files.isDirectory(targetDirectory) ? targetDirectory.resolve(Path.of(telegramFile.filePath()).getFileName()) : targetDirectory;
|
||||
@@ -203,16 +224,21 @@ public class OkHttpTelegramBot implements TelegramBot {
|
||||
public static final class Builder {
|
||||
private int updateLimit = 10;
|
||||
private int updateTimeout = 25;
|
||||
private boolean enableHandlers = false;
|
||||
private boolean enableScan = false;
|
||||
private final String token;
|
||||
private UpdateConsumer updateConsumer;
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
private ExecutorService pool;
|
||||
|
||||
public Builder(String token) {
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
|
||||
public Builder threadPool(ExecutorService pool) {
|
||||
this.pool = pool;
|
||||
return this;
|
||||
}
|
||||
public Builder objectMapper(ObjectMapper objectMapper) {
|
||||
this.objectMapper = objectMapper;
|
||||
return this;
|
||||
@@ -233,17 +259,6 @@ public class OkHttpTelegramBot implements TelegramBot {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder enableHandlers() {
|
||||
this.enableHandlers = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder enableHandlers(boolean enableScan) {
|
||||
this.enableHandlers = true;
|
||||
this.enableScan = enableScan;
|
||||
return this;
|
||||
}
|
||||
|
||||
public OkHttpTelegramBot build() {
|
||||
return new OkHttpTelegramBot(this);
|
||||
}
|
||||
|
||||
9
longpolling-okhttp/src/main/java/module-info.java
Normal file
9
longpolling-okhttp/src/main/java/module-info.java
Normal file
@@ -0,0 +1,9 @@
|
||||
module longpolling.okhttp {
|
||||
exports hdvtdev.telegram.longpolling.okhttp;
|
||||
requires core;
|
||||
requires okhttp3;
|
||||
requires com.fasterxml.jackson.core;
|
||||
requires com.fasterxml.jackson.databind;
|
||||
|
||||
|
||||
}
|
||||
@@ -2,3 +2,8 @@ rootProject.name = 'TeleJ'
|
||||
include 'core'
|
||||
include 'longpolling-okhttp'
|
||||
|
||||
|
||||
include 'test'
|
||||
include 'event-handlers'
|
||||
include 'annotation-processor'
|
||||
include 'event-handlers-annotations'
|
||||
Reference in New Issue
Block a user