From 5d0d2a49438515c6e1e14d6d9d1beaa142ef2253 Mon Sep 17 00:00:00 2001 From: jarvis2f <137974272+jarvis2f@users.noreply.github.com> Date: Tue, 21 Jan 2025 21:10:37 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat:=20Support=20get=20archived=20?= =?UTF-8?q?chats.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/telegram/files/HttpVerticle.java | 3 +- .../java/telegram/files/TelegramChats.java | 53 ++++++++++++++++--- .../java/telegram/files/TelegramVerticle.java | 5 +- web/src/components/chat-select.tsx | 41 +++++++++++--- web/src/components/swr-provider.tsx | 4 ++ web/src/components/ui/command.tsx | 2 +- web/src/hooks/use-telegram-chat.tsx | 7 ++- 7 files changed, 96 insertions(+), 19 deletions(-) diff --git a/api/src/main/java/telegram/files/HttpVerticle.java b/api/src/main/java/telegram/files/HttpVerticle.java index 8cf4970..ac83b02 100644 --- a/api/src/main/java/telegram/files/HttpVerticle.java +++ b/api/src/main/java/telegram/files/HttpVerticle.java @@ -380,9 +380,10 @@ public class HttpVerticle extends AbstractVerticle { } String query = ctx.request().getParam("query"); String chatId = ctx.request().getParam("chatId"); + String archived = ctx.request().getParam("archived"); getTelegramVerticle(telegramId) .ifPresentOrElse(telegramVerticle -> - telegramVerticle.getChats(Convert.toLong(chatId), query) + telegramVerticle.getChats(Convert.toLong(chatId), query, Convert.toBool(archived, false)) .onSuccess(ctx::json) .onFailure(ctx::fail), () -> ctx.fail(404)); } diff --git a/api/src/main/java/telegram/files/TelegramChats.java b/api/src/main/java/telegram/files/TelegramChats.java index 5aed278..12dc428 100644 --- a/api/src/main/java/telegram/files/TelegramChats.java +++ b/api/src/main/java/telegram/files/TelegramChats.java @@ -4,8 +4,12 @@ import cn.hutool.core.util.StrUtil; import cn.hutool.log.Log; import cn.hutool.log.LogFactory; import org.drinkless.tdlib.TdApi; +import org.jooq.lambda.tuple.Tuple; -import java.util.*; +import java.util.List; +import java.util.NavigableSet; +import java.util.Objects; +import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.stream.Collectors; @@ -19,14 +23,18 @@ public class TelegramChats { private final NavigableSet mainChatList = new TreeSet<>(); + private final NavigableSet archivedChatList = new TreeSet<>(); + private boolean haveFullMainChatList = false; + private boolean haveFullArchivedChatList = false; + public TelegramChats(TelegramClient client) { this.client = client; } - public List getMainChatList(Long activatedChatId, String query, int limit) { - List chatList = mainChatList.stream() + public List getChatList(Long activatedChatId, String query, int limit, boolean archived) { + List chatList = (archived ? archivedChatList : mainChatList).stream() .map(OrderedChat::chatId) .map(chats::get) .filter(Objects::nonNull) @@ -65,6 +73,27 @@ public class TelegramChats { } } + public void loadArchivedChatList() { + synchronized (archivedChatList) { + if (!haveFullArchivedChatList) { + // send LoadChats request if there are some unknown chats and have not enough known chats + client.execute(new TdApi.LoadChats(new TdApi.ChatListArchive(), 100)) + .onSuccess(object -> { + // chats had already been received through updates, let's retry request + loadArchivedChatList(); + }) + .onFailure(error -> { + if (((TelegramRunException) error).getError().code == 404) { + synchronized (archivedChatList) { + haveFullArchivedChatList = true; + log.debug("Archived chat list is loaded, size: %d".formatted(archivedChatList.size())); + } + } + }); + } + } + } + public void onChatUpdated(TdApi.Object object) { switch (object.getConstructor()) { case TdApi.UpdateNewChat.CONSTRUCTOR: { @@ -74,7 +103,6 @@ public class TelegramChats { chats.put(chat.id, chat); TdApi.ChatPosition[] positions = chat.positions; - chat.positions = new TdApi.ChatPosition[0]; setChatPositions(chat, positions); } break; @@ -115,7 +143,8 @@ public class TelegramChats { } case TdApi.UpdateChatPosition.CONSTRUCTOR: { TdApi.UpdateChatPosition updateChat = (TdApi.UpdateChatPosition) object; - if (updateChat.position.list.getConstructor() != TdApi.ChatListMain.CONSTRUCTOR) { + if (updateChat.position.list.getConstructor() != TdApi.ChatListMain.CONSTRUCTOR + && updateChat.position.list.getConstructor() != TdApi.ChatListArchive.CONSTRUCTOR) { break; } @@ -123,7 +152,7 @@ public class TelegramChats { synchronized (chat) { int i; for (i = 0; i < chat.positions.length; i++) { - if (chat.positions[i].list.getConstructor() == TdApi.ChatListMain.CONSTRUCTOR) { + if (chat.positions[i].list.getConstructor() == updateChat.position.list.getConstructor()) { break; } } @@ -147,7 +176,7 @@ public class TelegramChats { } private void setChatPositions(TdApi.Chat chat, TdApi.ChatPosition[] positions) { - synchronized (mainChatList) { + synchronized (Tuple.tuple(mainChatList, archivedChatList)) { synchronized (chat) { for (TdApi.ChatPosition position : chat.positions) { if (position.list.getConstructor() == TdApi.ChatListMain.CONSTRUCTOR) { @@ -155,6 +184,11 @@ public class TelegramChats { log.warn("Chat %d was not found in mainChatList".formatted(chat.id)); } } + if (position.list.getConstructor() == TdApi.ChatListArchive.CONSTRUCTOR) { + if (!archivedChatList.remove(new OrderedChat(chat.id, position))) { + log.warn("Chat %d was not found in archivedChatList".formatted(chat.id)); + } + } } chat.positions = positions; @@ -165,6 +199,11 @@ public class TelegramChats { log.warn("Chat %d was already in mainChatList".formatted(chat.id)); } } + if (position.list.getConstructor() == TdApi.ChatListArchive.CONSTRUCTOR) { + if (!archivedChatList.add(new OrderedChat(chat.id, position))) { + log.warn("Chat %d was already in archivedChatList".formatted(chat.id)); + } + } } } } diff --git a/api/src/main/java/telegram/files/TelegramVerticle.java b/api/src/main/java/telegram/files/TelegramVerticle.java index a2e8d98..26ffe49 100644 --- a/api/src/main/java/telegram/files/TelegramVerticle.java +++ b/api/src/main/java/telegram/files/TelegramVerticle.java @@ -171,8 +171,8 @@ public class TelegramVerticle extends AbstractVerticle { }); } - public Future getChats(Long activatedChatId, String query) { - return this.convertChat(telegramChats.getMainChatList(activatedChatId, query, 100)); + public Future getChats(Long activatedChatId, String query, boolean archived) { + return this.convertChat(telegramChats.getChatList(activatedChatId, query, 100, archived)); } public Future getChatFiles(long chatId, MultiMap filter) { @@ -753,6 +753,7 @@ public class TelegramVerticle extends AbstractVerticle { } sendHttpEvent(EventPayload.build(EventPayload.TYPE_AUTHORIZATION, authorizationState)); telegramChats.loadMainChatList(); + telegramChats.loadArchivedChatList(); break; case TdApi.AuthorizationStateLoggingOut.CONSTRUCTOR: break; diff --git a/web/src/components/chat-select.tsx b/web/src/components/chat-select.tsx index 5ed3269..fe84b45 100644 --- a/web/src/components/chat-select.tsx +++ b/web/src/components/chat-select.tsx @@ -1,4 +1,4 @@ -import { Check, ChevronsUpDown, Ellipsis } from "lucide-react"; +import { Archive, Check, ChevronsUpDown, Ellipsis, List } from "lucide-react"; import { Button } from "./ui/button"; import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover"; import { @@ -14,22 +14,30 @@ import { useTelegramChat } from "@/hooks/use-telegram-chat"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { cn } from "@/lib/utils"; import { CommandLoading } from "cmdk"; +import { Toggle } from "./ui/toggle"; +import { TooltipWrapper } from "@/components/ui/tooltip"; export default function ChatSelect({ disabled }: { disabled: boolean }) { const [open, setOpen] = useState(false); const [search, setSearch] = useState(""); + const [archived, setArchived] = useState(false); const { isLoading, handleQueryChange, chats, chat: selectedChat, handleChatChange, + handleArchivedChange, } = useTelegramChat(); useEffect(() => { handleQueryChange(search); }, [search, handleQueryChange]); + useEffect(() => { + handleArchivedChange(archived); + }, [archived, handleArchivedChange]); + return ( @@ -64,12 +72,31 @@ export default function ChatSelect({ disabled }: { disabled: boolean }) { - +
+ + + {archived ? ( + + ) : ( + + )} + + +
+ +
+
{isLoading && ( diff --git a/web/src/components/swr-provider.tsx b/web/src/components/swr-provider.tsx index c653708..6768a3c 100644 --- a/web/src/components/swr-provider.tsx +++ b/web/src/components/swr-provider.tsx @@ -28,6 +28,10 @@ export const SWRProvider = ({ children }: { children: React.ReactNode }) => { message = err.message; } + if (key.startsWith("http")) { + key = new URL(key).pathname; + } + toast({ description: (
diff --git a/web/src/components/ui/command.tsx b/web/src/components/ui/command.tsx index 7657047..56b7d3d 100644 --- a/web/src/components/ui/command.tsx +++ b/web/src/components/ui/command.tsx @@ -39,7 +39,7 @@ const CommandInput = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( -
+
void; handleQueryChange: (search: string) => void; + handleArchivedChange: (archived: boolean) => void; } const TelegramChatContext = createContext( @@ -30,6 +32,7 @@ export const TelegramChatProvider: React.FC = ({ children, }) => { const [query, setQuery] = useState(""); + const [archived, setArchived] = useState(false); const params = useParams<{ accountId: string; chatId: string }>(); const router = useRouter(); const { toast } = useToast(); @@ -44,7 +47,7 @@ export const TelegramChatProvider: React.FC = ({ isLoading, mutate, } = useSWR( - `/telegram/${accountId}/chats?query=${query}&chatId=${chatId ?? ""}`, + `/telegram/${accountId}/chats?query=${query}&archived=${archived}&chatId=${chatId ?? ""}`, ); const chat = useMemo( @@ -77,8 +80,10 @@ export const TelegramChatProvider: React.FC = ({ chat, chats: chats ?? [], query, + archived, handleChatChange, handleQueryChange: handleQueryChange, + handleArchivedChange: setArchived, }} > {children}