✨ 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 query = ctx.request().getParam("query");
|
||||||
String chatId = ctx.request().getParam("chatId");
|
String chatId = ctx.request().getParam("chatId");
|
||||||
|
String archived = ctx.request().getParam("archived");
|
||||||
getTelegramVerticle(telegramId)
|
getTelegramVerticle(telegramId)
|
||||||
.ifPresentOrElse(telegramVerticle ->
|
.ifPresentOrElse(telegramVerticle ->
|
||||||
telegramVerticle.getChats(Convert.toLong(chatId), query)
|
telegramVerticle.getChats(Convert.toLong(chatId), query, Convert.toBool(archived, false))
|
||||||
.onSuccess(ctx::json)
|
.onSuccess(ctx::json)
|
||||||
.onFailure(ctx::fail), () -> ctx.fail(404));
|
.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.Log;
|
||||||
import cn.hutool.log.LogFactory;
|
import cn.hutool.log.LogFactory;
|
||||||
import org.drinkless.tdlib.TdApi;
|
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.ConcurrentHashMap;
|
||||||
import java.util.concurrent.ConcurrentMap;
|
import java.util.concurrent.ConcurrentMap;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
@ -19,14 +23,18 @@ public class TelegramChats {
|
||||||
|
|
||||||
private final NavigableSet<OrderedChat> mainChatList = new TreeSet<>();
|
private final NavigableSet<OrderedChat> mainChatList = new TreeSet<>();
|
||||||
|
|
||||||
|
private final NavigableSet<OrderedChat> archivedChatList = new TreeSet<>();
|
||||||
|
|
||||||
private boolean haveFullMainChatList = false;
|
private boolean haveFullMainChatList = false;
|
||||||
|
|
||||||
|
private boolean haveFullArchivedChatList = false;
|
||||||
|
|
||||||
public TelegramChats(TelegramClient client) {
|
public TelegramChats(TelegramClient client) {
|
||||||
this.client = client;
|
this.client = client;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<TdApi.Chat> getMainChatList(Long activatedChatId, String query, int limit) {
|
public List<TdApi.Chat> getChatList(Long activatedChatId, String query, int limit, boolean archived) {
|
||||||
List<TdApi.Chat> chatList = mainChatList.stream()
|
List<TdApi.Chat> chatList = (archived ? archivedChatList : mainChatList).stream()
|
||||||
.map(OrderedChat::chatId)
|
.map(OrderedChat::chatId)
|
||||||
.map(chats::get)
|
.map(chats::get)
|
||||||
.filter(Objects::nonNull)
|
.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) {
|
public void onChatUpdated(TdApi.Object object) {
|
||||||
switch (object.getConstructor()) {
|
switch (object.getConstructor()) {
|
||||||
case TdApi.UpdateNewChat.CONSTRUCTOR: {
|
case TdApi.UpdateNewChat.CONSTRUCTOR: {
|
||||||
|
|
@ -74,7 +103,6 @@ public class TelegramChats {
|
||||||
chats.put(chat.id, chat);
|
chats.put(chat.id, chat);
|
||||||
|
|
||||||
TdApi.ChatPosition[] positions = chat.positions;
|
TdApi.ChatPosition[] positions = chat.positions;
|
||||||
chat.positions = new TdApi.ChatPosition[0];
|
|
||||||
setChatPositions(chat, positions);
|
setChatPositions(chat, positions);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
@ -115,7 +143,8 @@ public class TelegramChats {
|
||||||
}
|
}
|
||||||
case TdApi.UpdateChatPosition.CONSTRUCTOR: {
|
case TdApi.UpdateChatPosition.CONSTRUCTOR: {
|
||||||
TdApi.UpdateChatPosition updateChat = (TdApi.UpdateChatPosition) object;
|
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;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -123,7 +152,7 @@ public class TelegramChats {
|
||||||
synchronized (chat) {
|
synchronized (chat) {
|
||||||
int i;
|
int i;
|
||||||
for (i = 0; i < chat.positions.length; 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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -147,7 +176,7 @@ public class TelegramChats {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setChatPositions(TdApi.Chat chat, TdApi.ChatPosition[] positions) {
|
private void setChatPositions(TdApi.Chat chat, TdApi.ChatPosition[] positions) {
|
||||||
synchronized (mainChatList) {
|
synchronized (Tuple.tuple(mainChatList, archivedChatList)) {
|
||||||
synchronized (chat) {
|
synchronized (chat) {
|
||||||
for (TdApi.ChatPosition position : chat.positions) {
|
for (TdApi.ChatPosition position : chat.positions) {
|
||||||
if (position.list.getConstructor() == TdApi.ChatListMain.CONSTRUCTOR) {
|
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));
|
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;
|
chat.positions = positions;
|
||||||
|
|
@ -165,6 +199,11 @@ public class TelegramChats {
|
||||||
log.warn("Chat %d was already in mainChatList".formatted(chat.id));
|
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) {
|
public Future<JsonArray> getChats(Long activatedChatId, String query, boolean archived) {
|
||||||
return this.convertChat(telegramChats.getMainChatList(activatedChatId, query, 100));
|
return this.convertChat(telegramChats.getChatList(activatedChatId, query, 100, archived));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Future<JsonObject> getChatFiles(long chatId, MultiMap filter) {
|
public Future<JsonObject> getChatFiles(long chatId, MultiMap filter) {
|
||||||
|
|
@ -753,6 +753,7 @@ public class TelegramVerticle extends AbstractVerticle {
|
||||||
}
|
}
|
||||||
sendHttpEvent(EventPayload.build(EventPayload.TYPE_AUTHORIZATION, authorizationState));
|
sendHttpEvent(EventPayload.build(EventPayload.TYPE_AUTHORIZATION, authorizationState));
|
||||||
telegramChats.loadMainChatList();
|
telegramChats.loadMainChatList();
|
||||||
|
telegramChats.loadArchivedChatList();
|
||||||
break;
|
break;
|
||||||
case TdApi.AuthorizationStateLoggingOut.CONSTRUCTOR:
|
case TdApi.AuthorizationStateLoggingOut.CONSTRUCTOR:
|
||||||
break;
|
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 { Button } from "./ui/button";
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
|
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
|
||||||
import {
|
import {
|
||||||
|
|
@ -14,22 +14,30 @@ import { useTelegramChat } from "@/hooks/use-telegram-chat";
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { CommandLoading } from "cmdk";
|
import { CommandLoading } from "cmdk";
|
||||||
|
import { Toggle } from "./ui/toggle";
|
||||||
|
import { TooltipWrapper } from "@/components/ui/tooltip";
|
||||||
|
|
||||||
export default function ChatSelect({ disabled }: { disabled: boolean }) {
|
export default function ChatSelect({ disabled }: { disabled: boolean }) {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
|
const [archived, setArchived] = useState(false);
|
||||||
const {
|
const {
|
||||||
isLoading,
|
isLoading,
|
||||||
handleQueryChange,
|
handleQueryChange,
|
||||||
chats,
|
chats,
|
||||||
chat: selectedChat,
|
chat: selectedChat,
|
||||||
handleChatChange,
|
handleChatChange,
|
||||||
|
handleArchivedChange,
|
||||||
} = useTelegramChat();
|
} = useTelegramChat();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
handleQueryChange(search);
|
handleQueryChange(search);
|
||||||
}, [search, handleQueryChange]);
|
}, [search, handleQueryChange]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
handleArchivedChange(archived);
|
||||||
|
}, [archived, handleArchivedChange]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover open={open} onOpenChange={setOpen}>
|
<Popover open={open} onOpenChange={setOpen}>
|
||||||
<PopoverTrigger asChild disabled={disabled}>
|
<PopoverTrigger asChild disabled={disabled}>
|
||||||
|
|
@ -64,12 +72,31 @@ export default function ChatSelect({ disabled }: { disabled: boolean }) {
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent className="w-[300px] p-0">
|
<PopoverContent className="w-[300px] p-0">
|
||||||
<Command shouldFilter={false}>
|
<Command shouldFilter={false}>
|
||||||
<CommandInput
|
<div className="flex w-full border-b">
|
||||||
placeholder="Search chat..."
|
<TooltipWrapper
|
||||||
className="h-9"
|
content={archived ? "Show main chats" : "Show archived chats"}
|
||||||
value={search}
|
>
|
||||||
onValueChange={setSearch}
|
<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">
|
<CommandList className="relative">
|
||||||
{isLoading && (
|
{isLoading && (
|
||||||
<CommandLoading>
|
<CommandLoading>
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,10 @@ export const SWRProvider = ({ children }: { children: React.ReactNode }) => {
|
||||||
message = err.message;
|
message = err.message;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (key.startsWith("http")) {
|
||||||
|
key = new URL(key).pathname;
|
||||||
|
}
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
description: (
|
description: (
|
||||||
<div className="w-full flex flex-col space-y-2 overflow-hidden">
|
<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.ElementRef<typeof CommandPrimitive.Input>,
|
||||||
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
|
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
|
||||||
>(({ className, ...props }, ref) => (
|
>(({ 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" />
|
<Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
|
||||||
<CommandPrimitive.Input
|
<CommandPrimitive.Input
|
||||||
ref={ref}
|
ref={ref}
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,10 @@ interface TelegramChatContextType {
|
||||||
chat?: TelegramChat;
|
chat?: TelegramChat;
|
||||||
chats: TelegramChat[];
|
chats: TelegramChat[];
|
||||||
query: string;
|
query: string;
|
||||||
|
archived: boolean;
|
||||||
handleChatChange: (chatId: string) => void;
|
handleChatChange: (chatId: string) => void;
|
||||||
handleQueryChange: (search: string) => void;
|
handleQueryChange: (search: string) => void;
|
||||||
|
handleArchivedChange: (archived: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TelegramChatContext = createContext<TelegramChatContextType | undefined>(
|
const TelegramChatContext = createContext<TelegramChatContextType | undefined>(
|
||||||
|
|
@ -30,6 +32,7 @@ export const TelegramChatProvider: React.FC<TelegramChatProviderProps> = ({
|
||||||
children,
|
children,
|
||||||
}) => {
|
}) => {
|
||||||
const [query, setQuery] = useState("");
|
const [query, setQuery] = useState("");
|
||||||
|
const [archived, setArchived] = useState(false);
|
||||||
const params = useParams<{ accountId: string; chatId: string }>();
|
const params = useParams<{ accountId: string; chatId: string }>();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
@ -44,7 +47,7 @@ export const TelegramChatProvider: React.FC<TelegramChatProviderProps> = ({
|
||||||
isLoading,
|
isLoading,
|
||||||
mutate,
|
mutate,
|
||||||
} = useSWR<TelegramChat[]>(
|
} = useSWR<TelegramChat[]>(
|
||||||
`/telegram/${accountId}/chats?query=${query}&chatId=${chatId ?? ""}`,
|
`/telegram/${accountId}/chats?query=${query}&archived=${archived}&chatId=${chatId ?? ""}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
const chat = useMemo(
|
const chat = useMemo(
|
||||||
|
|
@ -77,8 +80,10 @@ export const TelegramChatProvider: React.FC<TelegramChatProviderProps> = ({
|
||||||
chat,
|
chat,
|
||||||
chats: chats ?? [],
|
chats: chats ?? [],
|
||||||
query,
|
query,
|
||||||
|
archived,
|
||||||
handleChatChange,
|
handleChatChange,
|
||||||
handleQueryChange: handleQueryChange,
|
handleQueryChange: handleQueryChange,
|
||||||
|
handleArchivedChange: setArchived,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue