✨ feat: Support get archived chats.
This commit is contained in:
parent
ca08df92fd
commit
5d0d2a4943
7 changed files with 96 additions and 19 deletions
|
|
@ -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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<OrderedChat> mainChatList = new TreeSet<>();
|
||||
|
||||
private final NavigableSet<OrderedChat> archivedChatList = new TreeSet<>();
|
||||
|
||||
private boolean haveFullMainChatList = false;
|
||||
|
||||
private boolean haveFullArchivedChatList = false;
|
||||
|
||||
public TelegramChats(TelegramClient client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
public List<TdApi.Chat> getMainChatList(Long activatedChatId, String query, int limit) {
|
||||
List<TdApi.Chat> chatList = mainChatList.stream()
|
||||
public List<TdApi.Chat> getChatList(Long activatedChatId, String query, int limit, boolean archived) {
|
||||
List<TdApi.Chat> 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -171,8 +171,8 @@ public class TelegramVerticle extends AbstractVerticle {
|
|||
});
|
||||
}
|
||||
|
||||
public Future<JsonArray> getChats(Long activatedChatId, String query) {
|
||||
return this.convertChat(telegramChats.getMainChatList(activatedChatId, query, 100));
|
||||
public Future<JsonArray> getChats(Long activatedChatId, String query, boolean archived) {
|
||||
return this.convertChat(telegramChats.getChatList(activatedChatId, query, 100, archived));
|
||||
}
|
||||
|
||||
public Future<JsonObject> 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;
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild disabled={disabled}>
|
||||
|
|
@ -64,12 +72,31 @@ export default function ChatSelect({ disabled }: { disabled: boolean }) {
|
|||
</PopoverTrigger>
|
||||
<PopoverContent className="w-[300px] p-0">
|
||||
<Command shouldFilter={false}>
|
||||
<CommandInput
|
||||
placeholder="Search chat..."
|
||||
className="h-9"
|
||||
value={search}
|
||||
onValueChange={setSearch}
|
||||
/>
|
||||
<div className="flex w-full border-b">
|
||||
<TooltipWrapper
|
||||
content={archived ? "Show main chats" : "Show archived chats"}
|
||||
>
|
||||
<Toggle
|
||||
className="h-9 rounded-none border-r"
|
||||
pressed={archived}
|
||||
onPressedChange={setArchived}
|
||||
>
|
||||
{archived ? (
|
||||
<Archive className="h-5 w-5" />
|
||||
) : (
|
||||
<List className="h-5 w-5" />
|
||||
)}
|
||||
</Toggle>
|
||||
</TooltipWrapper>
|
||||
<div className="flex-1">
|
||||
<CommandInput
|
||||
placeholder="Search chat..."
|
||||
className="h-9"
|
||||
value={search}
|
||||
onValueChange={setSearch}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<CommandList className="relative">
|
||||
{isLoading && (
|
||||
<CommandLoading>
|
||||
|
|
|
|||
|
|
@ -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: (
|
||||
<div className="w-full flex flex-col space-y-2 overflow-hidden">
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ const CommandInput = React.forwardRef<
|
|||
React.ElementRef<typeof CommandPrimitive.Input>,
|
||||
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div className="flex items-center border-b px-3" cmdk-input-wrapper="">
|
||||
<div className="flex items-center px-3" cmdk-input-wrapper="">
|
||||
<Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
<CommandPrimitive.Input
|
||||
ref={ref}
|
||||
|
|
|
|||
|
|
@ -14,8 +14,10 @@ interface TelegramChatContextType {
|
|||
chat?: TelegramChat;
|
||||
chats: TelegramChat[];
|
||||
query: string;
|
||||
archived: boolean;
|
||||
handleChatChange: (chatId: string) => void;
|
||||
handleQueryChange: (search: string) => void;
|
||||
handleArchivedChange: (archived: boolean) => void;
|
||||
}
|
||||
|
||||
const TelegramChatContext = createContext<TelegramChatContextType | undefined>(
|
||||
|
|
@ -30,6 +32,7 @@ export const TelegramChatProvider: React.FC<TelegramChatProviderProps> = ({
|
|||
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<TelegramChatProviderProps> = ({
|
|||
isLoading,
|
||||
mutate,
|
||||
} = useSWR<TelegramChat[]>(
|
||||
`/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<TelegramChatProviderProps> = ({
|
|||
chat,
|
||||
chats: chats ?? [],
|
||||
query,
|
||||
archived,
|
||||
handleChatChange,
|
||||
handleQueryChange: handleQueryChange,
|
||||
handleArchivedChange: setArchived,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
|
|
|||
Loading…
Reference in a new issue