1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
|
import {AppBskyActorProfile, AppBskyActorRef} from '@atproto/api'
import {makeAutoObservable, runInAction} from 'mobx'
import sampleSize from 'lodash.samplesize'
import {bundleAsync} from 'lib/async/bundle'
import {RootStoreModel} from '../root-store'
export type RefWithInfoAndFollowers = AppBskyActorRef.WithInfo & {
followers: AppBskyActorProfile.View[]
}
export type ProfileViewFollows = AppBskyActorProfile.View & {
follows: AppBskyActorRef.WithInfo[]
}
export class FoafsModel {
isLoading = false
hasData = false
sources: string[] = []
foafs: Map<string, ProfileViewFollows> = new Map()
popular: RefWithInfoAndFollowers[] = []
constructor(public rootStore: RootStoreModel) {
makeAutoObservable(this)
}
get hasContent() {
if (this.popular.length > 0) {
return true
}
for (const foaf of this.foafs.values()) {
if (foaf.follows.length) {
return true
}
}
return false
}
fetch = bundleAsync(async () => {
try {
this.isLoading = true
await this.rootStore.me.follows.fetchIfNeeded()
// grab 10 of the users followed by the user
this.sources = sampleSize(
Object.keys(this.rootStore.me.follows.followDidToRecordMap),
10,
)
if (this.sources.length === 0) {
return
}
this.foafs.clear()
this.popular.length = 0
// fetch their profiles
const profiles = await this.rootStore.api.app.bsky.actor.getProfiles({
actors: this.sources,
})
// fetch their follows
const results = await Promise.allSettled(
this.sources.map(source =>
this.rootStore.api.app.bsky.graph.getFollows({user: source}),
),
)
// store the follows and construct a "most followed" set
const popular: RefWithInfoAndFollowers[] = []
for (let i = 0; i < results.length; i++) {
const res = results[i]
const profile = profiles.data.profiles[i]
const source = this.sources[i]
if (res.status === 'fulfilled' && profile) {
// filter out users already followed by the user or that *is* the user
res.value.data.follows = res.value.data.follows.filter(follow => {
return (
follow.did !== this.rootStore.me.did &&
!this.rootStore.me.follows.isFollowing(follow.did)
)
})
runInAction(() => {
this.foafs.set(source, {
...profile,
follows: res.value.data.follows,
})
})
for (const follow of res.value.data.follows) {
let item = popular.find(p => p.did === follow.did)
if (!item) {
item = {...follow, followers: []}
popular.push(item)
}
item.followers.push(profile)
}
}
}
popular.sort((a, b) => b.followers.length - a.followers.length)
runInAction(() => {
this.popular = popular.filter(p => p.followers.length > 1).slice(0, 20)
})
this.hasData = true
} catch (e) {
console.error('Failed to fetch FOAFs', e)
} finally {
runInAction(() => {
this.isLoading = false
})
}
})
}
|