Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/526-group-bot-label.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
A friend's Bot in a group chat or face-to-face room is now named like everywhere else, Bot name plus its owner's username: `Reviewer (flybot682b8a43)` instead of `Reviewer · FlyBot 682b8a43 (flybot682b8a43)`. An owner name made up from the install id (`FlyBot <id>`) gives way to the username in member lists, message headers, @ menu labels and join, leave and room-lead lines; the full handle stays in the tooltip ([#526](https://github.com/ThinkFlowLab/FlyBot/issues/526)).
35 changes: 27 additions & 8 deletions core/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -1459,9 +1459,28 @@ function pickerBotLabel(bot) {
isChief: bot.remote.isChief
});
}
var AUTO_OWNER = /^flybot[\s._-]*(?=[0-9a-f]*\d)[0-9a-f]{6,}$/i;
function isAutoOwnerName(name) {
return AUTO_OWNER.test(String(name ?? "").trim());
}
function ownerNameOf(record) {
if (!record) return "";
return cleanPeerName(record.ownerName || record.owner || record.homeName || record.ownerHome || record.home || ownerFromHandle(record.handle), "");
const named = cleanPeerName(record.ownerName || record.owner || record.homeName || record.ownerHome || record.home || ownerFromHandle(record.handle), "");
if (!isAutoOwnerName(named)) return named;
const other = [record.ownerName, record.owner, record.homeName].map((value) => cleanPeerName(value, "")).find((value) => value && !isAutoOwnerName(value));
return other || cleanPeerName(ownerFromHandle(record.handle) || ownerFromHandle(record.ownerHome) || ownerFromHandle(record.home), "") || named;
}
function seatOwnerName(ownerName, handle) {
return ownerName ? ownerNameOf({ ownerName, handle }) : "";
}
var ownerKey = (value) => String(value ?? "").toLowerCase().replace(/[\s._@-]+/g, "");
function remoteBotName(record, fallback = "") {
const name = cleanPeerName(record?.displayName || record?.name, fallback);
const at = name.lastIndexOf(" · ");
if (at <= 0) return name;
const tail = ownerKey(name.slice(at + 3));
const owners = [record?.ownerName, record?.owner, record?.homeName, ownerFromHandle(record?.handle), ownerNameOf(record)].map(ownerKey).filter(Boolean);
return isAutoOwnerName(name.slice(at + 3)) || owners.includes(tail) ? name.slice(0, at).trim() : name;
}
function isChiefRecord(record) {
if (!record) return false;
Expand All @@ -1470,7 +1489,7 @@ function isChiefRecord(record) {
}
function remoteBotTitle(record) {
const owner = ownerNameOf(record) || cleanPeerName(record?.handle, "Friend");
const name = cleanPeerName(record?.displayName || record?.name, isChiefRecord(record) ? "Chief" : cleanPeerName(record?.handle, "Bot"));
const name = remoteBotName(record, isChiefRecord(record) ? "Chief" : cleanPeerName(record?.handle, "Bot"));
return `${name} (${owner})`;
}
function remoteOwnerBadge(record) {
Expand Down Expand Up @@ -5094,8 +5113,8 @@ function faceSeats(room2, roster, { left = false } = {}) {
key: p.handle || p.id,
local,
record: p,
person: local ? cleanPeerName(p.ownerName || roster.self?.displayName, "") : ownerNameOf(p),
bot: local ? local.name : cleanPeerName(p.displayName || p.name, "Bot"),
person: local ? seatOwnerName(p.ownerName, p.handle) || cleanPeerName(roster.self?.displayName, "") : ownerNameOf(p),
bot: local ? local.name : remoteBotName(p, "Bot"),
mine: Boolean(local)
};
};
Expand Down Expand Up @@ -8604,7 +8623,7 @@ var normHandle = (handle) => String(handle ?? "").trim().replace(/^@+/, "").toLo
var remoteRecord = (bot) => bot?.remote ? bot.remote === true ? bot : bot.remote : null;
var myOwnerName = (roster, live, bot) => {
const seat = live.find((p) => !p.remote && p.kind !== "human" && (p.id === bot.id || bot.handle && p.handle === bot.handle));
return cleanPeerName(seat?.ownerName || roster.self?.displayName, "");
return seatOwnerName(seat?.ownerName, seat?.handle) || cleanPeerName(roster.self?.displayName, "");
};
var uniqueBy = (list, key) => {
const seen = /* @__PURE__ */ new Set();
Expand Down Expand Up @@ -12137,7 +12156,7 @@ var handleKey = (handle) => String(handle ?? "").trim().replace(/^@+/, "").toLow
var isLeadSeat = (seat, lead) => Boolean(handleKey(lead) && handleKey(seat.record?.handle) === handleKey(lead));
function FacePersonRow({ person, onOpenBot, lead, className = "bt-set-row bt-bs-row bt-gs-member", size = 26 }) {
const bots = person.seats.flatMap((seat, index) => {
const name = seat.local && onOpenBot ? (0, import_react30.createElement)("button", { key: seat.key, type: "button", className: "bt-face-bot", title: t("Open {name}'s chat", { name: seat.bot }), onClick: () => onOpenBot(seat.local.id) }, seat.bot) : (0, import_react30.createElement)("span", { key: seat.key, className: "bt-face-bot" }, seat.bot);
const name = seat.local && onOpenBot ? (0, import_react30.createElement)("button", { key: seat.key, type: "button", className: "bt-face-bot", title: t("Open {name}'s chat", { name: seat.bot }), onClick: () => onOpenBot(seat.local.id) }, seat.bot) : (0, import_react30.createElement)("span", { key: seat.key, className: "bt-face-bot", title: seat.record?.handle || void 0 }, seat.bot);
const marked = isLeadSeat(seat, lead) ? [name, (0, import_react30.createElement)("span", { key: `${seat.key}-lead`, className: "bt-tag", "data-flybot": "face-lead-tag" }, t("Lead"))] : [name];
return index === 0 ? marked : [(0, import_react30.createElement)("span", { key: `${seat.key}-sep` }, t(", ")), ...marked];
});
Expand Down Expand Up @@ -12351,7 +12370,7 @@ function GroupSettingsForm({ room: room2, roster, actions, run, onDeleted }) {
"span",
{ className: "bt-gs-who" },
(0, import_react30.createElement)(BotAvatar, { bot: lookBotForRemote(person, roster), size: 26, badge: false, live: false }),
(0, import_react30.createElement)("span", { className: "bt-row-title" }, person.label || remoteBotTitle(person)),
(0, import_react30.createElement)("span", { className: "bt-row-title", title: person.handle || void 0 }, person.label || remoteBotTitle(person)),
room2.origin === "face-to-face" ? null : (0, import_react30.createElement)("span", { className: "bt-tag" }, t("Friend's Bot"))
),
room2.origin === "face-to-face" || members.length + remotes.length <= 2 ? null : (0, import_react30.createElement)("button", { type: "button", className: "bt-x", "aria-label": t("Remove {name} from the group", { name: remoteBotTitle(person) }), title: t("Remove from group"), onClick: () => update({ removeParticipants: [{ kind: "bot", id: person.handle || person.id, remote: true }] }) }, "×")
Expand Down Expand Up @@ -14819,7 +14838,7 @@ function GroupOverview({ room: room2, roster, actions, usage, useWork, openSetti
"span",
{ className: "bt-member-name" },
(0, import_react37.createElement)(BotAvatar, { bot: lookBotForRemote(person, roster), size: 26, badge: false, live: false }),
(0, import_react37.createElement)("span", null, person.label || remoteBotTitle(person))
(0, import_react37.createElement)("span", { title: person.handle || void 0 }, person.label || remoteBotTitle(person))
),
face ? null : (0, import_react37.createElement)("span", { className: "bt-chip" }, t("Friend's Bot"))
)
Expand Down
4 changes: 2 additions & 2 deletions core/src/client/at-menu.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// rows stay beside them, because a source cannot be hidden for one Session (a face
// room filters the session rows out of view: face-at-filter.js).
import { mentionCandidates, remoteMentionRows } from './flybot-registry.js'
import { cleanPeerName, friendBotTitle, ownerNameOf, remoteBotTitle, listedChatBots } from '../shared/remote-label.js'
import { cleanPeerName, friendBotTitle, ownerNameOf, remoteBotTitle, listedChatBots, seatOwnerName } from '../shared/remote-label.js'
import { t } from './i18n.js'
import { currentPart, relativeShort } from './text.js'

Expand Down Expand Up @@ -78,7 +78,7 @@ export const remoteRecord = bot => (bot?.remote ? (bot.remote === true ? bot : b
// As `faceSeats` names it: the owner name Bot Social stamped on my Bot's seat, else my own name.
const myOwnerName = (roster, live, bot) => {
const seat = live.find(p => !p.remote && p.kind !== 'human' && (p.id === bot.id || (bot.handle && p.handle === bot.handle)))
return cleanPeerName(seat?.ownerName || roster.self?.displayName, '')
return seatOwnerName(seat?.ownerName, seat?.handle) || cleanPeerName(roster.self?.displayName, '')
}
const uniqueBy = (list, key) => {
const seen = new Set()
Expand Down
2 changes: 1 addition & 1 deletion core/src/client/details-drawer.js
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ function GroupOverview({ room, roster, actions, usage, useWork, openSettings })
h('div', { className: 'bt-member' },
h('span', { className: 'bt-member-name' },
h(BotAvatar, { bot: lookBotForRemote(person, roster), size: 26, badge: false, live: false }),
h('span', null, person.label || remoteBotTitle(person))),
h('span', { title: person.handle || undefined }, person.label || remoteBotTitle(person))),
face ? null : h('span', { className: 'bt-chip' }, t('Friend\'s Bot'))))),
...members.map((member) => {
const entry = entries.find(item => item.id === member.id)
Expand Down
6 changes: 3 additions & 3 deletions core/src/client/face-seats.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// tiles, the @ menu, the bubble headers and the @ chips in a bubble (#229, #305, #457). Pure, so
// the bubble module can use it.
import { t } from './i18n.js'
import { cleanPeerName, ownerNameOf } from '../shared/remote-label.js'
import { cleanPeerName, ownerNameOf, remoteBotName, seatOwnerName } from '../shared/remote-label.js'

// A face-to-face room lists people, each with the Bots they brought, and names every Bot one way
// for every member: "Chief (Bob)", my own "Chief (Ann)" too (#229, #384, #457). Names come from
Expand All @@ -17,8 +17,8 @@ export function faceSeats(room, roster, { left = false } = {}) {
key: p.handle || p.id,
local,
record: p,
person: local ? cleanPeerName(p.ownerName || roster.self?.displayName, '') : ownerNameOf(p),
bot: local ? local.name : cleanPeerName(p.displayName || p.name, 'Bot'),
person: local ? (seatOwnerName(p.ownerName, p.handle) || cleanPeerName(roster.self?.displayName, '')) : ownerNameOf(p),
bot: local ? local.name : remoteBotName(p, 'Bot'),
mine: Boolean(local),
}
}
Expand Down
4 changes: 2 additions & 2 deletions core/src/client/group-settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export function FacePersonRow({ person, onOpenBot, lead, className = 'bt-set-row
const bots = person.seats.flatMap((seat, index) => {
const name = seat.local && onOpenBot
? h('button', { key: seat.key, type: 'button', className: 'bt-face-bot', title: t("Open {name}'s chat", { name: seat.bot }), onClick: () => onOpenBot(seat.local.id) }, seat.bot)
: h('span', { key: seat.key, className: 'bt-face-bot' }, seat.bot)
: h('span', { key: seat.key, className: 'bt-face-bot', title: seat.record?.handle || undefined }, seat.bot)
const marked = isLeadSeat(seat, lead) ? [name, h('span', { key: `${seat.key}-lead`, className: 'bt-tag', 'data-flybot': 'face-lead-tag' }, t('Lead'))] : [name]
return index === 0 ? marked : [h('span', { key: `${seat.key}-sep` }, t(', ')), ...marked]
})
Expand Down Expand Up @@ -184,7 +184,7 @@ export function GroupSettingsForm({ room, roster, actions, run, onDeleted }) {
remotes.map((person) => h('div', { key: person.handle || person.id, className: 'bt-set-row bt-bs-row bt-gs-member' },
h('span', { className: 'bt-gs-who' },
h(BotAvatar, { bot: lookBotForRemote(person, roster), size: 26, badge: false, live: false }),
h('span', { className: 'bt-row-title' }, person.label || remoteBotTitle(person)),
h('span', { className: 'bt-row-title', title: person.handle || undefined }, person.label || remoteBotTitle(person)),
room.origin === 'face-to-face' ? null : h('span', { className: 'bt-tag' }, t('Friend\'s Bot'))),
room.origin === 'face-to-face' || members.length + remotes.length <= 2
? null
Expand Down
33 changes: 31 additions & 2 deletions core/src/shared/remote-label.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,38 @@ export function pickerBotLabel(bot) {
})
}

// An owner name made up from an install id ("FlyBot 682b8a43") names nobody (#526): the owner's
// username, the part of the home handle Bot Social derived from it, does.
const AUTO_OWNER = /^flybot[\s._-]*(?=[0-9a-f]*\d)[0-9a-f]{6,}$/i
export function isAutoOwnerName(name) {
return AUTO_OWNER.test(String(name ?? '').trim())
}

export function ownerNameOf(record) {
if (!record) return ''
return cleanPeerName(record.ownerName || record.owner || record.homeName || record.ownerHome || record.home || ownerFromHandle(record.handle), '')
const named = cleanPeerName(record.ownerName || record.owner || record.homeName || record.ownerHome || record.home || ownerFromHandle(record.handle), '')
if (!isAutoOwnerName(named)) return named
const other = [record.ownerName, record.owner, record.homeName].map(value => cleanPeerName(value, '')).find(value => value && !isAutoOwnerName(value))
return other || cleanPeerName(ownerFromHandle(record.handle) || ownerFromHandle(record.ownerHome) || ownerFromHandle(record.home), '') || named
}

// The owner Bot Social stamped on my own Bot's seat, read the same way (#526); none: ''.
export function seatOwnerName(ownerName, handle) {
return ownerName ? ownerNameOf({ ownerName, handle }) : ''
}

const ownerKey = value => String(value ?? '').toLowerCase().replace(/[\s._@-]+/g, '')

// Bot Social labels a room member "<Bot> · <owner>". As a Bot's name that repeats the owner the
// title adds anyway (#526: "Reviewer · FlyBot 682b8a43 (flybot682b8a43)"), so only the Bot's part
// stays. A name whose tail is not that owner is the Bot's own name and is kept whole.
export function remoteBotName(record, fallback = '') {
const name = cleanPeerName(record?.displayName || record?.name, fallback)
const at = name.lastIndexOf(' · ')
if (at <= 0) return name
const tail = ownerKey(name.slice(at + 3))
const owners = [record?.ownerName, record?.owner, record?.homeName, ownerFromHandle(record?.handle), ownerNameOf(record)].map(ownerKey).filter(Boolean)
return isAutoOwnerName(name.slice(at + 3)) || owners.includes(tail) ? name.slice(0, at).trim() : name
}

export function isChiefRecord(record) {
Expand All @@ -90,7 +119,7 @@ export function isChiefRecord(record) {
// its owner in parentheses: "Helper (Bob)", and a friend's Chief the same way: "Chief (Bob)".
export function remoteBotTitle(record) {
const owner = ownerNameOf(record) || cleanPeerName(record?.handle, 'Friend')
const name = cleanPeerName(record?.displayName || record?.name, isChiefRecord(record) ? 'Chief' : cleanPeerName(record?.handle, 'Bot'))
const name = remoteBotName(record, isChiefRecord(record) ? 'Chief' : cleanPeerName(record?.handle, 'Bot'))
return `${name} (${owner})`
}

Expand Down
Loading
Loading