Skip to content

Commit 1d135bb

Browse files
committed
Fix #46 AskPlantnet root ref, plus add manual test
1 parent f7ec8c3 commit 1d135bb

File tree

12 files changed

+426
-8
lines changed

12 files changed

+426
-8
lines changed

src/domain/post.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ export const fromBlueskyPost = post => {
5151
"$type": record["$type"],
5252
"createdAt": record.createdAt,
5353
"langs": record.langs,
54-
"text": record.text
54+
"text": record.text,
55+
"reply": record.reply
5556
}
5657
};
5758
if (isSet(embed)) {

src/plugins/AskPlantnet.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ export default class AskPlantnet {
77
constructor(config, loggerService, blueskyService, plantnetCommonService, plantnetApiService) {
88
this.isAvailable = false;
99
this.logger = loggerService.getLogger().child({label: 'AskPl@ntNet'});
10-
this.logger.level = "INFO"; // DEBUG will show search results
1110
this.blueskyService = blueskyService;
1211
this.plantnetSimulate = (config.bot.plantnetSimulate === true);
1312
this.plantnetCommonService = plantnetCommonService;

src/plugins/Plantnet.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ export default class Plantnet {
66
constructor(config, loggerService, blueskyService, plantnetCommonService, plantnetApiService) {
77
this.isAvailable = false;
88
this.logger = loggerService.getLogger().child({label: 'Pl@ntNet'});
9-
this.logger.level = "INFO"; // DEBUG will show search results
109
this.blueskyService = blueskyService;
1110
this.plantnetSimulate = (config.bot.plantnetSimulate === true);
1211
this.plantnetCommonService = plantnetCommonService;

src/plugins/UnMute.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import {pluginResolve} from "../services/BotService.js";
33
export default class UnMute {
44
constructor(config, loggerService, blueskyService) {
55
this.logger = loggerService.getLogger().child({label: 'UnMute'});
6-
this.logger.level = "INFO"; // DEBUG will show search results
76
this.blueskyService = blueskyService;
87
this.isAvailable = true;
98
}

src/services/BlueSkyService.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,9 @@ export default class BlueSkyService {
9999
replyTo(post, text, doSimulate, embed = null) {
100100
const bs = this;
101101
return new Promise(async (resolve, reject) => {
102-
const {uri, cid} = post;
102+
const {"uri":parentUri, "cid":parentCid} = post;
103+
const rootUri = post?.record?.reply?.root?.uri || parentUri;
104+
const rootCid = post?.record?.reply?.root?.cid || parentCid;
103105

104106
//~ rich format
105107
const rt = new RichText({text})
@@ -109,7 +111,10 @@ export default class BlueSkyService {
109111
bs.logger.error(`detectFacets error ${err.message}`);
110112
}
111113
const replyPost = {
112-
"reply": {"root": {uri, cid}, "parent": {uri, cid}},
114+
"reply": {
115+
"root": {"uri":rootUri, "cid":rootCid},
116+
"parent": {"uri":parentUri, "cid":parentCid}
117+
},
113118
"$type": "app.bsky.feed.post",
114119
text: rt.text,
115120
facets: rt.facets,

src/services/LoggerService.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,15 +46,19 @@ export default class LoggerService {
4646
})
4747
)
4848
});
49+
let isDebugLevelActivated = process.env["BES_DEBUG"] === "true";
50+
if (isDebugLevelActivated) {
51+
consoleTransport.level = 'debug';
52+
}
4953

5054
if (isSet(this.logtail)) { // https://logs.betterstack.com
5155
const transports = [new LogtailTransport(this.logtail), consoleTransport];
5256
this._winstonLogger = winston.createLogger({transports});
53-
console.log(` ☑ winston logtail logger`);
57+
console.log(` ☑ winston logtail logger${isDebugLevelActivated ? " with console in debug level" : ""}`);
5458
} else {
5559
const transports = [consoleTransport];
5660
this._winstonLogger = winston.createLogger({transports});
57-
console.log(` ☑ winston console logger`);
61+
console.log(` ☑ winston console logger${isDebugLevelActivated ? " with console in debug level" : ""}`);
5862
}
5963
}
6064

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// https://gist.github.com/boly38/TODO
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+
25+
class Bluesky {
26+
constructor() {
27+
this.identifier = expectEnvVariableToBeSet("BLUESKY_USERNAME");
28+
this.password = expectEnvVariableToBeSet("BLUESKY_PASSWORD");
29+
this.service = "https://api.bsky.social";
30+
}
31+
32+
async login() {
33+
const {identifier, password, service} = this;
34+
const agent = new BskyAgent({service})
35+
await agent.login({identifier, password});
36+
this.api = agent.api;
37+
}
38+
39+
async getPostThread(uri) {
40+
console.log(`getPostThread ${uri}`);
41+
const response = await this.api.app.bsky.feed.getPostThread({uri}, {})
42+
const {data} = response;
43+
const thread = data?.thread
44+
return thread
45+
}
46+
47+
}
48+
49+
/**
50+
* search thread using uri from env, or argv
51+
* example: node.exe tests/manual/bluesky_get_post_thread_by_uri.js "at://did:plc:iy7q3r5p5jagqaznuudppucr/app.bsky.feed.post/3ktxkrxw6ls2x"
52+
*/
53+
try {
54+
const bluesky = new Bluesky();
55+
console.log(`🧪🧪 login`);
56+
57+
const uri = process.env["URI"] || process.argv[2] || undefined;
58+
if (uri === undefined) {
59+
console.log(`please provide an uri`);
60+
process.exit(1);
61+
}
62+
await bluesky.login()
63+
const thread = await bluesky.getPostThread(uri);
64+
console.log(`thread:\n${JSON.stringify(thread, null, 2)}`);
65+
} catch (err) {
66+
exitFailed(err);
67+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// https://gist.github.com/boly38/275d95f0da14d8d708a14df9e0bb7c4a
2+
import process from "node:process";
3+
import {BskyAgent} from '@atproto/api';
4+
5+
const {"BLUESKY_USERNAME": identifier, "BLUESKY_PASSWORD": password} = process.env;// creds from env
6+
7+
const agent = new BskyAgent({"service": "https://api.bsky.social"})
8+
await agent.login({identifier, password});
9+
const response = await agent.api.app.bsky.actor.getPreferences();
10+
const {preferences} = response.data
11+
console.log(`${identifier}'s preferences:\n` + JSON.stringify(preferences, null, 2));
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
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+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// gist / https://gist.github.com/boly38/c01b3c685f92d111b0a4968d6ae7ca63
2+
import axios from "axios";
3+
import {BskyAgent} from '@atproto/api'
4+
5+
const identifier = process.env.BLUESKY_EMAIL;
6+
const password = process.env.BLUESKY_PASSWORD;
7+
const service = "https://api.bsky.social";
8+
const agent = new BskyAgent({service})
9+
await agent.login({identifier, password});
10+
11+
/**
12+
* Fetch image and get it's base64 value plus encoding as result
13+
* @param imageUri
14+
* @returns {Promise<{base64: string, encoding: *}>}
15+
*/
16+
const getImageUriEncodingAndBase64 = imageUri => {
17+
return axios
18+
.get(imageUri, {
19+
responseType: 'arraybuffer'
20+
})
21+
.then(response => {
22+
const encoding = response.headers["content-type"];
23+
const buffer = Buffer.from(response.data, 'binary');/* incoming data are binary */
24+
const base64 = buffer.toString('base64');
25+
return {encoding, buffer, base64}
26+
})
27+
};
28+
29+
const imageExample = "https://bs.plantnet.org/image/o/da65bab7ff4708f64db9d00ebb68b5dbfa2a4534";
30+
const createdAt = new Date().toISOString();
31+
const text = "PoC post with embed image";
32+
const alt = "this is embed image alt text";
33+
34+
console.log(`getImageUriEncodingAndBase64(${imageExample})`)
35+
getImageUriEncodingAndBase64(imageExample)
36+
.then(result => {
37+
const {encoding, buffer, base64} = result;
38+
if (encoding === undefined) {
39+
throw new Error("encoding is undefined");
40+
}
41+
if (base64?.length < 1) {
42+
throw new Error("image is empty");
43+
}
44+
if (base64?.length > 1000000) {
45+
throw new Error(`image file size too large (${base64?.length}). 1000000 bytes maximum`);
46+
}
47+
console.log(`base64?.length=${base64?.length} encoding=${encoding}`)
48+
// create blueSky blob of image
49+
agent.uploadBlob(buffer, {encoding})
50+
.then(upBlobResponse => {
51+
const {data} = upBlobResponse;
52+
const embed = {
53+
$type: 'app.bsky.embed.images',
54+
images: [ // can be an array up to 4 values
55+
{alt, "image": data.blob}
56+
]
57+
};
58+
agent.post({
59+
text, createdAt, embed
60+
})
61+
.then(() => {
62+
console.log("OK")
63+
})
64+
.catch(err => {
65+
console.error(`unable to post : ${err.message}`)
66+
});
67+
});
68+
});

0 commit comments

Comments
 (0)