Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 47 additions & 5 deletions actions/lib/posts.js
Original file line number Diff line number Diff line change
Expand Up @@ -193,12 +193,8 @@ export async function populateRecord(agent, request, shouldUploadImage = false)
$type: 'app.bsky.embed.record',
record: request.repostInfo
};
} else if (request.replyInfo) {
record.reply = {
root: request.rootInfo || request.replyInfo,
parent: request.replyInfo,
};
}
updateReplyRecord(request, record);

// If there is already another embed, don't generate the card embed.
if (!record.embed) {
Expand Down Expand Up @@ -238,6 +234,52 @@ export async function populateRecord(agent, request, shouldUploadImage = false)
return request;
}

function updateReplyRecord(request, record) {
if (request.replyInfo) {
record.reply = {
root: request.rootInfo || request.replyInfo,
parent: request.replyInfo,
};
}
return request;
}

export function maybeUpdateReplyInThread(request, previousPostInfo, rootPostInfo) {
if (request.replyURL === REPLY_IN_THREAD) {
request.replyInfo = previousPostInfo;
request.rootInfo = rootPostInfo;
updateReplyRecord(request, request.record);
console.log('Updated reply in thread', request);
}
}

// If the request contains rich text with thematic breaks, it will split the request into multiple
// requests.
export function maybeSplitRequests(request) {
if (request.action === 'repost') { // reposts are always single posts.
return [request];
}
if (!request.richText) {
return [request];
}
const thread = request.richText.split(/^\s*(?:[-*_]\s*){2,}\s*$/m)
.map((text) => text.trim())
.filter((text) => text.length > 0);

if (thread.length === 1) {
return [request];
}

return thread.map((richText, i) => ({
...request,
...(i === 0 ? undefined : {
action: 'reply', // Posts other than the first one are replies.
replyURL: REPLY_IN_THREAD,
}),
richText,
}));
}

/**
* @param {AtpAgent} agent
* @param {object} request
Expand Down
14 changes: 3 additions & 11 deletions actions/login-and-validate.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import process from 'node:process';
import path from 'node:path';
import { login } from './lib/login.js';
import { validateAccount, validateRequest } from './lib/validator.js';
import { populateRecord, REPLY_IN_THREAD } from './lib/posts.js';
import { populateRecord, maybeSplitRequests } 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.
Expand All @@ -21,16 +21,8 @@ if (Object.hasOwn(request, 'richTextFile')) {
richTextFile = path.resolve(path.dirname(requestFilePath), request.richTextFile);
request.richText = fs.readFileSync(richTextFile, 'utf-8');
}
const threadElements = request.action !== 'repost' && request.richText?.split(/\n+ {0,3}([-_*])[ \t]*(?:\1[ \t]*){2,}\n+/g);
const requests = threadElements?.length ?
threadElements.map((richText, i) => ({
...request,
...(i === 0 ? undefined : {
action: 'reply',
replyURL: REPLY_IN_THREAD,
}),
richText,
})) : [request];

const requests = maybeSplitRequests(request);

// Validate the account field.
const account = validateAccount(request, process.env);
Expand Down
7 changes: 2 additions & 5 deletions actions/process.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import fs from 'node:fs';
import assert from 'node:assert';
import process from 'node:process';
import path from 'node:path';
import { post, REPLY_IN_THREAD } from './lib/posts.js';
import { post, maybeUpdateReplyInThread } from './lib/posts.js';

// This script takes a path to a JSON with the pattern $base_path/new/$any_name.json,
// where $any_name can be anything, and then performs the action specified in it.
Expand Down Expand Up @@ -38,10 +38,7 @@ for (const request of requests) {
break;
}
case 'reply': {
if (request.replyURL === REPLY_IN_THREAD) {
request.replyInfo = previousPostInfo;
request.rootInfo = rootPostInfo;
}
maybeUpdateReplyInThread(request, previousPostInfo, rootPostInfo);
console.log(`Replying...`, request.replyURL, request.richText);
result = await post(agent, request);
break;
Expand Down
5 changes: 5 additions & 0 deletions actions/test/examples/new/thread.json.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"action": "post",
"account": "PRIMARY",
"richTextFile": "./thread.txt"
}
9 changes: 9 additions & 0 deletions actions/test/examples/new/thread.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
This is thread post one.

---

This is thread post two.

---

This is thread post three.
13 changes: 13 additions & 0 deletions actions/test/integration.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,4 +98,17 @@ console.log(repostRequest);
checkProcess(process.execPath, [ processPath, repostPath ]);
// repost alone does not generate new URLs.

// Test threading.
const threadPath = path.join(newDir, 'thread.json');
const threadExample = path.join(examplesDir, 'thread.json.example');
const threadRequest = loadJSON(threadExample);
fs.cpSync(threadExample, threadPath);
fs.cpSync(path.join(examplesDir, 'thread.txt'), path.join(newDir, 'thread.txt'));

console.log('--- Test threading ---');
console.log(threadRequest);
const threadChild = checkProcess(process.execPath, [ processPath, threadPath ]);
const threadURL = await getURLFromLastResult(threadChild.stdout);
console.log(`thread URL`, threadURL);

fs.rmSync(tmpdir, { recursive: true, force: true });