brainz-social-old-2/create_post.js

203 lines
No EOL
8.5 KiB
JavaScript

#!/usr/bin/env node
// TODO: .env compatibility.
const { confirm, editor, input } = require("@inquirer/prompts");
const commonmark = require("commonmark");
const chrono = require('chrono-node');
const path = require('path');
const fs = require('node:fs/promises');
(async () => {
// Queries with enquirer
// User to post as, autofill .env
const user = await input({ message: "User to post as ", default: "rallias" });
// Domain to post as, autofill .env
const domain = await input({ message: "Domain to post as ", default: "dev.brainz.social" });
// Post to reply to, autofill empty
// Subject, autofill empty
const subject = await input({ message: "Subject ", default: "" });
// Post content, autofill empty
const postContent = (await editor({ message: "Post content " })).replace(/\n$/, "");
// Poll yes/no
let pollOptions = [], endTime = Date.now()+86400000, multipleChoice = false;
const isPoll = await confirm({ message: "Is this a poll post? ", default: false });
if (isPoll) {
let pollDone = false;
while (!pollDone) {
const pollOption = await input({ message: "Enter poll option (empty to finish) ", default: "" });
if (pollOption === "") {
pollDone = true;
} else {
pollOptions.push(pollOption);
}
}
multipleChoice = await confirm({ message: "Allow multiple selection? ", default: false });
let hasEndTime = false;
while (!hasEndTime) {
endTime = chrono.parseDate(await(input({message: "How long should poll run (time in future/length of time)? "})), new Date(Date.now()), {forwardDate: true});
if (endTime > Date.now()) {
hasEndTime = true;
} else {
console.log("Unable to parse date. Try something like \"In one day\" or \"25th of December at Noon\". See \"chrono-node\" NPM module for further details.");
}
}
}
let parsedPostContent = "";
let tags = [];
let chars = Array.from(postContent);
for ( let i = 0; i < chars.length; i++ ) {
if ( chars[i] !== "@" )
parsedPostContent = `${parsedPostContent}${chars[i]}`;
if ( chars[i] === "@" ) {
let potentialMentionUsername = "";
let fulfilledPotentialMention = false;
let fulfilledReallyLikelyPotentialMention = false;
let potentialMentionDomain = "";
let lameDuckSpacing = "";
i++;
while ( i < chars.length && fulfilledPotentialMention === false ) {
if ( chars[i] !== "@" ) {
potentialMentionUsername = `${potentialMentionUsername}${chars[i]}`;
}
if ( chars[i] === "@" ) {
fulfilledPotentialMention = true;
i++
while ( i < chars.length && fulfilledReallyLikelyPotentialMention === false ) {
potentialMentionDomain = `${potentialMentionDomain}${chars[i]}`;
if ( chars[i].match(/\s/) ) {
fulfilledReallyLikelyPotentialMention = true;
lameDuckSpacing = chars[i];
} else {
i++;
}
}
}
if ( chars[i].match(/\s/) ) {
fulfilledPotentialMention = true;
}
i++;
}
if ( fulfilledPotentialMention ) {
if ( fulfilledReallyLikelyPotentialMention || i === chars.length ) {
const allowTag = await confirm({message: `Tag @${potentialMentionUsername}@${potentialMentionDomain}`, default: true});
if ( allowTag ) {
// TODO: Parse this at post creation time, rather than rely on dynamic content.
// In order to accomplish this, we'll need to be able to create a stub user and signed HTTP requests.
parsedPostContent = `${parsedPostContent}[@${potentialMentionUsername}](https://${domain}/resolveUser?user=${potentialMentionUsername}&domain=${potentialMentionDomain})`;
// TODO: Finger the user at post creation time, rather than rely on dynamic content.
tags.push(`https://${domain}/resolveWebfinger?user=${potentialMentionUsername}&domain=${potentialMentionDomain}`);
} else {
parsedPostContent = `${parsedPostContent}@${potentialMentionUsername}@${potentialMentionDomain}`;
}
if ( fulfilledReallyLikelyPotentialMention ) {
parsedPostContent = `${parsedPostContent}${lameDuckSpacing}`;
}
} else {
parsedPostContent = `${parsedPostContent}@${potentialMentionUsername}`;
}
} else {
parsedPostContent = `${parsedPostContent}@${potentialMentionUsername}`;
}
}
}
// TODO: Support attachments.
let postContentHtml = (new commonmark.HtmlRenderer()).render(new commonmark.Parser().parse(parsedPostContent)).replace(/\n$/, "");
let timestamp = Date.now();
let postId = `${timestamp}`;
let postObj = {
"@context": [
"https://www.w3.org/ns/activitystreams",
{
"ostatus": "http://ostatus.org#",
"sensitive": "as:sensitive",
"toot": "http://joinmastodon.org/ns#",
"votersCount": "toot:votersCount",
"litepub": "http://litepub.social/ns#",
"directMessage": "litepub:directMessage"
}
],
"id": `https://${domain}/users/${user}/statuses/${postId}`,
"type": "Note",
"inReplyTo": null,
"published": (new Date(Date.now())).toISOString(),
"url": `https://${domain}/users/${user}/statuses/${postId}`,
"attributedTo": `https://${domain}/users/${user}`,
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [
`https://${domain}/users/${user}/followers`,
...tags
],
"sensitive": false,
"content": postContentHtml,
"contentMap": {
// TODO: Multiple Language Support
"en": postContentHtml
},
"attachment": [],
"tag": [],
"replies": {
"id": `https://${domain}/users/${user}/statuses/${postId}/replies`,
"type": "Collection",
"first": {
"type": "CollectionPage",
"next": `https://${domain}/users/${user}/statuses/${postId}/replies?only_other_accounts=true\u0026page=true`,
"partOf": `https://${domain}/users/${user}/statuses/${postId}/replies`,
"items": []
}
}
};
if (subject) {
postObj.summary = subject;
postObj.sensitive = true;
}
if (isPoll) {
postObj.type = "Question";
const options = pollOptions.map((value) => {
return {
type: "Note",
name: value,
replies: {
type: "Collection",
totalItems: 0
}
}
});
if ( multipleChoice ) {
postObj.anyOf = options;
} else {
postObj.oneOf = options;
}
postObj.endTime = new Date(endTime).toISOString();
postObj.votersCount = 0;
};
const writeDir = path.join(__dirname, "json", "users", user, "statuses");
const writePath = path.join(writeDir, `${postId}.json`);
await fs.mkdir(writeDir, {recursive: true});
await fs.writeFile(writePath, JSON.stringify(postObj));
console.log(`Post created successfully! ${writePath}`);
const postActivity = {
object: postObj,
"@context": postObj["@context"],
to: postObj.to,
cc: postObj.cc,
type: "Create",
actor: postObj.attributedTo,
id: `${postObj.id}/activity`,
published: postObj.published
};
delete postActivity.object["@context"];
const activityWriteDir = path.join(writeDir, postId);
const activityWritePath = path.join(activityWriteDir, "activity.json");
await fs.mkdir(activityWriteDir, {recursive: true});
await fs.writeFile(activityWritePath, JSON.stringify(postActivity));
console.log(`Activity created successfully! ${activityWritePath}`);
})();