package telegram.files; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.bean.copier.CopyOptions; import cn.hutool.core.codec.Base64; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.convert.Convert; import cn.hutool.core.convert.TypeConverter; import cn.hutool.core.util.ClassUtil; import cn.hutool.core.util.ReflectUtil; import io.vertx.core.Future; import io.vertx.core.impl.NoStackTraceException; import org.drinkless.tdlib.TdApi; import org.jooq.lambda.tuple.Tuple; import org.jooq.lambda.tuple.Tuple1; import telegram.files.repository.FileRecord; import telegram.files.repository.SettingKey; import java.util.*; @SuppressWarnings("unchecked") public class TdApiHelp { // name -> class private static final Map>> FUNCTIONS = new HashMap<>(); // CONSTRUCTOR -> class private static final Map> CONSTRUCTORS = new HashMap<>(); public static final List FILE_CONTENT_CONSTRUCTORS = Arrays.asList( TdApi.MessagePhoto.CONSTRUCTOR, TdApi.MessageVideo.CONSTRUCTOR, TdApi.MessageAudio.CONSTRUCTOR, TdApi.MessageDocument.CONSTRUCTOR ); private static final TypeConverter TD_API_TYPE_CONVERTER = (targetType, value) -> { if (value == null) { return null; } if (value instanceof Map) { Map map = (Map) value; if (!map.containsKey("@type")) { return Convert.convertWithCheck(targetType, value, null, false); } Integer constructor = Convert.toInt(map.get("@type")); Class objClazz = CONSTRUCTORS.get(constructor); if (objClazz == null) { return null; } return BeanUtil.toBean(map, objClazz); } return Convert.convertWithCheck(targetType, value, null, false); }; private static final CopyOptions COPY_OPTIONS = new CopyOptions().setConverter(TD_API_TYPE_CONVERTER); static { Arrays.stream(TdApi.class.getClasses()) .filter(ClassUtil::isNormalClass) .filter(TdApi.Object.class::isAssignableFrom) .forEach(clazz -> { if (TdApi.Function.class.isAssignableFrom(clazz)) { FUNCTIONS.put(clazz.getSimpleName(), (Class>) clazz); } Object constructor = ReflectUtil.getStaticFieldValue(ReflectUtil.getField(clazz, "CONSTRUCTOR")); if (constructor != null) { CONSTRUCTORS.put(Convert.toInt(constructor), (Class) clazz); } }); } public static TdApi.Function getFunction(String method, Object params) { Class> func = FUNCTIONS.get(method); if (func == null) { return null; } if (params == null) { return ReflectUtil.newInstance(func); } return BeanUtil.toBean(params, func, COPY_OPTIONS); } public static String getChatType(TdApi.ChatType type) { return switch (type.getConstructor()) { case TdApi.ChatTypePrivate.CONSTRUCTOR -> "private"; case TdApi.ChatTypeBasicGroup.CONSTRUCTOR -> "group"; case TdApi.ChatTypeSupergroup.CONSTRUCTOR -> ((TdApi.ChatTypeSupergroup) type).isChannel ? "channel" : "group"; case TdApi.ChatTypeSecret.CONSTRUCTOR -> "secret"; default -> "unknown"; }; } public static TdApi.SearchMessagesFilter getSearchMessagesFilter(String fileType) { return switch (fileType) { case "media" -> new TdApi.SearchMessagesFilterPhotoAndVideo(); case "photo" -> new TdApi.SearchMessagesFilterPhoto(); case "video" -> new TdApi.SearchMessagesFilterVideo(); case "audio" -> new TdApi.SearchMessagesFilterAudio(); case "file" -> new TdApi.SearchMessagesFilterDocument(); default -> null; }; } public static String getSearchMessagesFilterType(TdApi.SearchMessagesFilter filter) { return switch (filter.getConstructor()) { case TdApi.SearchMessagesFilterPhotoAndVideo.CONSTRUCTOR -> "media"; case TdApi.SearchMessagesFilterPhoto.CONSTRUCTOR -> "photo"; case TdApi.SearchMessagesFilterVideo.CONSTRUCTOR -> "video"; case TdApi.SearchMessagesFilterAudio.CONSTRUCTOR -> "audio"; case TdApi.SearchMessagesFilterDocument.CONSTRUCTOR -> "file"; default -> "unknown"; }; } public static List getFileIds(List messages) { if (CollUtil.isEmpty(messages)) { return new ArrayList<>(); } return messages.stream() .filter(message -> FILE_CONTENT_CONSTRUCTORS.contains(message.content.getConstructor())) .map(TdApiHelp::getFileId) .filter(Objects::nonNull) .toList(); } public static List getFileUniqueIds(List messages) { if (CollUtil.isEmpty(messages)) { return new ArrayList<>(); } return messages.stream() .filter(message -> FILE_CONTENT_CONSTRUCTORS.contains(message.content.getConstructor())) .map(TdApiHelp::getFileUniqueId) .filter(Objects::nonNull) .toList(); } public static Integer getFileId(TdApi.Message message) { return getFileHandler(message).map(FileHandler::getFileId).orElse(null); } public static String getFileUniqueId(TdApi.Message message) { return getFileHandler(message).map(FileHandler::getFileUniqueId).orElse(null); } public static List filterUniqueMessages(List messages) { if (CollUtil.isEmpty(messages)) return messages; Set fileIds = new HashSet<>(); return messages.stream() .filter(message -> { Integer fileId = TdApiHelp.getFileId(message); if (fileId == null) return false; if (fileIds.contains(fileId)) { return false; } fileIds.add(fileId); return true; }) .toList(); } public static FileRecord.DownloadStatus getDownloadStatus(TdApi.File file) { if (file == null || file.local == null) { return null; } if (file.local.isDownloadingActive) { return FileRecord.DownloadStatus.downloading; } else if (file.local.isDownloadingCompleted) { return FileRecord.DownloadStatus.completed; } else { return FileRecord.DownloadStatus.paused; } } public static > Optional getFileHandler(TdApi.Message message) { if (message == null) return Optional.empty(); switch (message.content.getConstructor()) { case TdApi.MessagePhoto.CONSTRUCTOR -> { return Optional.of((T) new PhotoHandler(message)); } case TdApi.MessageVideo.CONSTRUCTOR -> { return Optional.of((T) new VideoHandler(message)); } case TdApi.MessageAudio.CONSTRUCTOR -> { return Optional.of((T) new AudioHandler(message)); } case TdApi.MessageDocument.CONSTRUCTOR -> { return Optional.of((T) new DocumentHandler(message)); } default -> { return Optional.empty(); } } } public static abstract class FileHandler { protected TdApi.Message message; protected T content; public FileHandler(TdApi.Message message) { this.message = message; this.content = (T) message.content; } public abstract Integer getFileId(); public abstract String getFileUniqueId(); public TdApi.File getPreviewFileId(Tuple tuple) { throw new UnsupportedOperationException("This message type does not support preview file"); } public abstract FileRecord convertFileRecord(long telegramId); public T getContent() { return content; } } public static class PhotoHandler extends FileHandler { public PhotoHandler(TdApi.Message message) { super(message); } @Override public Integer getFileId() { return content.photo.sizes[content.photo.sizes.length - 1].photo.id; } @Override public String getFileUniqueId() { return content.photo.sizes[content.photo.sizes.length - 1].photo.remote.uniqueId; } @Override public TdApi.File getPreviewFileId(Tuple tuple) { TdApi.MessagePhoto messagePhoto = this.content; String size = ((Tuple1) tuple).v1; TdApi.PhotoSize photoSize = Arrays.stream(messagePhoto.photo.sizes) .map(ComparablePhotoSize::new) .filter(comparablePhotoSize -> comparablePhotoSize.compareTo(size) <= 0) .max((o1, o2) -> o2.compareTo(o1.getPhotoSize())) .map(ComparablePhotoSize::getPhotoSize) .stream().findFirst() .orElse(null); if (photoSize == null) { throw new NoStackTraceException("The photo no size that less than or equal to " + size); } return photoSize.photo; } @Override public FileRecord convertFileRecord(long telegramId) { TdApi.PhotoSize photoSize = content.photo.sizes[content.photo.sizes.length - 1]; return new FileRecord( getFileId(), photoSize.photo.remote.uniqueId, telegramId, message.chatId, message.id, message.date, message.hasSensitiveContent, photoSize.photo.size == 0 ? photoSize.photo.expectedSize : photoSize.photo.size, photoSize.photo.local == null ? 0 : photoSize.photo.local.downloadedSize, "photo", null, null, Base64.encode((byte[]) BeanUtil.getProperty(content, "photo.minithumbnail.data")), content.caption.text, null, "idle", System.currentTimeMillis(), null ); } } public static class VideoHandler extends FileHandler { public VideoHandler(TdApi.Message message) { super(message); } @Override public Integer getFileId() { return content.video.video.id; } @Override public String getFileUniqueId() { return content.video.video.remote.uniqueId; } @Override public TdApi.File getPreviewFileId(Tuple tuple) { TdApi.MessageVideo messageVideo = this.content; TdApi.Thumbnail thumbnail = messageVideo.video.thumbnail; if (thumbnail == null) { throw new NoStackTraceException("Video thumbnail not found"); } return thumbnail.file; } @Override public FileRecord convertFileRecord(long telegramId) { TdApi.File file = content.video.video; return new FileRecord( file.id, file.remote.uniqueId, telegramId, message.chatId, message.id, message.date, message.hasSensitiveContent, file.size == 0 ? file.expectedSize : file.size, file.local == null ? 0 : file.local.downloadedSize, "video", content.video.mimeType, content.video.fileName, Base64.encode((byte[]) BeanUtil.getProperty(content, "video.minithumbnail.data")), content.caption.text, null, "idle", System.currentTimeMillis(), null ); } } public static class AudioHandler extends FileHandler { public AudioHandler(TdApi.Message message) { super(message); } @Override public Integer getFileId() { return content.audio.audio.id; } @Override public String getFileUniqueId() { return content.audio.audio.remote.uniqueId; } @Override public FileRecord convertFileRecord(long telegramId) { TdApi.File file = content.audio.audio; return new FileRecord( file.id, file.remote.uniqueId, telegramId, message.chatId, message.id, message.date, message.hasSensitiveContent, file.size == 0 ? file.expectedSize : file.size, file.local == null ? 0 : file.local.downloadedSize, "audio", content.audio.mimeType, content.audio.fileName, Base64.encode((byte[]) BeanUtil.getProperty(content, "audio.albumCoverMinithumbnail.data")), content.caption.text, null, "idle", System.currentTimeMillis(), null ); } } public static class DocumentHandler extends FileHandler { public DocumentHandler(TdApi.Message message) { super(message); } @Override public Integer getFileId() { return content.document.document.id; } @Override public String getFileUniqueId() { return content.document.document.remote.uniqueId; } @Override public FileRecord convertFileRecord(long telegramId) { TdApi.File file = content.document.document; return new FileRecord( file.id, file.remote.uniqueId, telegramId, message.chatId, message.id, message.date, message.hasSensitiveContent, file.size == 0 ? file.expectedSize : file.size, file.local == null ? 0 : file.local.downloadedSize, "file", content.document.mimeType, content.document.fileName, Base64.encode((byte[]) BeanUtil.getProperty(content, "document.minithumbnail.data")), content.caption.text, null, "idle", System.currentTimeMillis(), null ); } } public static class ComparablePhotoSize implements Comparable { private final TdApi.PhotoSize photoSize; private final String[] sizes = {"s", "m", "x", "y", "w", "a", "b", "c", "d"}; public ComparablePhotoSize(TdApi.PhotoSize photoSize) { this.photoSize = photoSize; } @Override public int compareTo(TdApi.PhotoSize o) { return Integer.compare(Arrays.binarySearch(sizes, photoSize.type), Arrays.binarySearch(sizes, o.type)); } public int compareTo(String size) { return Integer.compare(Arrays.binarySearch(sizes, photoSize.type), Arrays.binarySearch(sizes, size)); } public TdApi.PhotoSize getPhotoSize() { return photoSize; } } }