Avatar and icon URLs

This commit is contained in:
Andrew Ferrazzutti 2021-03-23 02:37:30 -04:00
parent 8263023d2b
commit b007751610
8 changed files with 82 additions and 33 deletions

View File

@ -31,23 +31,24 @@ class Portal:
other_user: str
mxid: Optional[RoomID]
name: Optional[str]
icon_url: Optional[str]
encrypted: bool
async def insert(self) -> None:
q = ("INSERT INTO portal (chat_id, other_user, mxid, name, encrypted) "
"VALUES ($1, $2, $3, $4, $5)")
q = ("INSERT INTO portal (chat_id, other_user, mxid, name, icon_url, encrypted) "
"VALUES ($1, $2, $3, $4, $5, $6)")
await self.db.execute(q, self.chat_id, self.other_user, self.mxid, self.name,
self.encrypted)
self.icon_url, self.encrypted)
async def update(self) -> None:
q = ("UPDATE portal SET other_user=$2, mxid=$3, name=$4, encrypted=$5 "
q = ("UPDATE portal SET other_user=$2, mxid=$3, name=$4, icon_url=$5, encrypted=$6 "
"WHERE chat_id=$1")
await self.db.execute(q, self.chat_id, self.other_user,
self.mxid, self.name, self.encrypted)
self.mxid, self.name, self.icon_url, self.encrypted)
@classmethod
async def get_by_mxid(cls, mxid: RoomID) -> Optional['Portal']:
q = ("SELECT chat_id, other_user, mxid, name, encrypted "
q = ("SELECT chat_id, other_user, mxid, name, icon_url, encrypted "
"FROM portal WHERE mxid=$1")
row = await cls.db.fetchrow(q, mxid)
if not row:
@ -56,7 +57,7 @@ class Portal:
@classmethod
async def get_by_chat_id(cls, chat_id: int) -> Optional['Portal']:
q = ("SELECT chat_id, other_user, mxid, name, encrypted "
q = ("SELECT chat_id, other_user, mxid, name, icon_url, encrypted "
"FROM portal WHERE chat_id=$1")
row = await cls.db.fetchrow(q, chat_id)
if not row:
@ -65,12 +66,12 @@ class Portal:
@classmethod
async def find_private_chats(cls) -> List['Portal']:
rows = await cls.db.fetch("SELECT chat_id, other_user, mxid, name, encrypted "
rows = await cls.db.fetch("SELECT chat_id, other_user, mxid, name, icon_url, encrypted "
"FROM portal WHERE other_user IS NOT NULL")
return [cls(**row) for row in rows]
@classmethod
async def all_with_room(cls) -> List['Portal']:
rows = await cls.db.fetch("SELECT chat_id, other_user, mxid, name, encrypted "
rows = await cls.db.fetch("SELECT chat_id, other_user, mxid, name, icon_url, encrypted "
"FROM portal WHERE mxid IS NOT NULL")
return [cls(**row) for row in rows]

View File

@ -28,20 +28,20 @@ class Puppet:
mid: str
name: Optional[str]
# TODO avatar: Optional[str]
avatar_url: Optional[str]
is_registered: bool
async def insert(self) -> None:
q = "INSERT INTO puppet (mid, name, is_registered) VALUES ($1, $2, $3)"
await self.db.execute(q, self.mid, self.name, self.is_registered)
q = "INSERT INTO puppet (mid, name, avatar_url, is_registered) VALUES ($1, $2, $3, $4)"
await self.db.execute(q, self.mid, self.name, self.avatar_url, self.is_registered)
async def update(self) -> None:
q = "UPDATE puppet SET name=$2, is_registered=$3 WHERE mid=$1"
await self.db.execute(q, self.mid, self.name, self.is_registered)
q = "UPDATE puppet SET name=$2, avatar_url=$3, is_registered=$4 WHERE mid=$1"
await self.db.execute(q, self.mid, self.name, self.avatar_url, self.is_registered)
@classmethod
async def get_by_mid(cls, mid: str) -> Optional['Puppet']:
row = await cls.db.fetchrow("SELECT mid, name, is_registered FROM puppet WHERE mid=$1",
row = await cls.db.fetchrow("SELECT mid, name, avatar_url, is_registered FROM puppet WHERE mid=$1",
mid)
if not row:
return None

View File

@ -46,3 +46,13 @@ async def upgrade_v1(conn: Connection) -> None:
UNIQUE (mxid, mx_room)
)""")
@upgrade_table.register(description="Avatars and icons")
async def upgrade_avatars(conn: Connection) -> None:
for (table, column) in [('puppet', 'avatar_url'), ('portal', 'icon_url')]:
column_exists = await conn.fetchval(
"SELECT EXISTS(SELECT FROM information_schema.columns "
f"WHERE table_name='{table}' AND column_name='{column}')")
if not column_exists:
await conn.execute(f'ALTER TABLE "{table}" ADD COLUMN {column} TEXT')

View File

@ -62,9 +62,9 @@ class Portal(DBPortal, BasePortal):
_last_participant_update: Set[str]
def __init__(self, chat_id: int, other_user: Optional[str] = None,
mxid: Optional[RoomID] = None, name: Optional[str] = None, encrypted: bool = False
) -> None:
super().__init__(chat_id, other_user, mxid, name, encrypted)
mxid: Optional[RoomID] = None, name: Optional[str] = None, icon_url: Optional[str] = None,
encrypted: bool = False) -> None:
super().__init__(chat_id, other_user, mxid, name, icon_url, encrypted)
self._create_room_lock = asyncio.Lock()
self.log = self.log.getChild(str(chat_id))
@ -240,6 +240,7 @@ class Portal(DBPortal, BasePortal):
# TODO Consider setting no room name for non-group chats.
# But then the LINE bot itself may appear in the title...
changed = await self._update_name(f"{conv.name} (LINE)")
changed = await self._update_icon(conv.iconURL) or changed
if changed:
await self.update_bridge_info()
await self.update()
@ -255,6 +256,15 @@ class Portal(DBPortal, BasePortal):
return True
return False
async def _update_icon(self, icon_url: Optional[str]) -> bool:
if self.icon_url != icon_url:
self.icon_url = icon_url
if icon_url:
# TODO set icon from bytes
pass
return True
return False
async def _update_participants(self, participants: List[Participant]) -> None:
if not self.mxid:
return
@ -472,6 +482,8 @@ class Portal(DBPortal, BasePortal):
await DBMessage.delete_all(self.mxid)
self.by_mxid.pop(self.mxid, None)
self.mxid = None
self.name = None
self.icon_url = None
self.encrypted = False
await self.update()

View File

@ -38,8 +38,9 @@ class Puppet(DBPuppet, BasePuppet):
default_mxid: UserID
def __init__(self, mid: str, name: Optional[str] = None, is_registered: bool = False) -> None:
super().__init__(mid=mid, name=name, is_registered=is_registered)
def __init__(self, mid: str, name: Optional[str] = None, avatar_url: Optional[str] = None,
is_registered: bool = False) -> None:
super().__init__(mid=mid, name=name, avatar_url=avatar_url, is_registered=is_registered)
self.log = self.log.getChild(mid)
self.default_mxid = self.get_mxid_from_id(mid)
@ -63,7 +64,7 @@ class Puppet(DBPuppet, BasePuppet):
async def update_info(self, info: Participant) -> None:
update = False
update = await self._update_name(info.name) or update
# TODO Update avatar
update = await self._update_avatar(info.avatarURL) or update
if update:
await self.update()
@ -75,6 +76,15 @@ class Puppet(DBPuppet, BasePuppet):
return True
return False
async def _update_avatar(self, avatar_url: Optional[str]) -> bool:
if avatar_url != self.avatar_url:
self.avatar_url = avatar_url
if avatar_url:
# TODO set the avatar from bytes
pass
return True
return False
def _add_to_cache(self) -> None:
self.by_mid[self.mid] = self

View File

@ -28,6 +28,7 @@ class RPCError(Exception):
class ChatListInfo(SerializableAttrs['ChatListInfo']):
id: int
name: str
iconURL: Optional[str]
lastMsg: str
lastMsgDate: str
@ -35,7 +36,7 @@ class ChatListInfo(SerializableAttrs['ChatListInfo']):
@dataclass
class Participant(SerializableAttrs['Participant']):
id: str
# TODO avatar: str
avatarURL: Optional[str]
name: str

View File

@ -174,9 +174,6 @@ class MautrixController {
// Room members are always friends (right?),
// so search the friend list for the sender's name
// and get their ID from there.
// TODO For rooms, allow creating Matrix puppets in case
// a message is sent by someone who since left the
// room and never had a puppet made for them yet.
sender.id = this.getUserIdFromFriendsList(sender.name)
// Group members aren't necessarily friends,
// but the participant list includes their ID.
@ -185,7 +182,7 @@ class MautrixController {
const participantsList = document.querySelector(participantsListSelector)
sender.id = participantsList.querySelector(`img[alt='${senderName}'`).parentElement.parentElement.getAttribute("data-mid")
}
// TODO Avatar
sender.avatarURL = this.getParticipantListItemAvatarURL(element)
} else {
// TODO Get own ID and store it somewhere appropriate.
// Unable to get own ID from a room chat...
@ -199,7 +196,7 @@ class MautrixController {
await window.__mautrixShowParticipantsList()
const participantsList = document.querySelector(participantsListSelector)
sender.name = this.getParticipantListItemName(participantsList.children[0])
// TODO avatar
sender.avatarURL = this.getParticipantListItemAvatarURL(participantsList.children[0])
sender.id = this.ownID
}
@ -306,7 +303,7 @@ class MautrixController {
* @typedef Participant
* @type object
* @property {string} id - The member ID for the participant
* TODO @property {string} avatar - The URL of the participant's avatar
* @property {string} avatarURL - The URL of the participant's avatar
* @property {string} name - The contact list name of the participant
*/
@ -314,6 +311,15 @@ class MautrixController {
return element.querySelector(".mdRGT13Ttl").innerText
}
getParticipantListItemAvatarURL(element) {
const img = element.querySelector(".mdRGT13Img img[src]")
if (img && img.getAttribute("data-picture-path") != "" && img.src.startsWith("blob:")) {
return img.src
} else {
return ""
}
}
getParticipantListItemId(element) {
// TODO Cache own ID
return element.getAttribute("data-mid")
@ -334,7 +340,7 @@ class MautrixController {
// One idea is to add real ID as suffix if we're in a group, and
// put in the puppet DB table somehow.
id: this.ownID,
// TODO avatar: child.querySelector("img").src,
avatarURL: this.getParticipantListItemAvatarURL(element.children[0]),
name: this.getParticipantListItemName(element.children[0]),
}
@ -343,7 +349,7 @@ class MautrixController {
const id = this.getParticipantListItemId(child) || this.getUserIdFromFriendsList(name)
return {
id: id, // NOTE Don't want non-own user's ID to ever be null.
// TODO avatar: child.querySelector("img").src,
avatarURL: this.getParticipantListItemAvatarURL(child),
name: name,
}
}))
@ -354,7 +360,7 @@ class MautrixController {
* @type object
* @property {number} id - The ID of the chat.
* @property {string} name - The name of the chat.
* TODO @property {string} icon - The icon of the chat.
* @property {string} iconURL - The URL of the chat icon.
* @property {string} lastMsg - The most recent message in the chat.
* May be prefixed by sender name.
* @property {string} lastMsgDate - An imprecise date for the most recent message
@ -369,6 +375,15 @@ class MautrixController {
return element.querySelector(".mdCMN04Ttl").innerText
}
getChatListItemIconURL(element) {
const img = element.querySelector(".mdCMN04Img > :not(.mdCMN04ImgInner) > img[src]")
if (img && img.getAttribute("data-picture-path") != "" && img.src.startsWith("blob:")) {
return img.src
} else {
return ""
}
}
getChatListItemLastMsg(element) {
return element.querySelector(".mdCMN04Desc").innerText
}
@ -388,7 +403,7 @@ class MautrixController {
return !element.classList.contains("chatList") ? null : {
id: knownId || this.getChatListItemId(element),
name: this.getChatListItemName(element),
// TODO icon, but only for groups
iconURL: this.getChatListItemIconURL(element),
lastMsg: this.getChatListItemLastMsg(element),
lastMsgDate: this.getChatListItemLastMsgDate(element),
}

View File

@ -496,7 +496,7 @@ export default class MessagesPuppeteer {
//await chatDetailArea.$(".MdTxtDesc02") || // 1:1 chat with custom title - get participant's real name
participants = [{
id: id,
// TODO avatar, or leave null since this is a 1:1 chat
avatarURL: chatListInfo.iconURL,
name: chatListInfo.name,
}]
}