forked from Mirrors/gomuks
hicli/database: store DM user ID in database
This commit is contained in:
parent
ac6f2713e5
commit
39cb5f28a0
9 changed files with 73 additions and 37 deletions
|
@ -21,7 +21,8 @@ import (
|
||||||
|
|
||||||
const (
|
const (
|
||||||
getRoomBaseQuery = `
|
getRoomBaseQuery = `
|
||||||
SELECT room_id, creation_content, tombstone_content, name, name_quality, avatar, explicit_avatar, topic, canonical_alias,
|
SELECT room_id, creation_content, tombstone_content, name, name_quality,
|
||||||
|
avatar, explicit_avatar, dm_user_id, topic, canonical_alias,
|
||||||
lazy_load_summary, encryption_event, has_member_list, preview_event_rowid, sorting_timestamp,
|
lazy_load_summary, encryption_event, has_member_list, preview_event_rowid, sorting_timestamp,
|
||||||
unread_highlights, unread_notifications, unread_messages, marked_unread, prev_batch
|
unread_highlights, unread_notifications, unread_messages, marked_unread, prev_batch
|
||||||
FROM room
|
FROM room
|
||||||
|
@ -42,18 +43,19 @@ const (
|
||||||
name_quality = CASE WHEN $4 IS NOT NULL THEN $5 ELSE room.name_quality END,
|
name_quality = CASE WHEN $4 IS NOT NULL THEN $5 ELSE room.name_quality END,
|
||||||
avatar = COALESCE($6, room.avatar),
|
avatar = COALESCE($6, room.avatar),
|
||||||
explicit_avatar = CASE WHEN $6 IS NOT NULL THEN $7 ELSE room.explicit_avatar END,
|
explicit_avatar = CASE WHEN $6 IS NOT NULL THEN $7 ELSE room.explicit_avatar END,
|
||||||
topic = COALESCE($8, room.topic),
|
dm_user_id = COALESCE($8, room.dm_user_id),
|
||||||
canonical_alias = COALESCE($9, room.canonical_alias),
|
topic = COALESCE($9, room.topic),
|
||||||
lazy_load_summary = COALESCE($10, room.lazy_load_summary),
|
canonical_alias = COALESCE($10, room.canonical_alias),
|
||||||
encryption_event = COALESCE($11, room.encryption_event),
|
lazy_load_summary = COALESCE($11, room.lazy_load_summary),
|
||||||
has_member_list = room.has_member_list OR $12,
|
encryption_event = COALESCE($12, room.encryption_event),
|
||||||
preview_event_rowid = COALESCE($13, room.preview_event_rowid),
|
has_member_list = room.has_member_list OR $13,
|
||||||
sorting_timestamp = COALESCE($14, room.sorting_timestamp),
|
preview_event_rowid = COALESCE($14, room.preview_event_rowid),
|
||||||
unread_highlights = COALESCE($15, room.unread_highlights),
|
sorting_timestamp = COALESCE($15, room.sorting_timestamp),
|
||||||
unread_notifications = COALESCE($16, room.unread_notifications),
|
unread_highlights = COALESCE($16, room.unread_highlights),
|
||||||
unread_messages = COALESCE($17, room.unread_messages),
|
unread_notifications = COALESCE($17, room.unread_notifications),
|
||||||
marked_unread = COALESCE($18, room.marked_unread),
|
unread_messages = COALESCE($18, room.unread_messages),
|
||||||
prev_batch = COALESCE($19, room.prev_batch)
|
marked_unread = COALESCE($19, room.marked_unread),
|
||||||
|
prev_batch = COALESCE($20, room.prev_batch)
|
||||||
WHERE room_id = $1
|
WHERE room_id = $1
|
||||||
`
|
`
|
||||||
setRoomPrevBatchQuery = `
|
setRoomPrevBatchQuery = `
|
||||||
|
@ -153,6 +155,7 @@ type Room struct {
|
||||||
NameQuality NameQuality `json:"name_quality"`
|
NameQuality NameQuality `json:"name_quality"`
|
||||||
Avatar *id.ContentURI `json:"avatar,omitempty"`
|
Avatar *id.ContentURI `json:"avatar,omitempty"`
|
||||||
ExplicitAvatar bool `json:"explicit_avatar"`
|
ExplicitAvatar bool `json:"explicit_avatar"`
|
||||||
|
DMUserID *id.UserID `json:"dm_user_id,omitempty"`
|
||||||
Topic *string `json:"topic,omitempty"`
|
Topic *string `json:"topic,omitempty"`
|
||||||
CanonicalAlias *id.RoomAlias `json:"canonical_alias,omitempty"`
|
CanonicalAlias *id.RoomAlias `json:"canonical_alias,omitempty"`
|
||||||
|
|
||||||
|
@ -188,6 +191,10 @@ func (r *Room) CheckChangesAndCopyInto(other *Room) (hasChanges bool) {
|
||||||
other.ExplicitAvatar = r.ExplicitAvatar
|
other.ExplicitAvatar = r.ExplicitAvatar
|
||||||
hasChanges = true
|
hasChanges = true
|
||||||
}
|
}
|
||||||
|
if r.DMUserID != nil {
|
||||||
|
other.DMUserID = r.DMUserID
|
||||||
|
hasChanges = true
|
||||||
|
}
|
||||||
if r.Topic != nil {
|
if r.Topic != nil {
|
||||||
other.Topic = r.Topic
|
other.Topic = r.Topic
|
||||||
hasChanges = true
|
hasChanges = true
|
||||||
|
@ -250,6 +257,7 @@ func (r *Room) Scan(row dbutil.Scannable) (*Room, error) {
|
||||||
&r.NameQuality,
|
&r.NameQuality,
|
||||||
&r.Avatar,
|
&r.Avatar,
|
||||||
&r.ExplicitAvatar,
|
&r.ExplicitAvatar,
|
||||||
|
&r.DMUserID,
|
||||||
&r.Topic,
|
&r.Topic,
|
||||||
&r.CanonicalAlias,
|
&r.CanonicalAlias,
|
||||||
dbutil.JSON{Data: &r.LazyLoadSummary},
|
dbutil.JSON{Data: &r.LazyLoadSummary},
|
||||||
|
@ -281,6 +289,7 @@ func (r *Room) sqlVariables() []any {
|
||||||
r.NameQuality,
|
r.NameQuality,
|
||||||
r.Avatar,
|
r.Avatar,
|
||||||
r.ExplicitAvatar,
|
r.ExplicitAvatar,
|
||||||
|
r.DMUserID,
|
||||||
r.Topic,
|
r.Topic,
|
||||||
r.CanonicalAlias,
|
r.CanonicalAlias,
|
||||||
dbutil.JSONPtr(r.LazyLoadSummary),
|
dbutil.JSONPtr(r.LazyLoadSummary),
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
-- v0 -> v10 (compatible with v10+): Latest revision
|
-- v0 -> v11 (compatible with v10+): Latest revision
|
||||||
CREATE TABLE account (
|
CREATE TABLE account (
|
||||||
user_id TEXT NOT NULL PRIMARY KEY,
|
user_id TEXT NOT NULL PRIMARY KEY,
|
||||||
device_id TEXT NOT NULL,
|
device_id TEXT NOT NULL,
|
||||||
|
@ -18,6 +18,7 @@ CREATE TABLE room (
|
||||||
name_quality INTEGER NOT NULL DEFAULT 0,
|
name_quality INTEGER NOT NULL DEFAULT 0,
|
||||||
avatar TEXT,
|
avatar TEXT,
|
||||||
explicit_avatar INTEGER NOT NULL DEFAULT 0,
|
explicit_avatar INTEGER NOT NULL DEFAULT 0,
|
||||||
|
dm_user_id TEXT,
|
||||||
topic TEXT,
|
topic TEXT,
|
||||||
canonical_alias TEXT,
|
canonical_alias TEXT,
|
||||||
lazy_load_summary TEXT,
|
lazy_load_summary TEXT,
|
||||||
|
|
19
pkg/hicli/database/upgrades/11-dm-user-id.sql
Normal file
19
pkg/hicli/database/upgrades/11-dm-user-id.sql
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
-- v11 (compatible with v10+): Store direct chat user ID in database
|
||||||
|
ALTER TABLE room ADD COLUMN dm_user_id TEXT;
|
||||||
|
WITH dm_user_ids AS (
|
||||||
|
SELECT room_id, value
|
||||||
|
FROM room
|
||||||
|
INNER JOIN json_each(lazy_load_summary, '$."m.heroes"')
|
||||||
|
WHERE value NOT IN (SELECT value FROM json_each((
|
||||||
|
SELECT event.content
|
||||||
|
FROM current_state cs
|
||||||
|
INNER JOIN event ON cs.event_rowid = event.rowid
|
||||||
|
WHERE cs.room_id=room.room_id AND cs.event_type='io.element.functional_members' AND cs.state_key=''
|
||||||
|
), '$.service_members'))
|
||||||
|
GROUP BY room_id
|
||||||
|
HAVING COUNT(*) = 1
|
||||||
|
)
|
||||||
|
UPDATE room
|
||||||
|
SET dm_user_id=value
|
||||||
|
FROM dm_user_ids du
|
||||||
|
WHERE room.room_id=du.room_id;
|
|
@ -894,10 +894,11 @@ func (h *HiClient) processStateAndTimeline(
|
||||||
}
|
}
|
||||||
// Calculate name from participants if participants changed and current name was generated from participants, or if the room name was unset
|
// Calculate name from participants if participants changed and current name was generated from participants, or if the room name was unset
|
||||||
if (heroesChanged && updatedRoom.NameQuality <= database.NameQualityParticipants) || updatedRoom.NameQuality == database.NameQualityNil {
|
if (heroesChanged && updatedRoom.NameQuality <= database.NameQualityParticipants) || updatedRoom.NameQuality == database.NameQualityNil {
|
||||||
name, dmAvatarURL, err := h.calculateRoomParticipantName(ctx, room.ID, summary)
|
name, dmAvatarURL, dmUserID, err := h.calculateRoomParticipantName(ctx, room.ID, summary)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to calculate room name: %w", err)
|
return fmt.Errorf("failed to calculate room name: %w", err)
|
||||||
}
|
}
|
||||||
|
updatedRoom.DMUserID = &dmUserID
|
||||||
updatedRoom.Name = &name
|
updatedRoom.Name = &name
|
||||||
updatedRoom.NameQuality = database.NameQualityParticipants
|
updatedRoom.NameQuality = database.NameQualityParticipants
|
||||||
if !dmAvatarURL.IsEmpty() && !room.ExplicitAvatar {
|
if !dmAvatarURL.IsEmpty() && !room.ExplicitAvatar {
|
||||||
|
@ -966,15 +967,15 @@ func joinMemberNames(names []string, totalCount int) string {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *HiClient) calculateRoomParticipantName(ctx context.Context, roomID id.RoomID, summary *mautrix.LazyLoadSummary) (string, id.ContentURI, error) {
|
func (h *HiClient) calculateRoomParticipantName(ctx context.Context, roomID id.RoomID, summary *mautrix.LazyLoadSummary) (string, id.ContentURI, id.UserID, error) {
|
||||||
var primaryAvatarURL id.ContentURI
|
var primaryAvatarURL id.ContentURI
|
||||||
if summary == nil || len(summary.Heroes) == 0 {
|
if summary == nil || len(summary.Heroes) == 0 {
|
||||||
return "Empty room", primaryAvatarURL, nil
|
return "Empty room", primaryAvatarURL, "", nil
|
||||||
}
|
}
|
||||||
var functionalMembers []id.UserID
|
var functionalMembers []id.UserID
|
||||||
functionalMembersEvt, err := h.DB.CurrentState.Get(ctx, roomID, event.StateElementFunctionalMembers, "")
|
functionalMembersEvt, err := h.DB.CurrentState.Get(ctx, roomID, event.StateElementFunctionalMembers, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", primaryAvatarURL, fmt.Errorf("failed to get %s event: %w", event.StateElementFunctionalMembers.Type, err)
|
return "", primaryAvatarURL, "", fmt.Errorf("failed to get %s event: %w", event.StateElementFunctionalMembers.Type, err)
|
||||||
} else if functionalMembersEvt != nil {
|
} else if functionalMembersEvt != nil {
|
||||||
mautrixEvt := functionalMembersEvt.AsRawMautrix()
|
mautrixEvt := functionalMembersEvt.AsRawMautrix()
|
||||||
_ = mautrixEvt.Content.ParseRaw(mautrixEvt.Type)
|
_ = mautrixEvt.Content.ParseRaw(mautrixEvt.Type)
|
||||||
|
@ -990,16 +991,21 @@ func (h *HiClient) calculateRoomParticipantName(ctx context.Context, roomID id.R
|
||||||
} else if summary.InvitedMemberCount != nil {
|
} else if summary.InvitedMemberCount != nil {
|
||||||
memberCount = *summary.InvitedMemberCount
|
memberCount = *summary.InvitedMemberCount
|
||||||
}
|
}
|
||||||
|
var dmUserID id.UserID
|
||||||
for _, hero := range summary.Heroes {
|
for _, hero := range summary.Heroes {
|
||||||
if slices.Contains(functionalMembers, hero) {
|
if slices.Contains(functionalMembers, hero) {
|
||||||
|
// TODO save member count so push rule evaluation would use the subtracted one?
|
||||||
memberCount--
|
memberCount--
|
||||||
continue
|
continue
|
||||||
} else if len(members) >= 5 {
|
} else if len(members) >= 5 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
if dmUserID == "" {
|
||||||
|
dmUserID = hero
|
||||||
|
}
|
||||||
heroEvt, err := h.DB.CurrentState.Get(ctx, roomID, event.StateMember, hero.String())
|
heroEvt, err := h.DB.CurrentState.Get(ctx, roomID, event.StateMember, hero.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", primaryAvatarURL, fmt.Errorf("failed to get %s's member event: %w", hero, err)
|
return "", primaryAvatarURL, "", fmt.Errorf("failed to get %s's member event: %w", hero, err)
|
||||||
} else if heroEvt == nil {
|
} else if heroEvt == nil {
|
||||||
leftMembers = append(leftMembers, hero.String())
|
leftMembers = append(leftMembers, hero.String())
|
||||||
continue
|
continue
|
||||||
|
@ -1015,19 +1021,28 @@ func (h *HiClient) calculateRoomParticipantName(ctx context.Context, roomID id.R
|
||||||
}
|
}
|
||||||
if membership == "join" || membership == "invite" {
|
if membership == "join" || membership == "invite" {
|
||||||
members = append(members, name)
|
members = append(members, name)
|
||||||
|
dmUserID = hero
|
||||||
} else {
|
} else {
|
||||||
leftMembers = append(leftMembers, name)
|
leftMembers = append(leftMembers, name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(members)+len(leftMembers) > 1 || !primaryAvatarURL.IsValid() {
|
if !primaryAvatarURL.IsValid() {
|
||||||
primaryAvatarURL = id.ContentURI{}
|
primaryAvatarURL = id.ContentURI{}
|
||||||
}
|
}
|
||||||
if len(members) > 0 {
|
if len(members) > 0 {
|
||||||
return joinMemberNames(members, memberCount), primaryAvatarURL, nil
|
if len(members) > 1 {
|
||||||
|
primaryAvatarURL = id.ContentURI{}
|
||||||
|
dmUserID = ""
|
||||||
|
}
|
||||||
|
return joinMemberNames(members, memberCount), primaryAvatarURL, dmUserID, nil
|
||||||
} else if len(leftMembers) > 0 {
|
} else if len(leftMembers) > 0 {
|
||||||
return fmt.Sprintf("Empty room (was %s)", joinMemberNames(leftMembers, memberCount)), primaryAvatarURL, nil
|
if len(leftMembers) > 1 {
|
||||||
|
primaryAvatarURL = id.ContentURI{}
|
||||||
|
dmUserID = ""
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("Empty room (was %s)", joinMemberNames(leftMembers, memberCount)), primaryAvatarURL, "", nil
|
||||||
} else {
|
} else {
|
||||||
return "Empty room", primaryAvatarURL, nil
|
return "Empty room", primaryAvatarURL, "", nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
import { parseMXC } from "@/util/validation.ts"
|
import { parseMXC } from "@/util/validation.ts"
|
||||||
import { ContentURI, LazyLoadSummary, RoomID, UserID, UserProfile } from "./types"
|
import { ContentURI, RoomID, UserID, UserProfile } from "./types"
|
||||||
|
|
||||||
export const getMediaURL = (mxc?: string, encrypted: boolean = false): string | undefined => {
|
export const getMediaURL = (mxc?: string, encrypted: boolean = false): string | undefined => {
|
||||||
const [server, mediaID] = parseMXC(mxc)
|
const [server, mediaID] = parseMXC(mxc)
|
||||||
|
@ -93,20 +93,12 @@ interface RoomForAvatarURL {
|
||||||
room_id: RoomID
|
room_id: RoomID
|
||||||
name?: string
|
name?: string
|
||||||
dm_user_id?: UserID
|
dm_user_id?: UserID
|
||||||
lazy_load_summary?: LazyLoadSummary
|
|
||||||
avatar?: ContentURI
|
avatar?: ContentURI
|
||||||
avatar_url?: ContentURI
|
avatar_url?: ContentURI
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getRoomAvatarURL = (room: RoomForAvatarURL, avatarOverride?: ContentURI): string | undefined => {
|
export const getRoomAvatarURL = (room: RoomForAvatarURL, avatarOverride?: ContentURI): string | undefined => {
|
||||||
let dmUserID: UserID | undefined
|
return getAvatarURL(room.dm_user_id ?? room.room_id, {
|
||||||
if ("dm_user_id" in room) {
|
|
||||||
dmUserID = room.dm_user_id
|
|
||||||
} else if ("lazy_load_summary" in room) {
|
|
||||||
dmUserID = room.lazy_load_summary?.["m.heroes"]?.length === 1
|
|
||||||
? room.lazy_load_summary["m.heroes"][0] : undefined
|
|
||||||
}
|
|
||||||
return getAvatarURL(dmUserID ?? room.room_id, {
|
|
||||||
displayname: room.name,
|
displayname: room.name,
|
||||||
avatar_url: avatarOverride ?? room.avatar ?? room.avatar_url,
|
avatar_url: avatarOverride ?? room.avatar ?? room.avatar_url,
|
||||||
})
|
})
|
||||||
|
|
|
@ -187,8 +187,7 @@ export class StateStore {
|
||||||
const name = entry.meta.name ?? "Unnamed room"
|
const name = entry.meta.name ?? "Unnamed room"
|
||||||
return {
|
return {
|
||||||
room_id: entry.meta.room_id,
|
room_id: entry.meta.room_id,
|
||||||
dm_user_id: entry.meta.lazy_load_summary?.["m.heroes"]?.length === 1
|
dm_user_id: entry.meta.dm_user_id,
|
||||||
? entry.meta.lazy_load_summary["m.heroes"][0] : undefined,
|
|
||||||
sorting_timestamp: entry.meta.sorting_timestamp,
|
sorting_timestamp: entry.meta.sorting_timestamp,
|
||||||
preview_event,
|
preview_event,
|
||||||
preview_sender,
|
preview_sender,
|
||||||
|
|
|
@ -70,6 +70,7 @@ function visibleMetaIsEqual(meta1: DBRoom, meta2: DBRoom): boolean {
|
||||||
meta1.avatar === meta2.avatar &&
|
meta1.avatar === meta2.avatar &&
|
||||||
meta1.topic === meta2.topic &&
|
meta1.topic === meta2.topic &&
|
||||||
meta1.canonical_alias === meta2.canonical_alias &&
|
meta1.canonical_alias === meta2.canonical_alias &&
|
||||||
|
meta1.dm_user_id === meta2.dm_user_id &&
|
||||||
llSummaryIsEqual(meta1.lazy_load_summary, meta2.lazy_load_summary) &&
|
llSummaryIsEqual(meta1.lazy_load_summary, meta2.lazy_load_summary) &&
|
||||||
meta1.encryption_event?.algorithm === meta2.encryption_event?.algorithm &&
|
meta1.encryption_event?.algorithm === meta2.encryption_event?.algorithm &&
|
||||||
meta1.has_member_list === meta2.has_member_list
|
meta1.has_member_list === meta2.has_member_list
|
||||||
|
|
|
@ -54,6 +54,7 @@ export interface DBRoom {
|
||||||
name_quality: RoomNameQuality
|
name_quality: RoomNameQuality
|
||||||
avatar?: ContentURI
|
avatar?: ContentURI
|
||||||
explicit_avatar: boolean
|
explicit_avatar: boolean
|
||||||
|
dm_user_id?: UserID
|
||||||
topic?: string
|
topic?: string
|
||||||
canonical_alias?: RoomAlias
|
canonical_alias?: RoomAlias
|
||||||
lazy_load_summary?: LazyLoadSummary
|
lazy_load_summary?: LazyLoadSummary
|
||||||
|
|
|
@ -40,8 +40,7 @@ const MutualRooms = ({ client, userID }: MutualRoomsProps) => {
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
room_id: roomID,
|
room_id: roomID,
|
||||||
dm_user_id: roomData.meta.current.lazy_load_summary?.["m.heroes"]?.length === 1
|
dm_user_id: roomData.meta.current.dm_user_id,
|
||||||
? roomData.meta.current.lazy_load_summary["m.heroes"][0] : undefined,
|
|
||||||
name: roomData.meta.current.name ?? "Unnamed room",
|
name: roomData.meta.current.name ?? "Unnamed room",
|
||||||
avatar: roomData.meta.current.avatar,
|
avatar: roomData.meta.current.avatar,
|
||||||
search_name: "",
|
search_name: "",
|
||||||
|
|
Loading…
Add table
Reference in a new issue