Skip to content

Commit

Permalink
feat(syndicator-atproto): at protocol syndicator
Browse files Browse the repository at this point in the history
  • Loading branch information
paulrobertlloyd committed May 12, 2024
1 parent 2ea02a1 commit 613f633
Show file tree
Hide file tree
Showing 9 changed files with 495 additions and 0 deletions.
7 changes: 7 additions & 0 deletions indiekit.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const config = {
"@indiekit/post-type-video",
"@indiekit/preset-eleventy",
"@indiekit/store-github",
"@indiekit/syndicator-atproto",
"@indiekit/syndicator-internet-archive",
"@indiekit/syndicator-mastodon",
],
Expand Down Expand Up @@ -78,6 +79,12 @@ const config = {
endpoint: process.env.S3_ENDPOINT,
bucket: process.env.S3_BUCKET,
},
"@indiekit/syndicator-atproto": {
checked: true,
url: process.env.ATPROTO_URL,
user: process.env.ATPROTO_USER,
password: process.env.ATPROTO_PASSWORD,
},
"@indiekit/syndicator-internet-archive": {
checked: false,
accessKey: process.env.INTERNET_ARCHIVE_ACCESS_KEY,
Expand Down
123 changes: 123 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

33 changes: 33 additions & 0 deletions packages/syndicator-atproto/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# @indiekit/syndicator-atproto

[AT Protocol](https://atproto.com) syndicator for Indiekit.

## Installation

`npm i @indiekit/syndicator-atproto`

## Usage

Add `@indiekit/syndicator-atproto` to your list of plug-ins, specifying options as required:

```json
{
"plugins": ["@indiekit/syndicator-atproto"],
"@indiekit/syndicator-atproto": {
"url": "https://bsky.social",
"user": "username.bsky.social",
"password": "password",
"checked": true
}
}
```

## Options

| Option | Type | Description |
| :----------------- | :-------- | :------------------------------------------------------------------------------------------------------------ |
| `password` | `string` | Your AT protocol password. _Required_, defaults to `process.env.ATPROTO_PASSWORD`. |
| `url` | `string` | Your AT protocol service, i.e. `https://bsky.social`. _Required_. |
| `user` | `string` | Your AT protocol identifier (without the `@`). _Required_. |
| `checked` | `boolean` | Tell a Micropub client whether this syndicator should be enabled by default. _Optional_, defaults to `false`. |
| `includePermalink` | `boolean` | Always include a link to the original post. _Optional_, defaults to `false`. |
4 changes: 4 additions & 0 deletions packages/syndicator-atproto/assets/icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
97 changes: 97 additions & 0 deletions packages/syndicator-atproto/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import process from "node:process";
import { IndiekitError } from "@indiekit/error";
import { atproto } from "./lib/atproto.js";

const defaults = {
checked: false,
includePermalink: false,
password: process.env.ATPROTO_PASSWORD,
user: "",
};

export default class AtProtoSyndicator {
/**
* @param {object} [options] - Plug-in options
* @param {boolean} [options.includePermalink] - Include permalink in status
* @param {string} [options.password] - Password
* @param {string} [options.url] - Server URL
* @param {string} [options.user] - Username
* @param {boolean} [options.checked] - Check syndicator in UI
*/
constructor(options = {}) {
this.name = "AT Protocol syndicator";
this.options = { ...defaults, ...options };
}

get #url() {
return this.options?.url ? new URL(this.options.url) : false;
}

get #user() {
return this.options?.user
? `@${this.options.user.replace("@", "")}`
: false;
}

get environment() {
return ["ATPROTO_PASSWORD"];
}

get info() {
const service = {
name: "AT Protocol",
photo: "/assets/@indiekit-atproto/icon.svg",
};
const user = this.#user;
const url = this.#url;

if (!url) {
return {
error: "Service URL required",
service,
};
}

if (!user) {
return {
error: "User identifier required",
service,
};
}

const uid = `${url.protocol}//${this.options.user.replace("@", "")}`;
service.url = url.href;

return {
checked: this.options.checked,
name: user,
uid,
service,
user: {
name: user,
url: uid,
},
};
}

async syndicate(properties, publication) {
try {
return await atproto({
identifier: this.options.user,
password: this.options.password,
includePermalink: this.options.includePermalink,
service: `${this.#url.protocol}//${this.#url.hostname}`,
}).post(properties, publication.me);
} catch (error) {
throw new IndiekitError(error.message, {
cause: error,
plugin: this.name,
status: error.statusCode,
});
}
}

init(Indiekit) {
Indiekit.addSyndicator(this);
}
}
49 changes: 49 additions & 0 deletions packages/syndicator-atproto/lib/atproto.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { BskyAgent, RichText } from "@atproto/api";
import { getStatusText } from "./utils.js";

/**
* Syndicate post to an AT Protocol service
* @param {object} options - Syndicator options
* @param {string} options.identifier - User identifier
* @param {string} options.password - Password
* @param {boolean} options.includePermalink - Include permalink in status
* @param {string} options.service - Service URL
* @returns {object} Post functions
*/
export const atproto = ({
identifier,
password,
includePermalink,
service,
}) => ({
async client() {
const agent = new BskyAgent({ service });

await agent.login({ identifier, password });

return agent;
},

/**
* Post to AT Protocol
* @param {object} properties - JF2 properties
* @returns {Promise<string|boolean>} URL of syndicated status
*/
async post(properties) {
const client = await this.client();
const text = getStatusText(properties, {
includePermalink,
service,
});

const rt = new RichText({ text });
await rt.detectFacets(client);

return client.post({
$type: "app.bsky.feed.post",
text: rt.text,
facets: rt.facets,
createdAt: new Date().toISOString(),
});
},
});
Loading

0 comments on commit 613f633

Please sign in to comment.