Phase 435: search query filters, pin logging, and guild.search errors (v0.8.398).
parseSearchQuery integration via _applySearchQueryFilters; channel search structured filters; pin.message and pin.unpin spans with error logs; guild.search error logging on failure. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
@@ -137,6 +137,8 @@ Primary consumer: `apps/pearcord/index.js` sidecar reading pear-pipe JSON.
|
||||
|
||||
**v0.8.397:** `ingestInboundGuildMessage(msg)` processes guild mesh gossip while on home (mention alerts + inbox via DB member lookup). `markAllNotificationsRead` / `openNotificationTarget` / `markChannelRead` log structured errors. Smoke helper: `simulateGuildMessage(platform, msg)` in `smoke-runner.cjs`.
|
||||
|
||||
**v0.8.398:** `parseSearchQuery` + `_applySearchQueryFilters` for guild/channel search; `pinMessage`/`unpinMessage` log `pin.message`/`pin.unpin` spans and errors; `guild.search error` on search failure.
|
||||
|
||||
**v0.8.252:** Guild search mesh skipped when `guildOpenNoViewableChannels`; emoji batch prefetch skips negative miss TTL; sticker gossip prefetch gated on `attachments`; `session.startup` logs INFO when initial guild mesh flush was bounded. Smoke: `test:guild-search-no-viewable-skip`, `test:sticker-prefetch-attachments-ready`.
|
||||
|
||||
**v0.8.248:** Guild open filters unread/last picks with `_canViewChannel`; sets `guildOpenNoViewableChannels` when none accessible; defers `_ensureGuildModules` via `setTimeout(0)` after channel pick; emoji mesh prefetch clears miss cache on success. Smokes: `test:guild-viewable-channel-pick`, `test:guild-open-no-viewable`, `test:guild-plain-beats-dual-combo`.
|
||||
|
||||
@@ -1712,25 +1712,50 @@ class PearcordPlatform extends EventEmitter {
|
||||
return []
|
||||
}
|
||||
const qLower = String(q).toLowerCase()
|
||||
const { parseSearchQuery, rankAndEnrichSearchHits } = require('pearcord-search')
|
||||
const parsed = parseSearchQuery(q)
|
||||
const textNeedle = String(parsed.text || '').toLowerCase()
|
||||
let rows = []
|
||||
if (this.messages?.channelId === channelId) {
|
||||
rows = await this.messages.history(200)
|
||||
} else {
|
||||
rows = await this.db.find(COLLECTIONS.MESSAGES, { channelId })
|
||||
}
|
||||
const pinRows = await this.db.find(COLLECTIONS.PINS, { channelId })
|
||||
const pinnedIds = new Set(pinRows.map((p) => p.messageId))
|
||||
const members = this.guild ? await this.guild.listMembers() : []
|
||||
const usersById = await this._usersById(members)
|
||||
const chRow = await this.db.get(COLLECTIONS.CHANNELS, { guildId: this.guild.guild.id, id: channelId })
|
||||
const rawHits = []
|
||||
for (const row of rows) {
|
||||
if (row.channelId && row.channelId !== channelId) continue
|
||||
const enriched = enrichMessageRow(row)
|
||||
const plain = this._messagePlaintext(enriched)
|
||||
if (textNeedle && !plain.toLowerCase().includes(textNeedle)) continue
|
||||
if (!textNeedle && !parsed.from && !parsed.has && !parsed.before && !parsed.after) {
|
||||
if (!plain.toLowerCase().includes(qLower)) continue
|
||||
}
|
||||
const mem = members.find((m) => m.userId === enriched.authorId)
|
||||
const u = usersById[enriched.authorId] || {}
|
||||
const { messageMatchesSearchFilters } = require('pearcord-search')
|
||||
if (
|
||||
!messageMatchesSearchFilters(enriched, parsed, {
|
||||
plaintext: plain,
|
||||
authorTokens: [mem?.nickname, u.username, u.displayName].filter(Boolean),
|
||||
authorId: enriched.authorId,
|
||||
channelName: chRow?.name,
|
||||
isPinned: pinnedIds.has(enriched.id),
|
||||
attachmentIds: enriched.attachmentIds
|
||||
})
|
||||
) {
|
||||
continue
|
||||
}
|
||||
rawHits.push({
|
||||
...enriched,
|
||||
channelId
|
||||
})
|
||||
}
|
||||
const { rankAndEnrichSearchHits } = require('pearcord-search')
|
||||
const hits = rankAndEnrichSearchHits(rawHits, q, { limit: 50 })
|
||||
const hits = rankAndEnrichSearchHits(rawHits, parsed.text || q, { limit: 50 })
|
||||
const searchMs = Date.now() - searchStarted
|
||||
this._lastSearchMeshMeta = {
|
||||
mesh: false,
|
||||
@@ -1751,6 +1776,7 @@ class PearcordPlatform extends EventEmitter {
|
||||
limit: 50,
|
||||
mesh: this._searchIncludeMesh
|
||||
})
|
||||
const filtered = await this._applySearchQueryFilters(out.hits, q, this.guild.guild.id)
|
||||
this._lastSearchMeshMeta = {
|
||||
mesh: out.mesh,
|
||||
meshHitCount: out.meshHitCount || 0,
|
||||
@@ -1763,18 +1789,22 @@ class PearcordPlatform extends EventEmitter {
|
||||
const searchMs = Date.now() - searchStarted
|
||||
this._lastSearchMeshMeta = { ...this._lastSearchMeshMeta, searchMs }
|
||||
span.end({
|
||||
count: out.hits?.length || 0,
|
||||
count: filtered?.length || 0,
|
||||
scope: 'guild',
|
||||
mesh: !!out.mesh,
|
||||
searchMs
|
||||
})
|
||||
this._commitSearchCache(cacheKey, out.hits)
|
||||
return out.hits
|
||||
this._commitSearchCache(cacheKey, filtered)
|
||||
return filtered
|
||||
}
|
||||
span.end({ count: 0, scope: this._searchScope })
|
||||
this._commitSearchCache(cacheKey, [])
|
||||
return []
|
||||
} catch (err) {
|
||||
this.log.error('guild.search error', {
|
||||
scope: this._searchScope,
|
||||
error: err?.message || String(err)
|
||||
})
|
||||
span.fail(err)
|
||||
throw err
|
||||
}
|
||||
@@ -2318,6 +2348,51 @@ class PearcordPlatform extends EventEmitter {
|
||||
return rankAndEnrichSearchHits(mapped, q, { limit })
|
||||
}
|
||||
|
||||
async _applySearchQueryFilters (hits, rawQuery, guildId) {
|
||||
const {
|
||||
parseSearchQuery,
|
||||
messageMatchesSearchFilters,
|
||||
hasStructuredSearchFilters
|
||||
} = require('pearcord-search')
|
||||
const filters = parseSearchQuery(rawQuery)
|
||||
if (!hasStructuredSearchFilters(filters)) return hits
|
||||
const gId = guildId || this.guild?.guild?.id
|
||||
const members = this.guild
|
||||
? await this.guild.listMembers()
|
||||
: gId
|
||||
? await this.db.find(COLLECTIONS.MEMBERS, { guildId: gId })
|
||||
: []
|
||||
const usersById = await this._usersById(members)
|
||||
const channels = gId ? await this.db.find(COLLECTIONS.CHANNELS, { guildId: gId }) : []
|
||||
const channelById = new Map(channels.map((c) => [c.id, c]))
|
||||
let pinnedKeys = null
|
||||
if (filters.has === 'pin' && gId) {
|
||||
const pins = await this.db.find(COLLECTIONS.PINS, { guildId: gId })
|
||||
pinnedKeys = new Set(pins.map((p) => `${p.channelId}:${p.messageId}`))
|
||||
}
|
||||
const out = []
|
||||
for (const hit of hits || []) {
|
||||
const mem = members.find((m) => m.userId === hit.authorId)
|
||||
const u = usersById[hit.authorId] || {}
|
||||
const ch = channelById.get(hit.channelId)
|
||||
const plain = this._messagePlaintext(hit)
|
||||
if (
|
||||
!messageMatchesSearchFilters(hit, filters, {
|
||||
plaintext: plain,
|
||||
authorTokens: [mem?.nickname, u.username, u.displayName].filter(Boolean),
|
||||
authorId: hit.authorId,
|
||||
channelName: ch?.name || hit.channelName,
|
||||
isPinned: pinnedKeys ? pinnedKeys.has(`${hit.channelId}:${hit.id}`) : false,
|
||||
attachmentIds: hit.attachmentIds
|
||||
})
|
||||
) {
|
||||
continue
|
||||
}
|
||||
out.push(hit)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
getGuildSearchIndexMeta (guildId) {
|
||||
const id = guildId || this.guild?.guild?.id
|
||||
if (!id) return { persisted: false, rowCount: 0, savedAt: null }
|
||||
@@ -10254,6 +10329,8 @@ class PearcordPlatform extends EventEmitter {
|
||||
}
|
||||
|
||||
async pinMessage (messageId) {
|
||||
const span = this.log.time('pin.message', { messageId })
|
||||
try {
|
||||
const user = this.identity.user
|
||||
if (!user || !this.guild?.guild) throw new Error('no guild')
|
||||
const roles = await this._memberRoles()
|
||||
@@ -10275,10 +10352,21 @@ class PearcordPlatform extends EventEmitter {
|
||||
await this.db.insert(COLLECTIONS.PINS, pin)
|
||||
this.guild.gossipPin(pin)
|
||||
this.emit('pin', pin)
|
||||
span.end({ channelId: pin.channelId })
|
||||
return pin
|
||||
} catch (err) {
|
||||
this.log.error('pin.message error', {
|
||||
messageId,
|
||||
error: err?.message || String(err)
|
||||
})
|
||||
span.fail(err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async unpinMessage (messageId) {
|
||||
const span = this.log.time('pin.unpin', { messageId })
|
||||
try {
|
||||
const roles = await this._memberRoles()
|
||||
if (!roleHasPermission(roles, PERMISSION.MANAGE_MESSAGES)) {
|
||||
throw new Error('no permission to unpin')
|
||||
@@ -10288,6 +10376,15 @@ class PearcordPlatform extends EventEmitter {
|
||||
messageId
|
||||
})
|
||||
this.emit('pin', { channelId: this.activeChannelId, messageId, removed: true })
|
||||
span.end({ channelId: this.activeChannelId })
|
||||
} catch (err) {
|
||||
this.log.error('pin.unpin error', {
|
||||
messageId,
|
||||
error: err?.message || String(err)
|
||||
})
|
||||
span.fail(err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async kickMember ({ userId, reason }) {
|
||||
|
||||
Reference in New Issue
Block a user