mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-16 02:15:37 +00:00
fix(zalo): update API version, batch group fetch, fix WS decrypt (#516)
- Bump DefaultAPIVersion from 665 to 671 to match current Zalo web client - Batch getmg-v2 requests (50 groups/batch) — Zalo rejects large payloads, causing error 114 for accounts with many groups (e.g. 250+) - Fix encryptType=1 WebSocket event decryption: add gzip decompression after base64 decode (matches zca-js pako.inflate behavior) - Split contacts picker into Friends/Groups tabs for better usability Co-authored-by: Luvu182 <208665161+Luvu182@users.noreply.github.com>
This commit is contained in:
1 parent
e297fb63b8
commit
be9044ca8e
4 files changed
+68
-31
No files matched your search
@@ -14,7 +14,7 @@ const (
|
||||
DefaultUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:133.0) Gecko/20100101 Firefox/133.0"
|
||||
DefaultLanguage = "vi"
|
||||
DefaultAPIType = 30
|
||||
DefaultAPIVersion = 665
|
||||
DefaultAPIVersion = 671
|
||||
DefaultComputerName = "Web"
|
||||
DefaultUIDSelf = "0"
|
||||
DefaultEncryptVer = "v2"
|
||||
|
||||
@@ -96,8 +96,32 @@ func FetchGroups(ctx context.Context, sess *Session) ([]GroupListInfo, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Step 2: Get group details from group service
|
||||
return fetchGroupDetails(ctx, sess, gridVerMap)
|
||||
// Step 2: Get group details in batches (Zalo rejects large payloads)
|
||||
const batchSize = 50
|
||||
ids := make([]string, 0, len(gridVerMap))
|
||||
for id := range gridVerMap {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
|
||||
var allGroups []GroupListInfo
|
||||
for i := 0; i < len(ids); i += batchSize {
|
||||
end := i + batchSize
|
||||
if end > len(ids) {
|
||||
end = len(ids)
|
||||
}
|
||||
batch := make(map[string]string, end-i)
|
||||
for _, id := range ids[i:end] {
|
||||
batch[id] = gridVerMap[id]
|
||||
}
|
||||
groups, err := fetchGroupDetails(ctx, sess, batch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
allGroups = append(allGroups, groups...)
|
||||
}
|
||||
|
||||
sort.Slice(allGroups, func(i, j int) bool { return allGroups[i].Name < allGroups[j].Name })
|
||||
return allGroups, nil
|
||||
}
|
||||
|
||||
// fetchGroupIDs gets group ID -> version map from group_poll service.
|
||||
@@ -222,7 +246,6 @@ func fetchGroupDetails(ctx context.Context, sess *Session, gridVerMap map[string
|
||||
TotalMember: info.TotalMember,
|
||||
})
|
||||
}
|
||||
sort.Slice(groups, func(i, j int) bool { return groups[i].Name < groups[j].Name })
|
||||
return groups, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -163,8 +163,12 @@ func (ln *Listener) decryptEventData(data string, encType uint, cipherKey string
|
||||
switch encType {
|
||||
case 0: // plaintext
|
||||
result = []byte(data)
|
||||
case 1: // base64 only
|
||||
result, err = base64.StdEncoding.DecodeString(data)
|
||||
case 1: // base64 + gzip
|
||||
raw, e := base64.StdEncoding.DecodeString(data)
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
result, err = decompressGzip(raw)
|
||||
case 2: // AES-GCM + gzip
|
||||
raw, e := ln.decryptAESGCMPayload(data, cipherKey)
|
||||
if e != nil {
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { useWsCall } from "@/hooks/use-ws-call";
|
||||
|
||||
interface Friend {
|
||||
@@ -134,37 +135,46 @@ export function ZaloContactsPicker({ instanceId, hasCredentials, value, onChange
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
className="h-8"
|
||||
/>
|
||||
<div className="max-h-48 overflow-y-auto rounded border p-2 space-y-1">
|
||||
{filteredFriends.length > 0 && (
|
||||
<>
|
||||
<p className="text-xs font-medium text-muted-foreground">{t("zalo.friends")}</p>
|
||||
{filteredFriends.map((f) => (
|
||||
<Tabs defaultValue="friends">
|
||||
<TabsList className="w-full">
|
||||
<TabsTrigger value="friends" className="flex-1 text-xs">
|
||||
{t("zalo.friends")} ({filteredFriends.length})
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="groups" className="flex-1 text-xs">
|
||||
{t("zalo.groups")} ({filteredGroups.length})
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="friends" className="mt-2">
|
||||
<div className="max-h-56 overflow-y-auto rounded border p-2 space-y-1">
|
||||
{filteredFriends.length > 0 ? filteredFriends.map((f) => (
|
||||
<label key={f.userId} className="flex items-center gap-2 py-0.5 text-sm cursor-pointer hover:bg-muted/50 rounded px-1">
|
||||
<input type="checkbox" checked={value.includes(f.userId)} onChange={() => toggle(f.userId)} />
|
||||
<span>{f.displayName}</span>
|
||||
<span className="text-xs text-muted-foreground ml-auto">{f.userId}</span>
|
||||
<span className="truncate">{f.displayName}</span>
|
||||
<span className="text-xs text-muted-foreground ml-auto shrink-0">{f.userId}</span>
|
||||
</label>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
{filteredGroups.length > 0 && (
|
||||
<>
|
||||
<p className="text-xs font-medium text-muted-foreground mt-2">{t("zalo.groups")}</p>
|
||||
{filteredGroups.map((g) => (
|
||||
)) : (
|
||||
<p className="text-sm text-muted-foreground py-2 text-center">
|
||||
{search ? t("zalo.noContactsMatch", { search }) : t("zalo.noContacts")}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</TabsContent>
|
||||
<TabsContent value="groups" className="mt-2">
|
||||
<div className="max-h-56 overflow-y-auto rounded border p-2 space-y-1">
|
||||
{filteredGroups.length > 0 ? filteredGroups.map((g) => (
|
||||
<label key={g.groupId} className="flex items-center gap-2 py-0.5 text-sm cursor-pointer hover:bg-muted/50 rounded px-1">
|
||||
<input type="checkbox" checked={value.includes(g.groupId)} onChange={() => toggle(g.groupId)} />
|
||||
<span>{g.name}</span>
|
||||
<span className="text-xs text-muted-foreground ml-auto">{t("zalo.membersCount", { count: g.totalMember })}</span>
|
||||
<span className="truncate">{g.name}</span>
|
||||
<span className="text-xs text-muted-foreground ml-auto shrink-0">{t("zalo.membersCount", { count: g.totalMember })}</span>
|
||||
</label>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
{filteredFriends.length === 0 && filteredGroups.length === 0 && (
|
||||
<p className="text-sm text-muted-foreground py-2 text-center">
|
||||
{search ? t("zalo.noContactsMatch", { search }) : t("zalo.noContacts")}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)) : (
|
||||
<p className="text-sm text-muted-foreground py-2 text-center">
|
||||
{search ? t("zalo.noContactsMatch", { search }) : t("zalo.noContacts")}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</>
|
||||
)}
|
||||
|
||||
|
||||
Reference in new issue
Block a user