* Hide reactions, reposts, bots from feed

* Improve feed performance
This commit is contained in:
styppo 2023-01-17 00:13:15 +00:00
parent ecbb2f98e3
commit 2e78bdd51e
No known key found for this signature in database
GPG Key ID: 3AAA685C50724C28
5 changed files with 87 additions and 37 deletions

View File

@ -44,7 +44,6 @@ export default defineComponent({
methods: { methods: {
async load() { async load() {
if (this.loading) return if (this.loading) return
console.log('loading')
this.loading = true this.loading = true
this.$emit('loading') this.$emit('loading')

View File

@ -41,21 +41,28 @@ export default class Note {
return this.eventRefs().ancestor() return this.eventRefs().ancestor()
} }
pubkeyTags() {
return this.tags.filter(tag => tag.type === TagType.PUBKEY)
}
eventTags() {
return this.tags.filter(tag => tag.type === TagType.EVENT)
}
pubkeyRefs() { pubkeyRefs() {
return this.tags return this.pubkeyTags().map(tag => tag.ref)
.filter(tag => tag.type === TagType.PUBKEY)
.map(tag => tag.ref)
} }
eventRefs() { eventRefs() {
const refs = this.tags return new EventRefs(this.eventTags().map(tag => tag.ref))
.filter(tag => tag.type === TagType.EVENT) }
.map(tag => tag.ref)
return new EventRefs(refs) relatedPubkeys() {
return [this.author].concat(this.pubkeyRefs())
} }
contentTagRefs() { contentTagRefs() {
const regex = /#\[([0-9]+)]/ig const regex = /#\[([0-9]+)]/g
let refs = [] let refs = []
let match let match
while ((match = regex.exec(this.content))) { while ((match = regex.exec(this.content))) {
@ -64,9 +71,16 @@ export default class Note {
return refs return refs
} }
isRepostOrTag() {
return Note.isRepostOrTag(this)
}
static isRepostOrTag(event) {
return /#\[([0-9]+)]/.test(event.content)
}
isReaction() { isReaction() {
return this.kind === EventKind.REACTION return Note.isReaction(this)
|| (this.hasAncestor() && Note.isReactionContent(this.content))
} }
static isReaction(event) { static isReaction(event) {

View File

@ -22,6 +22,9 @@ export const useStatStore = defineStore('stat', {
if (Note.isReaction(event)) { if (Note.isReaction(event)) {
const stats = this.getOrInit(event.eventRefs().ancestor()) const stats = this.getOrInit(event.eventRefs().ancestor())
stats.reactions++ stats.reactions++
} else if (Note.isRepostOrTag(event)) {
const stats = this.getOrInit(event.eventRefs().ancestor())
stats.shares++
} else { } else {
for (const eventId of event.eventRefs()) { for (const eventId of event.eventRefs()) {
const stats = this.getOrInit(eventId) const stats = this.getOrInit(eventId)

View File

@ -33,13 +33,13 @@
<div class="load-more-container" :class="{'more-available': numUnreads}"> <div class="load-more-container" :class="{'more-available': numUnreads}">
<AsyncLoadButton <AsyncLoadButton
v-if="numUnreads" v-if="numUnreads"
:load-fn="loadUnreads" :load-fn="loadNewer"
:label="`Load ${numUnreads} unreads`" :label="`Load ${numUnreads} unreads`"
/> />
</div> </div>
<template v-for="(thread, i) in feedItems"> <template v-for="(thread, i) in feedItems">
<Thread v-if="defer(i)" :key="thread[0].id" :thread="thread" class="full-width" /> <Thread v-if="true || defer(i)" :key="thread[0].id" :thread="thread" class="full-width" />
</template> </template>
<ListPlaceholder :count="feedItems?.length" :loading="loading" /> <ListPlaceholder :count="feedItems?.length" :loading="loading" />
@ -66,6 +66,7 @@ import {useNostrStore} from 'src/nostr/NostrStore'
import Defer from 'src/utils/Defer' import Defer from 'src/utils/Defer'
import {EventKind} from 'src/nostr/model/Event' import {EventKind} from 'src/nostr/model/Event'
import DateUtils from 'src/utils/DateUtils' import DateUtils from 'src/utils/DateUtils'
import Bots from 'src/utils/bots'
const Feeds = { const Feeds = {
global: { global: {
@ -79,6 +80,8 @@ const Feeds = {
const feedOrder = (a, b) => b[0].createdAt - a[0].createdAt const feedOrder = (a, b) => b[0].createdAt - a[0].createdAt
const MAX_ITEMS_VISIBLE = 50
export default defineComponent({ export default defineComponent({
name: 'Feed', name: 'Feed',
components: { components: {
@ -110,14 +113,11 @@ export default defineComponent({
return this.feeds[this.selectedFeed] return this.feeds[this.selectedFeed]
}, },
feedItems() { feedItems() {
return this.activeFeed?.items return this.activeFeed?.visible
},
feedUnreads() {
return this.activeFeed?.unreads
}, },
numUnreads() { numUnreads() {
if (this.recentlyLoaded) return 0 if (this.recentlyLoaded) return 0
return this.activeFeed?.unreads.length return this.activeFeed?.newer.length
}, },
}, },
methods: { methods: {
@ -129,22 +129,27 @@ export default defineComponent({
filters, filters,
{subId: `feed:${feedId}`} {subId: `feed:${feedId}`}
) )
stream.on('init', events => { stream.on('init', notes => {
const items = events.map(event => [event]) // TODO Single element thread const items = notes
.filter(this.filterNote.bind(this))
.map(note => [note]) // TODO Single element thread
items.sort(feedOrder) items.sort(feedOrder)
this.feeds[feedId].items = items.slice(0, filters.limit) this.feeds[feedId].visible = items.slice(0, filters.limit)
this.loading = false this.loading = false
// Wait a bit before showing the first unreads // Wait a bit before showing the first unreads
setTimeout(() => this.recentlyLoaded = false, 5000) setTimeout(() => this.recentlyLoaded = false, 5000)
}) })
stream.on('update', event => { stream.on('update', note => {
this.feeds[feedId].unreads.push([event]) // TODO Single element thread if (this.filterNote(note)) {
this.feeds[feedId].newer.push([note]) // TODO Single element thread
}
}) })
this.feeds[feedId] = { this.feeds[feedId] = {
items: [], visible: [],
unreads: [], newer: [],
older: [],
stream, stream,
} }
}, },
@ -152,12 +157,18 @@ export default defineComponent({
this.initFeed(feedId) this.initFeed(feedId)
this.selectedFeed = feedId this.selectedFeed = feedId
}, },
loadUnreads() { loadNewer() {
// TODO Deduplicate feed items // TODO Deduplicate feed items
const items = this.feedUnreads.concat(this.feedItems) this.activeFeed.newer.sort(feedOrder)
const items = this.activeFeed.newer.concat(this.feedItems)
if (items.length > MAX_ITEMS_VISIBLE) {
const older = items.splice(MAX_ITEMS_VISIBLE)
this.activeFeed.older = older.concat(this.activeFeed.older)
}
//items.sort(feedOrder) //items.sort(feedOrder)
this.activeFeed.items = items
this.activeFeed.unreads = [] this.activeFeed.visible = items
this.activeFeed.newer = []
// Wait a bit before showing unreads again // Wait a bit before showing unreads again
this.recentlyLoaded = true this.recentlyLoaded = true
@ -167,22 +178,35 @@ export default defineComponent({
}, },
async loadOlder() { async loadOlder() {
const until = this.feedItems[this.feedItems.length - 1]?.[0]?.createdAt || DateUtils.now() const until = this.feedItems[this.feedItems.length - 1]?.[0]?.createdAt || DateUtils.now()
console.log('until', new Date(until * 1000))
const filters = Object.assign({}, Feeds[this.selectedFeed].filters, {until}) const filters = Object.assign({}, Feeds[this.selectedFeed].filters, {until})
if (this.activeFeed.older.length >= filters.limit) {
const chunk = this.activeFeed.older.splice(0, filters.limit)
this.activeFeed.visible = this.feedItems.concat(chunk)
return chunk
}
// Remove any residual older items
this.activeFeed.older = []
const older = await this.nostr.fetch(filters, {subId: `feed:${this.selectedFeed}-older`}) const older = await this.nostr.fetch(filters, {subId: `feed:${this.selectedFeed}-older`})
const items = older.map(event => [event]).sort(feedOrder) const items = older
.filter(note => note.createdAt <= until)
.filter(this.filterNote.bind(this))
.map(note => [note]) // TODO Single element thread
.sort(feedOrder)
console.log('got items', older)
console.log('length before', this.activeFeed.items.length)
// TODO Deduplicate feed items // TODO Deduplicate feed items
this.activeFeed.items = this.feedItems.concat(items) this.activeFeed.visible = this.feedItems.concat(items)
console.log('length after', this.activeFeed.items.length)
return older return older
}, },
filterNote(note) {
if (note.isReaction()) return false
if (note.isRepostOrTag()) return false
if (note.relatedPubkeys().some(Bots.isBot)) return false
return true
}
}, },
mounted() { mounted() {
this.initFeed(this.selectedFeed) this.initFeed(this.selectedFeed)

10
src/utils/bots.js Normal file
View File

@ -0,0 +1,10 @@
import {bech32ToHex} from 'src/utils/utils'
const Bots = new Set()
export default Bots
Bots.isBot = Bots.has.bind(Bots)
Bots.add = pubkey => Set.prototype.add.call(Bots, bech32ToHex(pubkey))
Bots.add('npub1tsgw6pncspg4d5u778hk63s3pls70evs4czfsmx0fzap9xwt203qtkhtk4') // gpt3
Bots.add('npub17stpezz4suqdywh33k9x8pht04l76a5sfrsjj7q3mnp5ap5937eqdt58d7') // bitcoin_bot
Bots.add('npub1xe59lfgsdvduqwh8h65zahkc2hv02mzpmdxghhhcpx0puret9taqheapxc') // moe_bot