1+ // https://gist.github.com/boly38/fb0a83e21bb73c212203c261b3cad287
2+ import { BskyAgent } from '@atproto/api'
3+ import dayjs from "dayjs" ;
4+ import utc from "dayjs/plugin/utc.js"
5+ import timezone from "dayjs/plugin/timezone.js"
6+
7+ dayjs . extend ( utc )
8+ dayjs . extend ( timezone )
9+
10+ /****** lib ******/
11+ const exitFailed = err => {
12+ console . error ( `❌ ${ err . message } ` ) ;
13+ process . exit ( 1 ) ;
14+ }
15+ const nowMinusHoursUTCISO = ( nbHours = 1 ) => dayjs . utc ( ) . subtract ( nbHours , 'hour' ) . toISOString ( )
16+ const expectEnvVariableToBeSet = envKey => {
17+ let val = process . env [ envKey ] ;
18+ if ( val === undefined ) {
19+ console . log ( `please provide a ${ envKey } ` ) ;
20+ process . exit ( 1 ) ;
21+ }
22+ return val ;
23+ }
24+ const retainOnePostByAuthor = ( posts , author ) => posts . filter ( p => p ?. author ?. displayName === author ) [ 0 ] ;
25+ const assumeObjectOrLeave = ( object , msg ) => {
26+ if ( object === undefined ) {
27+ console . log ( msg )
28+ process . exit ( 0 )
29+ }
30+ }
31+ const authorDidFromPost = p => p ?. author ?. did ;
32+
33+ class Bluesky {
34+ constructor ( ) {
35+ this . identifier = expectEnvVariableToBeSet ( "BLUESKY_USERNAME" ) ;
36+ this . password = expectEnvVariableToBeSet ( "BLUESKY_PASSWORD" ) ;
37+ this . service = "https://api.bsky.social" ;
38+ }
39+
40+ async login ( ) {
41+ const { identifier, password, service} = this ;
42+ const agent = new BskyAgent ( { service} )
43+ await agent . login ( { identifier, password} ) ;
44+ this . api = agent . api ;
45+ }
46+
47+ async postSearch ( author ) {
48+ let params = {
49+ "q" : author ,
50+ "sort" : "latest" ,
51+ "limit" : 5 ,
52+ // "since": nowMinusHoursUTCISO(720),
53+ // "until": nowMinusHoursUTCISO(0)
54+ } ;
55+ console . log ( `search ${ JSON . stringify ( params ) } ` ) ;
56+ const response = await this . api . app . bsky . feed . searchPosts ( params , { } ) ;
57+ return response . data . posts ;
58+ }
59+
60+ async showMutedActors ( ) {
61+ const response = await this . api . app . bsky . graph . getMutes ( { limit : 10 } , { } ) ;
62+ const { mutes} = response . data ;
63+ if ( mutes ?. length < 1 ) {
64+ console . log ( "no muted actors" ) ;
65+ return ;
66+ }
67+ console . log ( "muted actors : " , JSON . stringify ( mutes , null , 2 ) ) ;
68+ }
69+
70+ async muteActor ( actorDid ) {
71+ const response = await this . api . app . bsky . graph . muteActor ( { "actor" : actorDid } ) ;
72+ console . log ( `mute ${ actorDid } :` , response . data , response . success ) ;
73+ }
74+
75+ async unmuteActor ( actorDid ) {
76+ const response = await this . api . app . bsky . graph . unmuteActor ( { "actor" : actorDid } ) ;
77+ console . log ( `unmute ${ actorDid } :` , response . data , response . success ) ;
78+ }
79+ }
80+
81+ // ℹ️ to show muted actors via UI : https://bsky.app/moderation/muted-accounts
82+
83+ try {
84+ const author = expectEnvVariableToBeSet ( "BLUESKY_AUTHOR" ) ;
85+ const bluesky = new Bluesky ( ) ;
86+ console . log ( `🧪🧪 login` ) ;
87+
88+ await bluesky . login ( )
89+ const posts = await bluesky . postSearch ( author ) ;
90+ const candidate = retainOnePostByAuthor ( posts , author ) ;
91+ assumeObjectOrLeave ( candidate , "no candidate dude." ) ;
92+ // DEBUG // console.log("post:" + JSON.stringify(candidate, null, 2))
93+ const authorDid = authorDidFromPost ( candidate ) ;
94+ console . log ( `🧍 author ${ author } Did:${ authorDid } ` ) ;
95+ await bluesky . showMutedActors ( ) ;
96+ console . log ( `🧪🧪 mute ${ author } ` ) ;
97+ await bluesky . muteActor ( authorDid ) ;
98+ await bluesky . showMutedActors ( ) ;
99+ const postsWithMuted = await bluesky . postSearch ( author ) ;
100+ const candidateMuted = retainOnePostByAuthor ( postsWithMuted , author ) ;
101+ console . log ( "candidateMuted.author.viewer" , JSON . stringify ( candidateMuted . author . viewer , null , 2 ) )
102+ console . log ( `🧪🧪 unmute ${ author } ` ) ;
103+ await bluesky . unmuteActor ( authorDid ) ;
104+ await bluesky . showMutedActors ( ) ;
105+ const postsWithUnMuted = await bluesky . postSearch ( author ) ;
106+ const candidateUnMuted = retainOnePostByAuthor ( postsWithUnMuted , author ) ;
107+ console . log ( "candidateUnMuted.author.viewer" , JSON . stringify ( candidateUnMuted . author . viewer , null , 2 ) )
108+ } catch ( err ) {
109+ exitFailed ( err ) ;
110+ }
0 commit comments