diff --git a/api/src/main/java/telegram/files/HttpVerticle.java b/api/src/main/java/telegram/files/HttpVerticle.java index 8c3ec53..2571534 100644 --- a/api/src/main/java/telegram/files/HttpVerticle.java +++ b/api/src/main/java/telegram/files/HttpVerticle.java @@ -298,7 +298,7 @@ public class HttpVerticle extends AbstractVerticle { return; } TelegramVerticle telegramVerticle = telegramVerticleOptional.get(); - telegramVerticle.stop(); + telegramVerticle.delete(); telegramVerticles.remove(telegramVerticle); sessionTelegramVerticles.entrySet().removeIf(e -> e.getValue().equals(telegramVerticle)); ctx.end(); diff --git a/api/src/main/java/telegram/files/TelegramVerticle.java b/api/src/main/java/telegram/files/TelegramVerticle.java index bc6b8da..cf8dedc 100644 --- a/api/src/main/java/telegram/files/TelegramVerticle.java +++ b/api/src/main/java/telegram/files/TelegramVerticle.java @@ -52,6 +52,8 @@ public class TelegramVerticle extends AbstractVerticle { private String rootId; + private boolean needDelete = false; + public TelegramRecord telegramRecord; static { @@ -95,14 +97,13 @@ public class TelegramVerticle extends AbstractVerticle { client = Client.create(telegramUpdateHandler, this::handleException, this::handleException); } - @Override - public void stop() { - if (!this.authorized || this.telegramRecord == null) { - File root = FileUtil.file(this.rootPath); - if (root.exists()) { - FileUtil.del(root); - } - } + public void delete() { + this.execute(new TdApi.Close()) + .onSuccess(r -> { + log.info("[%s] Telegram account closed".formatted(this.getRootId())); + this.needDelete = true; + }) + .onFailure(e -> log.error("[%s] Failed to close telegram account: %s".formatted(this.getRootId(), e.getMessage()))); } public boolean check() { @@ -192,19 +193,30 @@ public class TelegramVerticle extends AbstractVerticle { } public Future getChatFiles(long chatId, MultiMap filter) { - TdApi.SearchChatMessages searchChatMessages = new TdApi.SearchChatMessages(); - searchChatMessages.chatId = chatId; - searchChatMessages.query = filter.get("search"); - searchChatMessages.fromMessageId = Convert.toLong(filter.get("fromMessageId"), 0L); - searchChatMessages.offset = Convert.toInt(filter.get("offset"), 0); - searchChatMessages.limit = Convert.toInt(filter.get("limit"), 20); - searchChatMessages.filter = TdApiHelp.getSearchMessagesFilter(filter.get("type")); + String status = filter.get("status"); + if (Arrays.asList("downloading", "paused", "completed", "error").contains(status)) { + return DataVerticle.fileRepository.getFiles(chatId, filter) + .map(tuple -> new JsonObject() + .put("files", tuple.v1) + .put("nextFromMessageId", tuple.v2) + .put("count", tuple.v3) + .put("size", tuple.v1.size()) + ); + } else { + TdApi.SearchChatMessages searchChatMessages = new TdApi.SearchChatMessages(); + searchChatMessages.chatId = chatId; + searchChatMessages.query = filter.get("search"); + searchChatMessages.fromMessageId = Convert.toLong(filter.get("fromMessageId"), 0L); + searchChatMessages.offset = Convert.toInt(filter.get("offset"), 0); + searchChatMessages.limit = Convert.toInt(filter.get("limit"), 20); + searchChatMessages.filter = TdApiHelp.getSearchMessagesFilter(filter.get("type")); - return this.execute(searchChatMessages) - .compose(foundChatMessages -> - DataVerticle.fileRepository.getFilesByUniqueId(TdApiHelp.getFileUniqueIds(Arrays.asList(foundChatMessages.messages))) - .map(fileRecords -> Tuple.tuple(foundChatMessages, fileRecords))) - .compose(this::convertFiles); + return this.execute(searchChatMessages) + .compose(foundChatMessages -> + DataVerticle.fileRepository.getFilesByUniqueId(TdApiHelp.getFileUniqueIds(Arrays.asList(foundChatMessages.messages))) + .map(fileRecords -> Tuple.tuple(foundChatMessages, fileRecords))) + .compose(r -> this.convertFiles(r, filter)); + } } public Future getChatFilesCount(long chatId) { @@ -394,10 +406,10 @@ public class TelegramVerticle extends AbstractVerticle { public Future execute(TdApi.Function method) { log.trace("[%s] Execute method: %s".formatted(getRootId(), TypeUtil.getTypeArgument(method.getClass()))); return Future.future(promise -> { - if (!authorized) { - promise.fail("Telegram account not found or not authorized"); - return; - } + // if (!authorized) { + // promise.fail("Telegram account not found or not authorized"); + // return; + // } client.send(method, object -> { if (object.getConstructor() == TdApi.Error.CONSTRUCTOR) { promise.fail("Execute method failed. " + object); @@ -512,6 +524,13 @@ public class TelegramVerticle extends AbstractVerticle { case TdApi.AuthorizationStateClosing.CONSTRUCTOR: break; case TdApi.AuthorizationStateClosed.CONSTRUCTOR: + if (needDelete) { + File root = FileUtil.file(this.rootPath); + if (root.exists()) { + FileUtil.del(root); + } + log.info("[%s] Telegram account deleted".formatted(this.getRootId())); + } break; default: log.warn("[%s] Unsupported authorization state received:%s".formatted(this.getRootId(), authorizationState)); @@ -586,9 +605,10 @@ public class TelegramVerticle extends AbstractVerticle { )); } - private Future convertFiles(Tuple2> tuple) { + private Future convertFiles(Tuple2> tuple, MultiMap filter) { TdApi.FoundChatMessages foundChatMessages = tuple.v1; Map fileRecords = tuple.v2; + boolean searchIdle = Objects.equals(filter.get("status"), FileRecord.DownloadStatus.idle.name()); return DataVerticle.settingRepository.getByKey(SettingKey.uniqueOnly) .map(uniqueOnly -> { @@ -613,6 +633,10 @@ public class TelegramVerticle extends AbstractVerticle { } else { fileRecord = fileRecord.withSourceField(source.id(), source.downloadedSize()); } + if (searchIdle && !Objects.equals(fileRecord.downloadStatus(), FileRecord.DownloadStatus.idle.name())) { + return null; + } + //TODO Processing of the same file under different accounts JsonObject fileObject = JsonObject.mapFrom(fileRecord); diff --git a/api/src/main/java/telegram/files/repository/FileRepository.java b/api/src/main/java/telegram/files/repository/FileRepository.java index 954143e..d686133 100644 --- a/api/src/main/java/telegram/files/repository/FileRepository.java +++ b/api/src/main/java/telegram/files/repository/FileRepository.java @@ -1,7 +1,9 @@ package telegram.files.repository; import io.vertx.core.Future; +import io.vertx.core.MultiMap; import io.vertx.core.json.JsonObject; +import org.jooq.lambda.tuple.Tuple3; import java.util.List; import java.util.Map; @@ -13,6 +15,8 @@ public interface FileRepository { Future> getFiles(long chatId, List fileIds); + Future, Long, Long>> getFiles(long chatId, MultiMap filter); + Future> getFilesByUniqueId(List uniqueIds); Future getByPrimaryKey(int fileId, String uniqueId); diff --git a/api/src/main/java/telegram/files/repository/impl/FileRepositoryImpl.java b/api/src/main/java/telegram/files/repository/impl/FileRepositoryImpl.java index fa1b3b7..cb768bd 100644 --- a/api/src/main/java/telegram/files/repository/impl/FileRepositoryImpl.java +++ b/api/src/main/java/telegram/files/repository/impl/FileRepositoryImpl.java @@ -1,14 +1,19 @@ package telegram.files.repository.impl; import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.IterUtil; +import cn.hutool.core.convert.Convert; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.log.Log; import cn.hutool.log.LogFactory; import io.vertx.core.Future; +import io.vertx.core.MultiMap; import io.vertx.core.json.JsonObject; import io.vertx.jdbcclient.JDBCPool; import io.vertx.sqlclient.templates.SqlTemplate; +import org.jooq.lambda.tuple.Tuple; +import org.jooq.lambda.tuple.Tuple3; import telegram.files.repository.FileRecord; import telegram.files.repository.FileRepository; @@ -84,6 +89,60 @@ public class FileRepositoryImpl implements FileRepository { }); } + @Override + public Future, Long, Long>> getFiles(long chatId, MultiMap filter) { + String status = filter.get("status"); + String search = filter.get("search"); + Long fromMessageId = Convert.toLong(filter.get("fromMessageId"), 0L); + String type = filter.get("type"); + + String whereClause = "chat_id = #{chatId}"; + Map params = MapUtil.of("chatId", chatId); + if (StrUtil.isNotBlank(status)) { + whereClause += " AND download_status = #{status}"; + params.put("status", status); + } + if (StrUtil.isNotBlank(search)) { + whereClause += " AND (file_name LIKE #{search} OR caption LIKE #{search})"; + params.put("search", "%%" + search + "%%"); + } + if (fromMessageId > 0) { + whereClause += " AND message_id > #{fromMessageId}"; + params.put("fromMessageId", fromMessageId); + } + if (StrUtil.isNotBlank(type)) { + if (Objects.equals(type, "media")) { + whereClause += " AND type IN ('photo', 'video')"; + } else { + whereClause += " AND type = #{type}"; + params.put("type", type); + } + } + return Future.all( + SqlTemplate + .forQuery(pool, """ + SELECT * FROM file_record WHERE %s ORDER BY date DESC + """.formatted(whereClause)) + .mapTo(FileRecord.ROW_MAPPER) + .execute(params) + .onFailure(err -> log.error("Failed to get file record: %s".formatted(err.getMessage()))) + .map(IterUtil::toList) + , + SqlTemplate + .forQuery(pool, """ + SELECT COUNT(*) FROM file_record WHERE %s + """.formatted(whereClause)) + .mapTo(rs -> rs.getLong(0)) + .execute(params) + .onFailure(err -> log.error("Failed to get file record count: %s".formatted(err.getMessage()))) + .map(rs -> rs.size() > 0 ? rs.iterator().next() : 0L) + ).map(r -> { + List fileRecords = r.resultAt(0); + long nextFromMessageId = CollUtil.isEmpty(fileRecords) ? 0 : fileRecords.getLast().messageId(); + return Tuple.tuple(fileRecords, nextFromMessageId, r.resultAt(1)); + }); + } + @Override public Future> getFilesByUniqueId(List uniqueIds) { uniqueIds = uniqueIds.stream() diff --git a/request.http b/request.http index 8019926..cf33284 100644 --- a/request.http +++ b/request.http @@ -60,6 +60,10 @@ Cookie: {{tf}} GET http://{{server}}/telegram/{{telegramId}}/chat/{{chatId}}/files?fromMessageId=0&type=media&status=all Cookie: {{tf}} +### Get download completed chat files +GET http://{{server}}/telegram/{{telegramId}}/chat/{{chatId}}/files?fromMessageId=0&type=media&status=completed +Cookie: {{tf}} + ### Get chat files count GET http://{{server}}/telegram/{{telegramId}}/chat/{{chatId}}/files/count Cookie: {{tf}}