dfkndsfjkdsjjdssdf

This commit is contained in:
hdvtdev
2025-04-22 12:35:04 +03:00
parent 8719e015ba
commit 52c9264253
398 changed files with 14324 additions and 0 deletions

View File

@@ -0,0 +1,24 @@
plugins {
id 'java'
}
group = 'com.github.hdvtdev'
version = '1.0.0'
repositories {
mavenCentral()
}
dependencies {
implementation 'com.fasterxml.jackson.core:jackson-databind:2.18.3'
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
implementation(project(":core"))
}
tasks.register('fat') {
jar {
from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } }
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}
}

View File

@@ -0,0 +1,335 @@
package hdvtdev.telegram.longpolling;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import hdvtdev.telegram.annotations.handlers.CallbackQueryHandler;
import hdvtdev.telegram.annotations.handlers.TextMessageHandler;
import hdvtdev.telegram.annotations.util.Jsonable;
import hdvtdev.telegram.core.TelegramBot;
import hdvtdev.telegram.exceptions.TelegramApiException;
import hdvtdev.telegram.exceptions.TelegramApiNetworkException;
import hdvtdev.telegram.exceptions.TelegramMethodParsingException;
import hdvtdev.telegram.methods.GetUpdates;
import hdvtdev.telegram.methods.TelegramApiMethod;
import hdvtdev.telegram.objects.CallbackQuery;
import hdvtdev.telegram.objects.Message;
import hdvtdev.telegram.objects.TelegramFile;
import hdvtdev.telegram.objects.Update;
import hdvtdev.telegram.util.ClassFinder;
import hdvtdev.telegram.util.InvokeMethod;
import okhttp3.*;
import org.jetbrains.annotations.NotNull;
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.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;
public class OkHttpTelegramBot implements TelegramBot {
private final String TELEGRAM_API_URL;
private final String TELEGRAM_FILE_API_URL;
private final ObjectMapper json = new ObjectMapper();
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();
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 int updateLimit = 10;
private int updateTimeout = 25;
private final OkHttpClient client = buildOkHttpClient();
private OkHttpClient buildOkHttpClient() {
Dispatcher dispatcher = new Dispatcher();
dispatcher.setMaxRequests(100);
dispatcher.setMaxRequestsPerHost(100);
return new OkHttpClient.Builder()
.dispatcher(dispatcher)
.connectionPool(new ConnectionPool(
100,
75,
TimeUnit.SECONDS
))
.readTimeout(updateTimeout + 10, TimeUnit.SECONDS)
.writeTimeout(updateTimeout, TimeUnit.SECONDS)
.connectTimeout(updateTimeout, TimeUnit.SECONDS)
.retryOnConnectionFailure(true)
.build();
}
private UpdateConsumer updateConsumer;
private boolean enableHandlers = false;
private Map<String, InvokeMethod> messageHandlers;
private Map<String, InvokeMethod> callbackQueryHandlers;
private UserState userState;
private Map<String, UserState> userStateStorage;
public UserState getUserState(String id) {
return userStateStorage.getOrDefault(id, userState);
}
public void setUserState(String id, UserState userState) {
userStateStorage.put(id, userState);
}
public boolean compareUserState(String id, UserState userState) {
return getUserState(id).equals(userState);
}
public OkHttpTelegramBot(String token) {
this.TELEGRAM_API_URL = "https://api.telegram.org/bot" + token + "/";
this.TELEGRAM_FILE_API_URL = "https://api.telegram.org/file/bot" + token + "/";
}
private OkHttpTelegramBot(Builder builder) {
updateLimit = builder.updateLimit;
updateTimeout = builder.updateTimeout;
enableHandlers = builder.enableHandlers;
userStateStorage = builder.userStateStorage;
userState = builder.userState;
if (builder.updateConsumer != null) setUpdateConsumer(builder.updateConsumer);
if (enableHandlers) {
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));
}
this.TELEGRAM_API_URL = "https://api.telegram.org/bot" + builder.token + "/";
this.TELEGRAM_FILE_API_URL = "https://api.telegram.org/file/bot" + builder.token + "/";
}
/**
* Enables the default long polling update consumer for handlers.
* <p>
* This method is effective only if {@link #enableHandlers} is{@code true},
* otherwise, it just marks updates as read/processed without invoking any handlers.
* <p>
* <pre><code>
* Equivalent to: setUpdateConsumer(null);
* </code></pre>
* @throws IllegalStateException if an {@code UpdateConsumer} is already defined
* @see #setUpdateConsumer(UpdateConsumer)
* @see #enableHandlers
* @since 0.0.1
*/
public void enableDefaultUpdateConsumer() throws IllegalStateException {
setUpdateConsumer(null);
}
/**
* 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 #enableDefaultUpdateConsumer()
* @see #enableHandlers
* @since 0.0.1
*/
public 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);
}
private void getUpdates() {
List<Update> updates = List.of(awaitExecute(new GetUpdates(lastUpdateId.get() + 1, updateLimit, updateTimeout)));
System.out.println("UPDATE");
if (!updates.isEmpty()) {
try {
if (enableHandlers) {
for (Update update : updates) {
CompletableFuture.runAsync(() -> {
if (update.hasMessage()) {
Message message = update.message();
if (message.hasText() && !messageHandlers.isEmpty()) {
String key = message.text();
invokeAnnotatedMethod(messageHandlers.containsKey("*") ? messageHandlers.get("*") : messageHandlers.get(key), update);
}
}
if (update.hasCallbackQuery()) {
CallbackQuery callbackQuery = update.callbackQuery();
if (callbackQuery.hasMessage()) {
Message message = (Message) callbackQuery.message();
if (message.hasText() && !callbackQueryHandlers.isEmpty()) {
invokeAnnotatedMethod(callbackQueryHandlers.containsKey("*") ? callbackQueryHandlers.get("*") : callbackQueryHandlers.get(message.text()), update);
}
}
}
});
}
}
if (updateConsumer != null) CompletableFuture.runAsync(() -> updateConsumer.getUpdates(updates));
} finally {
lastUpdateId.set(updates.getLast().updateId());
if (!thread.isShutdown()) getUpdates();
}
}
}
private static void invokeAnnotatedMethod(InvokeMethod invokeMethod, Update update) {
if (invokeMethod != null) {
Method method = invokeMethod.method();
method.setAccessible(true);
try {
switch (invokeMethod.parameterType().getSimpleName()) {
case "Message" -> method.invoke(null, update.message());
case "Update" -> method.invoke(null, update);
case "CallbackQuery" -> method.invoke(null, update.callbackQuery());
default -> method.invoke(null);
}
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
}
public void shutdownUpdateConsumer() {
this.thread.close();
}
public <T> T awaitExecute(TelegramApiMethod<T> telegramApiMethod) throws TelegramApiException, TelegramApiNetworkException, TelegramMethodParsingException {
RequestBody body = telegramApiMethod.getBody();
Request.Builder request = new Request.Builder()
.url(TELEGRAM_API_URL + telegramApiMethod.getMethodName());
if (body == null) {
if (telegramApiMethod.getClass().isAnnotationPresent(Jsonable.class)) {
try {
request.post(RequestBody.create(json.writeValueAsString(telegramApiMethod), MediaType.get("application/json; charset=utf-8")));
} catch (JsonProcessingException e) {
throw new TelegramMethodParsingException(e);
}
}
} else request.post(body);
try (Response response = client.newCall(request.build()).execute()) {
String responseBody = Objects.requireNonNull(response.body()).string();
if (response.isSuccessful()) {
JsonNode rootNode = json.readTree(responseBody);
JsonNode resultNode = rootNode.path("result");
return json.treeToValue(resultNode, telegramApiMethod.getResponseClass());
} else {
throw new TelegramApiException(json.readValue(responseBody, TelegramApiException.ErrorResponse.class));
}
} catch (IOException e) {
throw new TelegramApiNetworkException(e);
}
}
@NotNull
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());
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;
Files.copy(responseBody.byteStream(), filePath, StandardCopyOption.REPLACE_EXISTING);
return new File(filePath.toUri());
} catch (IOException e) {
throw new TelegramApiNetworkException(e);
}
}
public File awaitDownloadFile(TelegramFile telegramFile, Path targetDirectory) {
return getFile(telegramFile, targetDirectory);
}
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 Map<String, UserState> userStateStorage;
private UserState userState;
private UpdateConsumer updateConsumer;
public Builder(String token) {
this.token = token;
}
public Builder updateConsumer(Main.Upd updateConsumer) {
this.updateConsumer = updateConsumer;
return this;
}
public Builder enableUserStateStorage(UserState defaultValue, Map<String, UserState> userStateStorage) {
this.userState = defaultValue;
this.userStateStorage = userStateStorage;
return this;
}
public Builder enableUserStateStorage(UserState defaultValue) {
return enableUserStateStorage(defaultValue, new ConcurrentHashMap<>());
}
public Builder updateLimit(int updateLimit) {
this.updateLimit = updateLimit;
return this;
}
public Builder updateTimeout(int updateTimeout) {
this.updateTimeout = updateTimeout;
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);
}
}
}

View File

@@ -0,0 +1,6 @@
module longpolling.okhttp {
exports hdvtdev.telegram.longpolling;
requires com.fasterxml.jackson.databind;
requires core;
requires okhttp3;
}