-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
1,186 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
BSKY_IDENTIFIER=somebody.bsky.social | ||
BSKY_APP_PASSWORD=<app password here> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
{ | ||
"tasks": { | ||
"update-feed": "deno run --allow-net --allow-env --env-file ./generate-solomon-threads-feed.ts > ../docs/xrpc/app.bsky.feed.getFeedSkeleton/index.json" | ||
}, | ||
"imports": { | ||
"@atproto/api": "npm:@atproto/api@^0.13.32" | ||
} | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import { AtpAgent } from "npm:@atproto/api"; | ||
import { | ||
getIdentifier, | ||
getPassword, | ||
LongformThreadFinder, | ||
} from "./lib/bsky.ts"; | ||
|
||
// handle: solomonmissouri.bsky.social | ||
const authorDID = "did:plc:w6adnkpcgqb67lqi5nxcy7l5"; | ||
const minDepth = 5; | ||
|
||
const agent = new AtpAgent({ | ||
service: "https://bsky.social", | ||
}); | ||
await agent.login({ | ||
identifier: getIdentifier(), | ||
password: getPassword(), | ||
}); | ||
|
||
const threadFinder = new LongformThreadFinder(agent, authorDID, minDepth); | ||
await threadFinder.populateFeedMap(); | ||
|
||
const rootPosts = threadFinder.getRootPosts(); | ||
|
||
const feed = rootPosts.map((r) => { | ||
return { post: r.post.uri }; | ||
}); | ||
|
||
console.log(JSON.stringify({ feed }, undefined, 2)); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
import { AtpAgent } from "npm:@atproto/api"; | ||
import { FeedViewPost } from "@atproto/api"; | ||
|
||
export function getIdentifier() { | ||
const password = Deno.env.get("BSKY_IDENTIFIER"); | ||
if (!password) { | ||
throw new Error( | ||
"BSKY_IDENTIFIER not defined. Did you add it to an .env file and use Deno --env-file?", | ||
); | ||
} | ||
return password; | ||
} | ||
|
||
export function getPassword() { | ||
const password = Deno.env.get("BSKY_APP_PASSWORD"); | ||
if (!password) { | ||
throw new Error( | ||
"BSKY_APP_PASSWORD not defined. Did you add it to an .env file and use Deno --env-file?", | ||
); | ||
} | ||
return password; | ||
} | ||
|
||
// export type FeedMap = { [key: string]: FeedViewPost }; | ||
export type FeedMap = Map<string, FeedViewPost>; | ||
|
||
export class LongformThreadFinder { | ||
agent: AtpAgent; | ||
authorDID: string; | ||
minDepth: number; | ||
feedMap: FeedMap | undefined; | ||
|
||
constructor(agent: AtpAgent, authorDID: string, minDepth: number) { | ||
this.agent = agent; | ||
this.authorDID = authorDID; | ||
this.minDepth = minDepth; | ||
} | ||
|
||
async populateFeedMap() { | ||
this.feedMap = new Map<string, FeedViewPost>(); | ||
|
||
let cursor: string | undefined; | ||
let i = 0; | ||
do { | ||
const { data } = await this.agent.getAuthorFeed({ | ||
actor: this.authorDID, | ||
filter: "posts_and_author_threads", | ||
limit: 100, | ||
cursor, | ||
}); | ||
cursor = data.cursor; | ||
|
||
for (const feedView of data.feed) { | ||
// filter out reposts | ||
if (feedView.post.author.did === this.authorDID) { | ||
this.feedMap.set(feedView.post.uri, feedView); | ||
} | ||
} | ||
|
||
if (i++ > 1000) { | ||
break; | ||
} | ||
} while (cursor); | ||
} | ||
|
||
getRootPosts(): FeedViewPost[] { | ||
if (!this.feedMap) { | ||
throw new Error("call populateFeedMap first"); | ||
} | ||
|
||
const rootFeeds = new Set<FeedViewPost>(); | ||
|
||
for (const feedView of this.feedMap.values()) { | ||
const result = this.getRootAndDepth(feedView.post.uri, 1); | ||
if (result && result.depth >= this.minDepth) { | ||
rootFeeds.add(result.rootFeed); | ||
} | ||
} | ||
|
||
const sortedFeeds = [...rootFeeds.values()].toSorted((a, b) => | ||
b.post.record.createdAt.localeCompare(a.post.record.createdAt) | ||
); | ||
return sortedFeeds; | ||
} | ||
|
||
getRootAndDepth( | ||
postURI: string, | ||
depth: number, | ||
): { rootFeed: FeedViewPost; depth: number } | undefined { | ||
if (!this.feedMap) { | ||
throw new Error("call populateFeedMap first"); | ||
} | ||
|
||
const feed = this.feedMap.get(postURI); | ||
|
||
if (!feed) { | ||
return undefined; | ||
} | ||
|
||
if (!feed.reply) { | ||
return { rootFeed: feed, depth }; | ||
} | ||
|
||
return this.getRootAndDepth(feed.reply.parent.uri, depth + 1); | ||
} | ||
} |
Oops, something went wrong.