From 3e40dd155ddf895b26c5841c1c52bcbc08376330 Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Mon, 6 Jan 2025 20:20:58 +0100 Subject: [PATCH] Support embed url card (#43) * Support embed url card * fixup! Support embed url card --- actions/.gitignore | 1 + actions/lib/posts.js | 197 ++++++++++++++--- actions/lib/validator.js | 42 ---- actions/login-and-validate.js | 6 +- actions/package.json | 3 +- actions/process.js | 2 +- actions/test/examples/new/post.json.example | 4 +- .../test/examples/new/quote-post.json.example | 4 +- actions/test/examples/new/reply.json.example | 4 +- actions/test/examples/new/repost.json.example | 2 +- .../test/examples/processed/post.json.example | 2 +- .../processed/quote-post.json.example | 2 +- .../examples/processed/reply.json.example | 2 +- .../examples/processed/repost.json.example | 2 +- actions/test/integration.js | 2 +- actions/yarn.lock | 204 ++++++++++++++++++ automation.md | 6 +- records/new/post.json.example | 4 +- records/new/quote-post.json.example | 4 +- records/new/reply.json.example | 4 +- records/new/repost.json.example | 2 +- records/processed/post.json.example | 2 +- records/processed/quote-post.json.example | 2 +- records/processed/reply.json.example | 2 +- records/processed/repost.json.example | 2 +- 25 files changed, 404 insertions(+), 103 deletions(-) diff --git a/actions/.gitignore b/actions/.gitignore index 88a841b..b2c38e6 100644 --- a/actions/.gitignore +++ b/actions/.gitignore @@ -15,3 +15,4 @@ node_modules .env* +test/.tmp diff --git a/actions/lib/posts.js b/actions/lib/posts.js index a9f2d6e..6ce7cbe 100644 --- a/actions/lib/posts.js +++ b/actions/lib/posts.js @@ -1,5 +1,6 @@ -import AtpAgent, { RichText } from "@atproto/api"; +import AtpAgent, { AppBskyFeedPost, BlobRef, RichText } from "@atproto/api"; import assert from 'node:assert'; +import * as cheerio from 'cheerio'; export const REPLY_IN_THREAD = Symbol('Reply in thread'); @@ -69,43 +70,179 @@ export async function getPostURLFromURI(agent, uri) { } /** + * TODO(joyeecheung): support 'imageFiles' field in JSON files. * @param {AtpAgent} agent - * @param {object} request + * @param {ArrayBuffer} imgData + * @returns {BlobRef} */ -export async function post(agent, request) { - // TODO(joyeecheung): support images and embeds. - // TODO(joyeecheung): When Bluesky supports markdown or snippets, we should ideally - // read a relative path in the request containing those contents instead of reading from - // strings in a JSON. - const rt = new RichText({ text: request.richText }); - - await rt.detectFacets(agent); // automatically detects mentions and links - - const record = { - $type: 'app.bsky.feed.post', - text: rt.text, - facets: rt.facets, - createdAt: new Date().toISOString(), +async function uploadImage(agent, imgData) { + const res = await agent.uploadBlob(imgData, { + encoding: 'image/jpeg' + }); + return res.data.blob; +} + +// https://docs.bsky.app/docs/advanced-guides/posts#website-card-embeds +async function fetchEmbedUrlCard(url) { + console.log('Fetching embed card from', url); + + // The required fields for every embed card + const card = { + uri: url, + title: '', + description: '', }; - // https://docs.bsky.app/docs/tutorials/creating-a-post#quote-posts - if (request.repostURL) { - if (!request.repostInfo) { - request.repostInfo = await getPostInfoFromUrl(agent, request.repostURL); + try { + // Fetch the HTML + const resp = await fetch(url); + if (!resp.ok) { + throw new Error(`Failed to fetch URL: ${resp.status} ${resp.statusText}`); } - record.embed = { - $type: 'app.bsky.embed.record', - record: request.repostInfo - }; - } else if (request.replyURL) { - if (!request.replyInfo) { - request.replyInfo = await getPostInfoFromUrl(agent, request.replyURL); + const html = await resp.text(); + const $ = cheerio.load(html); + + // Parse out the "og:title" and "og:description" HTML meta tags + const titleTag = $('meta[property="og:title"]').attr('content'); + if (titleTag) { + card.title = titleTag; + } + + const descriptionTag = $('meta[property="og:description"]').attr('content'); + if (descriptionTag) { + card.description = descriptionTag; + } + + // If there is an "og:image" HTML meta tag, fetch and upload that image + const imageTag = $('meta[property="og:image"]').attr('content'); + if (imageTag) { + let imgURL = imageTag; + + // Naively turn a "relative" URL (just a path) into a full URL, if needed + if (!imgURL.includes('://')) { + imgURL = new URL(imgURL, url).href; + } + card.thumb = { $TO_BE_UPLOADED: imgURL }; } - record.reply = { - root: request.rootInfo || request.replyInfo, - parent: request.replyInfo, + + return { + $type: 'app.bsky.embed.external', + external: card, }; + } catch (error) { + console.error('Error generating embed URL card:', error.message); + throw error; } +} + +/** + * @typedef ReplyRequest + * @property {string} richText + * @property {string} replyURL + * @property {{cid: string, uri: string}?} replyInfo + */ + +/** + * @typedef PostRequest + * @property {string} richText + */ + +/** + * @typedef QuotePostRequest + * @property {string} richText + * @property {string} repostURL + * @property {{cid: string, uri: string}?} repostInfo + */ +/** + * It should be possible to invoked this method on the same request at least twice - + * once to populate the facets and the embed without uploading any files if shouldUploadImage + * is false, and then again uploading files if shouldUploadImage is true. + * @param {AtpAgent} agent + * @param {ReplyRequest|PostRequest|QuotePostRequest} request + * @param {boolean} shouldUploadImage + * @returns {AppBskyFeedPost.Record} + */ +export async function populateRecord(agent, request, shouldUploadImage = false) { + console.log(`Generating record, shouldUploadImage = ${shouldUploadImage}, request = `, request); + + if (request.repostURL && !request.repostInfo) { + request.repostInfo = await getPostInfoFromUrl(agent, request.repostURL); + } + if (request.replyURL && request.replyURL !== REPLY_IN_THREAD && !request.replyInfo) { + request.replyInfo = await getPostInfoFromUrl(agent, request.replyURL); + } + + if (request.richText && !request.record) { + // TODO(joyeecheung): When Bluesky supports markdown or snippets, we should render the text + // as markdown. + const rt = new RichText({ text: request.richText }); + + await rt.detectFacets(agent); // automatically detects mentions and links + + const record = { + $type: 'app.bsky.feed.post', + text: rt.text, + facets: rt.facets, + createdAt: new Date().toISOString(), + }; + + // https://docs.bsky.app/docs/tutorials/creating-a-post#quote-posts + if (request.repostInfo) { + record.embed = { + $type: 'app.bsky.embed.record', + record: request.repostInfo + }; + } else if (request.replyInfo) { + record.reply = { + root: request.rootInfo || request.replyInfo, + parent: request.replyInfo, + }; + } + + // If there is already another embed, don't generate the card embed. + if (!record.embed) { + // Find the first URL, match until the first whitespace or punctuation. + const urlMatch = request.richText.match(/https?:\/\/[^\s\]\[\"\'\<\>]+/); + if (urlMatch !== null) { + const url = urlMatch[0]; + const card = await fetchEmbedUrlCard(url); + record.embed = card; + } + } + request.record = record; + } + + if (shouldUploadImage && request.record?.embed?.external?.thumb?.$TO_BE_UPLOADED) { + const card = request.record.embed.external; + const imgURL = card.thumb.$TO_BE_UPLOADED; + try { + console.log('Fetching image', imgURL); + const imgResp = await fetch(imgURL); + if (!imgResp.ok) { + throw new Error(`Failed to fetch image ${imgURL}: ${imgResp.status} ${imgResp.statusText}`); + } + const imgData = await imgResp.arrayBuffer(); + console.log('Uploading image', imgURL, 'size = ', imgData.byteLength); + card.thumb = await uploadImage(agent, imgData); + } catch (e) { + // If image upload fails, post the embed card without the image, at worst we see a + // link card without an image which is not a big deal. + console.log(`Failed to fetch or upload image ${imgURL}`, e); + } + } + + console.log('Generated record'); + console.dir(request.record, { depth: 3 }); + + return request; +} + +/** + * @param {AtpAgent} agent + * @param {object} request + */ +export async function post(agent, request) { + const { record } = await populateRecord(agent, request, true); return agent.post(record); } diff --git a/actions/lib/validator.js b/actions/lib/validator.js index 6f105e3..98a52ae 100644 --- a/actions/lib/validator.js +++ b/actions/lib/validator.js @@ -50,45 +50,3 @@ export function validateRequest(request) { assert.fail('Unknown action ' + request.action); } } - -/** - * @param {import('@atproto/api').AtpAgent} agent - * @param {object} request - * @param {string} fieldName - */ -async function validatePostURLInRequest(agent, request, fieldName) { - if (request.replyURL === REPLY_IN_THREAD) return request.replyInfo; - let result; - try { - result = await getPostInfoFromUrl(agent, request[fieldName]); - } finally { - if (!result) { - console.error(`Invalid "${fieldName}" field, ${request[fieldName]}`); - } - } - return result; -} - -/** - * Validate the post URLs in the request and extend them into { uri, cid } pairs - * if necessary. - * @param {import('@atproto/api').AtpAgent} agent - * @param {object} request - */ -export async function validateAndExtendRequestReferences(agent, request) { - switch(request.action) { - case 'repost': - case 'quote-post': { - const info = await validatePostURLInRequest(agent, request, 'repostURL'); - request.repostInfo = info; - break; - } - case 'reply': { - const info = await validatePostURLInRequest(agent, request, 'replyURL'); - request.replyInfo = info; - break; - } - default: - break; - } -} diff --git a/actions/login-and-validate.js b/actions/login-and-validate.js index 65cc472..f0170b0 100755 --- a/actions/login-and-validate.js +++ b/actions/login-and-validate.js @@ -4,8 +4,8 @@ import fs from 'node:fs'; import process from 'node:process'; import path from 'node:path'; import { login } from './lib/login.js'; -import { validateAccount, validateRequest, validateAndExtendRequestReferences } from './lib/validator.js'; -import { REPLY_IN_THREAD } from './lib/posts.js'; +import { validateAccount, validateRequest } from './lib/validator.js'; +import { populateRecord, REPLY_IN_THREAD } from './lib/posts.js'; // The JSON file must contains the following fields: // - "account": a string field indicating the account to use to perform the action. @@ -40,6 +40,6 @@ requests.forEach(validateRequest); const agent = await login(account); // Validate and extend the post URLs in the request into { cid, uri } records. -await Promise.all(requests.map(request => validateAndExtendRequestReferences(agent, request))); +await Promise.all(requests.map(request => populateRecord(agent, request, false))); export { agent, requests, requestFilePath, richTextFile }; diff --git a/actions/package.json b/actions/package.json index 43b59c0..b7c2080 100644 --- a/actions/package.json +++ b/actions/package.json @@ -7,6 +7,7 @@ "repository": "https://github.com/nodejs/bluesky-playground", "packageManager": "yarn@4.5.3", "dependencies": { - "@atproto/api": "^0.13.18" + "@atproto/api": "^0.13.18", + "cheerio": "^1.0.0" } } diff --git a/actions/process.js b/actions/process.js index 02902cb..7d59936 100755 --- a/actions/process.js +++ b/actions/process.js @@ -28,7 +28,7 @@ for (const request of requests) { }; case 'repost': { console.log('Reposting...', request.repostURL); - assert(request.repostInfo); // Extended by validateAndExtendRequestReferences. + assert(request.repostInfo); // Extended by populateRecord. result = await agent.repost(request.repostInfo.uri, request.repostInfo.cid); break; } diff --git a/actions/test/examples/new/post.json.example b/actions/test/examples/new/post.json.example index dc4d60e..898036a 100644 --- a/actions/test/examples/new/post.json.example +++ b/actions/test/examples/new/post.json.example @@ -1,5 +1,5 @@ { "action": "post", - "account": "PIXEL", - "richText": "Hello from automation!" + "account": "PRIMARY", + "richText": "Hello from automation https://github.com/nodejs/bluesky" } \ No newline at end of file diff --git a/actions/test/examples/new/quote-post.json.example b/actions/test/examples/new/quote-post.json.example index f668a7b..51d9501 100644 --- a/actions/test/examples/new/quote-post.json.example +++ b/actions/test/examples/new/quote-post.json.example @@ -1,6 +1,6 @@ { "action": "quote-post", - "account": "PIXEL", - "richText": "Quote post from automation", + "account": "PRIMARY", + "richText": "Quote post from automation https://github.com/nodejs/bluesky", "repostURL": "https://bsky.app/profile/pixel-voyager.bsky.social/post/3lbg7zd32an2s" } \ No newline at end of file diff --git a/actions/test/examples/new/reply.json.example b/actions/test/examples/new/reply.json.example index 01af130..8800d27 100644 --- a/actions/test/examples/new/reply.json.example +++ b/actions/test/examples/new/reply.json.example @@ -1,6 +1,6 @@ { "action": "reply", - "account": "PIXEL", - "richText": "Reply from automation", + "account": "PRIMARY", + "richText": "Reply from automation https://github.com/nodejs/bluesky", "replyURL": "https://bsky.app/profile/pixel-voyager.bsky.social/post/3lbg7zd32an2s" } \ No newline at end of file diff --git a/actions/test/examples/new/repost.json.example b/actions/test/examples/new/repost.json.example index 10a823a..92fbab6 100644 --- a/actions/test/examples/new/repost.json.example +++ b/actions/test/examples/new/repost.json.example @@ -1,5 +1,5 @@ { "action": "repost", - "account": "PIXEL", + "account": "PRIMARY", "repostURL": "https://bsky.app/profile/pixel-voyager.bsky.social/post/3lbg7zd32an2s" } \ No newline at end of file diff --git a/actions/test/examples/processed/post.json.example b/actions/test/examples/processed/post.json.example index fa5c371..6301396 100644 --- a/actions/test/examples/processed/post.json.example +++ b/actions/test/examples/processed/post.json.example @@ -1,6 +1,6 @@ { "action": "post", - "account": "PIXEL", + "account": "PRIMARY", "richText": "Hello from automation!", "result": { "uri": "at://did:plc:tw2ov5bciclbz7b45sh4xlua/app.bsky.feed.post/3lbnijyd24t2i", diff --git a/actions/test/examples/processed/quote-post.json.example b/actions/test/examples/processed/quote-post.json.example index 82bc963..106f4b7 100644 --- a/actions/test/examples/processed/quote-post.json.example +++ b/actions/test/examples/processed/quote-post.json.example @@ -1,6 +1,6 @@ { "action": "quote-post", - "account": "PIXEL", + "account": "PRIMARY", "richText": "Quote post from automation", "repostURL": "https://bsky.app/profile/pixel-voyager.bsky.social/post/3lbnijyd24t2i", "repostInfo": { diff --git a/actions/test/examples/processed/reply.json.example b/actions/test/examples/processed/reply.json.example index 83a6c0e..d9ee089 100644 --- a/actions/test/examples/processed/reply.json.example +++ b/actions/test/examples/processed/reply.json.example @@ -1,6 +1,6 @@ { "action": "reply", - "account": "PIXEL", + "account": "PRIMARY", "richText": "Reply from automation", "replyURL": "https://bsky.app/profile/pixel-voyager.bsky.social/post/3lbnik24wgk2i", "replyInfo": { diff --git a/actions/test/examples/processed/repost.json.example b/actions/test/examples/processed/repost.json.example index ef4e2ed..5beeea1 100644 --- a/actions/test/examples/processed/repost.json.example +++ b/actions/test/examples/processed/repost.json.example @@ -1,6 +1,6 @@ { "action": "repost", - "account": "PIXEL", + "account": "PRIMARY", "repostURL": "https://bsky.app/profile/pixel-voyager.bsky.social/post/3lbnik3jixl2h", "repostInfo": { "uri": "at://did:plc:tw2ov5bciclbz7b45sh4xlua/app.bsky.feed.post/3lbnik3jixl2h", diff --git a/actions/test/integration.js b/actions/test/integration.js index bdf5b18..7c1f454 100644 --- a/actions/test/integration.js +++ b/actions/test/integration.js @@ -37,7 +37,7 @@ async function getURLFromLastResult(lastStdout) { lastStdout = lastStdout.toString(); const postMatch = lastStdout.match(/Processed and moved file: (.*) -> (.*)/); assert(postMatch); - const processed = loadJSON(postMatch[2]); + const processed = loadJSON(postMatch[2])[0]; assert(processed.result.uri); const uriParts = processed.result.uri.split('/'); const postId = uriParts[uriParts.length - 1]; diff --git a/actions/yarn.lock b/actions/yarn.lock index 796f26d..3b637f9 100644 --- a/actions/yarn.lock +++ b/actions/yarn.lock @@ -75,9 +75,125 @@ __metadata: resolution: "bluesky-playground@workspace:." dependencies: "@atproto/api": "npm:^0.13.18" + cheerio: "npm:^1.0.0" languageName: unknown linkType: soft +"boolbase@npm:^1.0.0": + version: 1.0.0 + resolution: "boolbase@npm:1.0.0" + checksum: 10c0/e4b53deb4f2b85c52be0e21a273f2045c7b6a6ea002b0e139c744cb6f95e9ec044439a52883b0d74dedd1ff3da55ed140cfdddfed7fb0cccbed373de5dce1bcf + languageName: node + linkType: hard + +"cheerio-select@npm:^2.1.0": + version: 2.1.0 + resolution: "cheerio-select@npm:2.1.0" + dependencies: + boolbase: "npm:^1.0.0" + css-select: "npm:^5.1.0" + css-what: "npm:^6.1.0" + domelementtype: "npm:^2.3.0" + domhandler: "npm:^5.0.3" + domutils: "npm:^3.0.1" + checksum: 10c0/2242097e593919dba4aacb97d7b8275def8b9ec70b00aa1f43335456870cfc9e284eae2080bdc832ed232dabb9eefcf56c722d152da4a154813fb8814a55d282 + languageName: node + linkType: hard + +"cheerio@npm:^1.0.0": + version: 1.0.0 + resolution: "cheerio@npm:1.0.0" + dependencies: + cheerio-select: "npm:^2.1.0" + dom-serializer: "npm:^2.0.0" + domhandler: "npm:^5.0.3" + domutils: "npm:^3.1.0" + encoding-sniffer: "npm:^0.2.0" + htmlparser2: "npm:^9.1.0" + parse5: "npm:^7.1.2" + parse5-htmlparser2-tree-adapter: "npm:^7.0.0" + parse5-parser-stream: "npm:^7.1.2" + undici: "npm:^6.19.5" + whatwg-mimetype: "npm:^4.0.0" + checksum: 10c0/d0e16925d9c36c879edfaef1c0244c866375a4c7b8d6ccd7ae0ad42da7d26263ea1a3c17b9a1aa5965918deeff2d40ac2e7223824f8e6eca972df3b81316a09f + languageName: node + linkType: hard + +"css-select@npm:^5.1.0": + version: 5.1.0 + resolution: "css-select@npm:5.1.0" + dependencies: + boolbase: "npm:^1.0.0" + css-what: "npm:^6.1.0" + domhandler: "npm:^5.0.2" + domutils: "npm:^3.0.1" + nth-check: "npm:^2.0.1" + checksum: 10c0/551c60dba5b54054741032c1793b5734f6ba45e23ae9e82761a3c0ed1acbb8cfedfa443aaba3a3c1a54cac12b456d2012a09d2cd5f0e82e430454c1b9d84d500 + languageName: node + linkType: hard + +"css-what@npm:^6.1.0": + version: 6.1.0 + resolution: "css-what@npm:6.1.0" + checksum: 10c0/a09f5a6b14ba8dcf57ae9a59474722e80f20406c53a61e9aedb0eedc693b135113ffe2983f4efc4b5065ae639442e9ae88df24941ef159c218b231011d733746 + languageName: node + linkType: hard + +"dom-serializer@npm:^2.0.0": + version: 2.0.0 + resolution: "dom-serializer@npm:2.0.0" + dependencies: + domelementtype: "npm:^2.3.0" + domhandler: "npm:^5.0.2" + entities: "npm:^4.2.0" + checksum: 10c0/d5ae2b7110ca3746b3643d3ef60ef823f5f078667baf530cec096433f1627ec4b6fa8c072f09d079d7cda915fd2c7bc1b7b935681e9b09e591e1e15f4040b8e2 + languageName: node + linkType: hard + +"domelementtype@npm:^2.3.0": + version: 2.3.0 + resolution: "domelementtype@npm:2.3.0" + checksum: 10c0/686f5a9ef0fff078c1412c05db73a0dce096190036f33e400a07e2a4518e9f56b1e324f5c576a0a747ef0e75b5d985c040b0d51945ce780c0dd3c625a18cd8c9 + languageName: node + linkType: hard + +"domhandler@npm:^5.0.2, domhandler@npm:^5.0.3": + version: 5.0.3 + resolution: "domhandler@npm:5.0.3" + dependencies: + domelementtype: "npm:^2.3.0" + checksum: 10c0/bba1e5932b3e196ad6862286d76adc89a0dbf0c773e5ced1eb01f9af930c50093a084eff14b8de5ea60b895c56a04d5de8bbc4930c5543d029091916770b2d2a + languageName: node + linkType: hard + +"domutils@npm:^3.0.1, domutils@npm:^3.1.0": + version: 3.1.0 + resolution: "domutils@npm:3.1.0" + dependencies: + dom-serializer: "npm:^2.0.0" + domelementtype: "npm:^2.3.0" + domhandler: "npm:^5.0.3" + checksum: 10c0/342d64cf4d07b8a0573fb51e0a6312a88fb520c7fefd751870bf72fa5fc0f2e0cb9a3958a573610b1d608c6e2a69b8e9b4b40f0bfb8f87a71bce4f180cca1887 + languageName: node + linkType: hard + +"encoding-sniffer@npm:^0.2.0": + version: 0.2.0 + resolution: "encoding-sniffer@npm:0.2.0" + dependencies: + iconv-lite: "npm:^0.6.3" + whatwg-encoding: "npm:^3.1.1" + checksum: 10c0/b312e0d67f339bec44e021e5210ee8ee90d7b8f9975eb2c79a36fd467eb07709e88dcf62ee20f62ee0d74a13874307d99557852a2de9b448f1e3fb991fc68257 + languageName: node + linkType: hard + +"entities@npm:^4.2.0, entities@npm:^4.5.0": + version: 4.5.0 + resolution: "entities@npm:4.5.0" + checksum: 10c0/5b039739f7621f5d1ad996715e53d964035f75ad3b9a4d38c6b3804bb226e282ffeae2443624d8fdd9c47d8e926ae9ac009c54671243f0c3294c26af7cc85250 + languageName: node + linkType: hard + "graphemer@npm:^1.4.0": version: 1.4.0 resolution: "graphemer@npm:1.4.0" @@ -85,6 +201,27 @@ __metadata: languageName: node linkType: hard +"htmlparser2@npm:^9.1.0": + version: 9.1.0 + resolution: "htmlparser2@npm:9.1.0" + dependencies: + domelementtype: "npm:^2.3.0" + domhandler: "npm:^5.0.3" + domutils: "npm:^3.1.0" + entities: "npm:^4.5.0" + checksum: 10c0/394f6323efc265bbc791d8c0d96bfe95984e0407565248521ab92e2dc7668e5ceeca7bc6ed18d408b9ee3b25032c5743368a4280d280332d782821d5d467ad8f + languageName: node + linkType: hard + +"iconv-lite@npm:0.6.3, iconv-lite@npm:^0.6.3": + version: 0.6.3 + resolution: "iconv-lite@npm:0.6.3" + dependencies: + safer-buffer: "npm:>= 2.1.2 < 3.0.0" + checksum: 10c0/98102bc66b33fcf5ac044099d1257ba0b7ad5e3ccd3221f34dd508ab4070edff183276221684e1e0555b145fce0850c9f7d2b60a9fcac50fbb4ea0d6e845a3b1 + languageName: node + linkType: hard + "iso-datestring-validator@npm:^2.2.2": version: 2.2.2 resolution: "iso-datestring-validator@npm:2.2.2" @@ -99,6 +236,50 @@ __metadata: languageName: node linkType: hard +"nth-check@npm:^2.0.1": + version: 2.1.1 + resolution: "nth-check@npm:2.1.1" + dependencies: + boolbase: "npm:^1.0.0" + checksum: 10c0/5fee7ff309727763689cfad844d979aedd2204a817fbaaf0e1603794a7c20db28548d7b024692f953557df6ce4a0ee4ae46cd8ebd9b36cfb300b9226b567c479 + languageName: node + linkType: hard + +"parse5-htmlparser2-tree-adapter@npm:^7.0.0": + version: 7.1.0 + resolution: "parse5-htmlparser2-tree-adapter@npm:7.1.0" + dependencies: + domhandler: "npm:^5.0.3" + parse5: "npm:^7.0.0" + checksum: 10c0/e5a4e0b834c84c9e244b5749f8d007f4baaeafac7a1da2c54be3421ffd9ef8fdec4f198bf55cda22e88e6ba95e9943f6ed5aa3ae5900b39972ebf5dc8c3f4722 + languageName: node + linkType: hard + +"parse5-parser-stream@npm:^7.1.2": + version: 7.1.2 + resolution: "parse5-parser-stream@npm:7.1.2" + dependencies: + parse5: "npm:^7.0.0" + checksum: 10c0/e236c61000d38ecad369e725a48506b051cebad8abb00e6d4e8bff7aa85c183820fcb45db1559cc90955bdbbdbd665ea94c41259594e74566fff411478dc7fcb + languageName: node + linkType: hard + +"parse5@npm:^7.0.0, parse5@npm:^7.1.2": + version: 7.2.1 + resolution: "parse5@npm:7.2.1" + dependencies: + entities: "npm:^4.5.0" + checksum: 10c0/829d37a0c709215a887e410a7118d754f8e1afd7edb529db95bc7bbf8045fb0266a7b67801331d8e8d9d073ea75793624ec27ce9ff3b96862c3b9008f4d68e80 + languageName: node + linkType: hard + +"safer-buffer@npm:>= 2.1.2 < 3.0.0": + version: 2.1.2 + resolution: "safer-buffer@npm:2.1.2" + checksum: 10c0/7e3c8b2e88a1841c9671094bbaeebd94448111dd90a81a1f606f3f67708a6ec57763b3b47f06da09fc6054193e0e6709e77325415dc8422b04497a8070fa02d4 + languageName: node + linkType: hard + "tlds@npm:^1.234.0": version: 1.255.0 resolution: "tlds@npm:1.255.0" @@ -117,6 +298,29 @@ __metadata: languageName: node linkType: hard +"undici@npm:^6.19.5": + version: 6.21.0 + resolution: "undici@npm:6.21.0" + checksum: 10c0/afa9bde6dcf8e0f5cf1ff2fa977ba73dd5510299ddfca0e1f37ff326554172ae31cb3d4a40b5a729601be1f21b96a2684f974d74dab53f9b6930fd47d1949246 + languageName: node + linkType: hard + +"whatwg-encoding@npm:^3.1.1": + version: 3.1.1 + resolution: "whatwg-encoding@npm:3.1.1" + dependencies: + iconv-lite: "npm:0.6.3" + checksum: 10c0/273b5f441c2f7fda3368a496c3009edbaa5e43b71b09728f90425e7f487e5cef9eb2b846a31bd760dd8077739c26faf6b5ca43a5f24033172b003b72cf61a93e + languageName: node + linkType: hard + +"whatwg-mimetype@npm:^4.0.0": + version: 4.0.0 + resolution: "whatwg-mimetype@npm:4.0.0" + checksum: 10c0/a773cdc8126b514d790bdae7052e8bf242970cebd84af62fb2f35a33411e78e981f6c0ab9ed1fe6ec5071b09d5340ac9178e05b52d35a9c4bcf558ba1b1551df + languageName: node + linkType: hard + "zod@npm:^3.23.8": version: 3.23.8 resolution: "zod@npm:3.23.8" diff --git a/automation.md b/automation.md index ccf9341..4867c52 100644 --- a/automation.md +++ b/automation.md @@ -98,11 +98,11 @@ cd ./actions node --env-file=./.env.bluesky test/integration.js ``` -All files starting with .env are ignored in this repository, you could save the credentials there, like in the `.env.bluesky` used above, if the account is named `PIXEL`, it should have: +All files starting with .env are ignored in this repository, you could save the credentials there, like in the `.env.bluesky` used above, if the account is named `PRIMARY`, it should have: ``` -BLUESKY_IDENTIFIER_PIXEL=... # The Bluesky handle -BLUESKY_APP_PASSWORD_PIXEL=... # The app password +BLUESKY_IDENTIFIER_PRIMARY=... # The Bluesky handle +BLUESKY_APP_PASSWORD_PRIMARY=... # The app password ``` [thematic break]: https://spec.commonmark.org/0.31.2/#thematic-breaks diff --git a/records/new/post.json.example b/records/new/post.json.example index dc4d60e..898036a 100644 --- a/records/new/post.json.example +++ b/records/new/post.json.example @@ -1,5 +1,5 @@ { "action": "post", - "account": "PIXEL", - "richText": "Hello from automation!" + "account": "PRIMARY", + "richText": "Hello from automation https://github.com/nodejs/bluesky" } \ No newline at end of file diff --git a/records/new/quote-post.json.example b/records/new/quote-post.json.example index f668a7b..51d9501 100644 --- a/records/new/quote-post.json.example +++ b/records/new/quote-post.json.example @@ -1,6 +1,6 @@ { "action": "quote-post", - "account": "PIXEL", - "richText": "Quote post from automation", + "account": "PRIMARY", + "richText": "Quote post from automation https://github.com/nodejs/bluesky", "repostURL": "https://bsky.app/profile/pixel-voyager.bsky.social/post/3lbg7zd32an2s" } \ No newline at end of file diff --git a/records/new/reply.json.example b/records/new/reply.json.example index 01af130..8800d27 100644 --- a/records/new/reply.json.example +++ b/records/new/reply.json.example @@ -1,6 +1,6 @@ { "action": "reply", - "account": "PIXEL", - "richText": "Reply from automation", + "account": "PRIMARY", + "richText": "Reply from automation https://github.com/nodejs/bluesky", "replyURL": "https://bsky.app/profile/pixel-voyager.bsky.social/post/3lbg7zd32an2s" } \ No newline at end of file diff --git a/records/new/repost.json.example b/records/new/repost.json.example index 10a823a..92fbab6 100644 --- a/records/new/repost.json.example +++ b/records/new/repost.json.example @@ -1,5 +1,5 @@ { "action": "repost", - "account": "PIXEL", + "account": "PRIMARY", "repostURL": "https://bsky.app/profile/pixel-voyager.bsky.social/post/3lbg7zd32an2s" } \ No newline at end of file diff --git a/records/processed/post.json.example b/records/processed/post.json.example index fa5c371..6301396 100644 --- a/records/processed/post.json.example +++ b/records/processed/post.json.example @@ -1,6 +1,6 @@ { "action": "post", - "account": "PIXEL", + "account": "PRIMARY", "richText": "Hello from automation!", "result": { "uri": "at://did:plc:tw2ov5bciclbz7b45sh4xlua/app.bsky.feed.post/3lbnijyd24t2i", diff --git a/records/processed/quote-post.json.example b/records/processed/quote-post.json.example index 82bc963..106f4b7 100644 --- a/records/processed/quote-post.json.example +++ b/records/processed/quote-post.json.example @@ -1,6 +1,6 @@ { "action": "quote-post", - "account": "PIXEL", + "account": "PRIMARY", "richText": "Quote post from automation", "repostURL": "https://bsky.app/profile/pixel-voyager.bsky.social/post/3lbnijyd24t2i", "repostInfo": { diff --git a/records/processed/reply.json.example b/records/processed/reply.json.example index 83a6c0e..d9ee089 100644 --- a/records/processed/reply.json.example +++ b/records/processed/reply.json.example @@ -1,6 +1,6 @@ { "action": "reply", - "account": "PIXEL", + "account": "PRIMARY", "richText": "Reply from automation", "replyURL": "https://bsky.app/profile/pixel-voyager.bsky.social/post/3lbnik24wgk2i", "replyInfo": { diff --git a/records/processed/repost.json.example b/records/processed/repost.json.example index ef4e2ed..5beeea1 100644 --- a/records/processed/repost.json.example +++ b/records/processed/repost.json.example @@ -1,6 +1,6 @@ { "action": "repost", - "account": "PIXEL", + "account": "PRIMARY", "repostURL": "https://bsky.app/profile/pixel-voyager.bsky.social/post/3lbnik3jixl2h", "repostInfo": { "uri": "at://did:plc:tw2ov5bciclbz7b45sh4xlua/app.bsky.feed.post/3lbnik3jixl2h",