SHA256
Refine direct message profile data
This commit is contained in:
@@ -7,6 +7,8 @@ import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import shine.db.MsgSubType;
|
||||
|
||||
/** Fast denormalized projection for user cards shown in dialogs/relations/profile lists. */
|
||||
public final class UserProfileStateDAO {
|
||||
private static volatile UserProfileStateDAO instance;
|
||||
@@ -32,25 +34,26 @@ public final class UserProfileStateDAO {
|
||||
}
|
||||
|
||||
|
||||
/** Effective outgoing social relation with priority close_friend > friend > contact > none. */
|
||||
/**
|
||||
* Effective outgoing social relation with priority close_friend > friend > contact > none.
|
||||
*
|
||||
* Use the canonical relation lookup instead of comparing only connections_state.to_login:
|
||||
* older/current blocks may address a user by blockchain name, so a direct to_login-only
|
||||
* query can incorrectly report "none" even when ListContacts/Connections sees the relation.
|
||||
*/
|
||||
public String getEffectiveRelationType(Connection c, String ownerLogin, String targetLogin) throws SQLException {
|
||||
if (ownerLogin == null || ownerLogin.isBlank() || targetLogin == null || targetLogin.isBlank()) return "none";
|
||||
try (PreparedStatement ps = c.prepareStatement("""
|
||||
SELECT CASE
|
||||
WHEN EXISTS(SELECT 1 FROM connections_state WHERE LOWER(login)=LOWER(?) AND LOWER(to_login)=LOWER(?) AND rel_type=10) THEN 'close_friend'
|
||||
WHEN EXISTS(SELECT 1 FROM connections_state WHERE LOWER(login)=LOWER(?) AND LOWER(to_login)=LOWER(?) AND rel_type=14) THEN 'friend'
|
||||
WHEN EXISTS(SELECT 1 FROM connections_state WHERE LOWER(login)=LOWER(?) AND LOWER(to_login)=LOWER(?) AND rel_type=20) THEN 'contact'
|
||||
ELSE 'none' END AS relation_type
|
||||
""")) {
|
||||
int i = 1;
|
||||
for (int n = 0; n < 3; n++) {
|
||||
ps.setString(i++, ownerLogin);
|
||||
ps.setString(i++, targetLogin);
|
||||
}
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
return rs.next() ? rs.getString("relation_type") : "none";
|
||||
}
|
||||
ConnectionsStateDAO relations = ConnectionsStateDAO.getInstance();
|
||||
if (relations.hasOutgoingByRelTypeCanonical(c, ownerLogin, targetLogin, MsgSubType.CONNECTION_CLOSE_FRIEND)) {
|
||||
return "close_friend";
|
||||
}
|
||||
if (relations.hasOutgoingByRelTypeCanonical(c, ownerLogin, targetLogin, MsgSubType.CONNECTION_FRIEND)) {
|
||||
return "friend";
|
||||
}
|
||||
if (relations.hasOutgoingByRelTypeCanonical(c, ownerLogin, targetLogin, MsgSubType.CONNECTION_CONTACT)) {
|
||||
return "contact";
|
||||
}
|
||||
return "none";
|
||||
}
|
||||
|
||||
public List<RelationCard> listRelations(Connection c, String ownerLogin, String listType, int limit, int offset) throws SQLException {
|
||||
|
||||
+11
-6
@@ -130,12 +130,17 @@ public class Net_GetDirectMessages_Handler implements JsonMessageHandler {
|
||||
private static Net_GetDirectMessages_Response.Avatar parseAvatar(String value) {
|
||||
Net_GetDirectMessages_Response.Avatar out = new Net_GetDirectMessages_Response.Avatar();
|
||||
String raw = value == null ? "" : value.trim();
|
||||
java.util.regex.Matcher ar = java.util.regex.Pattern.compile("(?:^|,)\\s*AR:([A-Za-z0-9_-]{43})(?:,|$)").matcher(raw);
|
||||
if (!ar.find()) ar = java.util.regex.Pattern.compile("AR:([A-Za-z0-9_-]{43})").matcher(raw);
|
||||
if (ar.find()) out.setAr(ar.group(1));
|
||||
java.util.regex.Matcher sha = java.util.regex.Pattern.compile("(?:^|,)\\s*SHA256:([A-Fa-f0-9]{64})(?:,|$)").matcher(raw);
|
||||
if (!sha.find()) sha = java.util.regex.Pattern.compile("SHA256:([A-Fa-f0-9]{64})").matcher(raw);
|
||||
if (sha.find()) out.setSha256Hex(sha.group(1).toLowerCase());
|
||||
|
||||
// Do not call Matcher.find() twice on the same matcher: the first successful
|
||||
// call advances it and the second one can make a perfectly valid avatar vanish.
|
||||
java.util.regex.Matcher ar = java.util.regex.Pattern.compile("AR:([A-Za-z0-9_-]{43})").matcher(raw);
|
||||
if (ar.find()) {
|
||||
out.setAr(ar.group(1));
|
||||
}
|
||||
java.util.regex.Matcher sha = java.util.regex.Pattern.compile("SHA256:([A-Fa-f0-9]{64})").matcher(raw);
|
||||
if (sha.find()) {
|
||||
out.setSha256Hex(sha.group(1).toLowerCase());
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
@@ -52,6 +52,18 @@ function cleanChannelMessagePreview(text) {
|
||||
|| (parsed.attachments.length ? 'Вложение' : 'Ждем ваших начинаний');
|
||||
}
|
||||
|
||||
// Keep the channel-list preview tolerant to small API naming changes. The current
|
||||
// server uses lastMessage.text/createdAtMs; legacy/alternate payloads are accepted
|
||||
// so the third line (last message + time) does not silently disappear.
|
||||
function resolveChannelLastMessage(summary) {
|
||||
const row = summary?.lastMessage || summary?.latestMessage || summary?.last_message || null;
|
||||
if (!row || typeof row !== 'object') return { text: '', createdAtMs: 0 };
|
||||
return {
|
||||
text: String(row.text ?? row.messageText ?? row.preview ?? row.body ?? '').trim(),
|
||||
createdAtMs: Number(row.createdAtMs ?? row.timeMs ?? row.created_at_ms ?? 0),
|
||||
};
|
||||
}
|
||||
|
||||
function isChannelsDemoMode() {
|
||||
try {
|
||||
const qs = new URLSearchParams(window.location.search);
|
||||
@@ -719,6 +731,7 @@ function mapApiChannelRow(summary, bucketKey, idx, index, notificationsState) {
|
||||
const isOwn = bucketKey === 'own';
|
||||
const title = displayTitle || channelName;
|
||||
const technicalLabel = `${ownerLogin} / ${channelName}`;
|
||||
const lastMessage = resolveChannelLastMessage(summary);
|
||||
|
||||
return {
|
||||
id: rowId,
|
||||
@@ -737,10 +750,10 @@ function mapApiChannelRow(summary, bucketKey, idx, index, notificationsState) {
|
||||
channelDescription,
|
||||
channelTypeCode,
|
||||
channelTypeVersion,
|
||||
messagePreview: cleanChannelMessagePreview(summary?.lastMessage?.text),
|
||||
messagePreview: cleanChannelMessagePreview(lastMessage.text),
|
||||
messagesCount: Number(summary?.messagesCount || 0),
|
||||
unreadCount: Number(summary?.unreadCount || 0),
|
||||
lastMessageAt: Number(summary?.lastMessage?.createdAtMs || 0),
|
||||
lastMessageAt: Number(lastMessage.createdAtMs || 0),
|
||||
isOwnChannel: isOwn,
|
||||
isSubscribed: !isOwn,
|
||||
notificationsEnabled: notificationsState[rowId] === true,
|
||||
|
||||
@@ -116,9 +116,6 @@ export async function loadProfileSnapshot(login) {
|
||||
});
|
||||
}
|
||||
|
||||
const firstName = String(fields.find((field) => field.key === 'first_name')?.value || '').trim();
|
||||
const lastName = String(fields.find((field) => field.key === 'last_name')?.value || '').trim();
|
||||
|
||||
const latestAccountRole = loadLatestByAliasesFromItems(items, ['account_role']);
|
||||
const rawAccountRole = String(latestAccountRole?.value || '').trim().toLowerCase();
|
||||
const accountRole = rawAccountRole === PROFILE_ACCOUNT_ROLE_PRIMARY || rawAccountRole === PROFILE_ACCOUNT_ROLE_NON_VOTING
|
||||
@@ -160,8 +157,6 @@ export async function loadProfileSnapshot(login) {
|
||||
|
||||
return {
|
||||
fields,
|
||||
firstName,
|
||||
lastName,
|
||||
toggles,
|
||||
accountRole,
|
||||
accountRoleTimeMs: latestAccountRole?.timeMs || 0,
|
||||
|
||||
@@ -10759,3 +10759,35 @@ body.chat-topbar-overlay .page-header.app-topbar-shell .header-center {
|
||||
font-weight: 500;
|
||||
color: rgba(189,207,232,0.72);
|
||||
}
|
||||
|
||||
/* ===== DM header regression fix (2026-09-02) =====
|
||||
* The peer identity button intentionally spans the whole free header area.
|
||||
* Its avatar uses a 119% glass overlay, so neither the center cell nor the
|
||||
* button may clip overflow. Text keeps its own ellipsis boundary instead.
|
||||
*/
|
||||
body.chat-topbar-overlay .page-header.app-topbar-shell .header-center {
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
.chat-header-peer-btn {
|
||||
overflow: visible !important;
|
||||
padding-inline: 2px !important;
|
||||
}
|
||||
|
||||
.chat-header-peer-btn .chat-header-avatar-slot {
|
||||
width: 44px !important;
|
||||
height: 44px !important;
|
||||
min-width: 44px !important;
|
||||
min-height: 44px !important;
|
||||
flex: 0 0 44px !important;
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
.chat-header-peer-btn .chat-header-avatar-slot .chat-header-avatar {
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
.chat-header-peer-text {
|
||||
flex: 1 1 auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user