diff --git a/src/components/AsyncLoadButton.vue b/src/components/AsyncLoadButton.vue
index add7477..c3dc02b 100644
--- a/src/components/AsyncLoadButton.vue
+++ b/src/components/AsyncLoadButton.vue
@@ -44,7 +44,6 @@ export default defineComponent({
methods: {
async load() {
if (this.loading) return
- console.log('loading')
this.loading = true
this.$emit('loading')
diff --git a/src/nostr/model/Note.js b/src/nostr/model/Note.js
index 038d567..c21eec6 100644
--- a/src/nostr/model/Note.js
+++ b/src/nostr/model/Note.js
@@ -41,21 +41,28 @@ export default class Note {
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() {
- return this.tags
- .filter(tag => tag.type === TagType.PUBKEY)
- .map(tag => tag.ref)
+ return this.pubkeyTags().map(tag => tag.ref)
}
eventRefs() {
- const refs = this.tags
- .filter(tag => tag.type === TagType.EVENT)
- .map(tag => tag.ref)
- return new EventRefs(refs)
+ return new EventRefs(this.eventTags().map(tag => tag.ref))
+ }
+
+ relatedPubkeys() {
+ return [this.author].concat(this.pubkeyRefs())
}
contentTagRefs() {
- const regex = /#\[([0-9]+)]/ig
+ const regex = /#\[([0-9]+)]/g
let refs = []
let match
while ((match = regex.exec(this.content))) {
@@ -64,9 +71,16 @@ export default class Note {
return refs
}
+ isRepostOrTag() {
+ return Note.isRepostOrTag(this)
+ }
+
+ static isRepostOrTag(event) {
+ return /#\[([0-9]+)]/.test(event.content)
+ }
+
isReaction() {
- return this.kind === EventKind.REACTION
- || (this.hasAncestor() && Note.isReactionContent(this.content))
+ return Note.isReaction(this)
}
static isReaction(event) {
diff --git a/src/nostr/store/StatStore.js b/src/nostr/store/StatStore.js
index 89f20d4..1467713 100644
--- a/src/nostr/store/StatStore.js
+++ b/src/nostr/store/StatStore.js
@@ -22,6 +22,9 @@ export const useStatStore = defineStore('stat', {
if (Note.isReaction(event)) {
const stats = this.getOrInit(event.eventRefs().ancestor())
stats.reactions++
+ } else if (Note.isRepostOrTag(event)) {
+ const stats = this.getOrInit(event.eventRefs().ancestor())
+ stats.shares++
} else {
for (const eventId of event.eventRefs()) {
const stats = this.getOrInit(eventId)
diff --git a/src/pages/Feed.vue b/src/pages/Feed.vue
index d18e77c..09982b3 100644
--- a/src/pages/Feed.vue
+++ b/src/pages/Feed.vue
@@ -33,13 +33,13 @@
-
+
@@ -66,6 +66,7 @@ import {useNostrStore} from 'src/nostr/NostrStore'
import Defer from 'src/utils/Defer'
import {EventKind} from 'src/nostr/model/Event'
import DateUtils from 'src/utils/DateUtils'
+import Bots from 'src/utils/bots'
const Feeds = {
global: {
@@ -79,6 +80,8 @@ const Feeds = {
const feedOrder = (a, b) => b[0].createdAt - a[0].createdAt
+const MAX_ITEMS_VISIBLE = 50
+
export default defineComponent({
name: 'Feed',
components: {
@@ -110,14 +113,11 @@ export default defineComponent({
return this.feeds[this.selectedFeed]
},
feedItems() {
- return this.activeFeed?.items
- },
- feedUnreads() {
- return this.activeFeed?.unreads
+ return this.activeFeed?.visible
},
numUnreads() {
if (this.recentlyLoaded) return 0
- return this.activeFeed?.unreads.length
+ return this.activeFeed?.newer.length
},
},
methods: {
@@ -129,22 +129,27 @@ export default defineComponent({
filters,
{subId: `feed:${feedId}`}
)
- stream.on('init', events => {
- const items = events.map(event => [event]) // TODO Single element thread
+ stream.on('init', notes => {
+ const items = notes
+ .filter(this.filterNote.bind(this))
+ .map(note => [note]) // TODO Single element thread
items.sort(feedOrder)
- this.feeds[feedId].items = items.slice(0, filters.limit)
+ this.feeds[feedId].visible = items.slice(0, filters.limit)
this.loading = false
// Wait a bit before showing the first unreads
setTimeout(() => this.recentlyLoaded = false, 5000)
})
- stream.on('update', event => {
- this.feeds[feedId].unreads.push([event]) // TODO Single element thread
+ stream.on('update', note => {
+ if (this.filterNote(note)) {
+ this.feeds[feedId].newer.push([note]) // TODO Single element thread
+ }
})
this.feeds[feedId] = {
- items: [],
- unreads: [],
+ visible: [],
+ newer: [],
+ older: [],
stream,
}
},
@@ -152,12 +157,18 @@ export default defineComponent({
this.initFeed(feedId)
this.selectedFeed = feedId
},
- loadUnreads() {
+ loadNewer() {
// 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)
- this.activeFeed.items = items
- this.activeFeed.unreads = []
+
+ this.activeFeed.visible = items
+ this.activeFeed.newer = []
// Wait a bit before showing unreads again
this.recentlyLoaded = true
@@ -167,22 +178,35 @@ export default defineComponent({
},
async loadOlder() {
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})
+ 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 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
- this.activeFeed.items = this.feedItems.concat(items)
-
- console.log('length after', this.activeFeed.items.length)
+ this.activeFeed.visible = this.feedItems.concat(items)
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() {
this.initFeed(this.selectedFeed)
diff --git a/src/utils/bots.js b/src/utils/bots.js
new file mode 100644
index 0000000..5e65637
--- /dev/null
+++ b/src/utils/bots.js
@@ -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