Skip to content

Commit 1f18f34

Browse files
committed
feat: add farcaster plugin
1 parent eaee155 commit 1f18f34

File tree

11 files changed

+602
-17
lines changed

11 files changed

+602
-17
lines changed

apps/plugin-manager/backend/src/plugin-service/plugin-registry.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ let pluginRegistry: Record<string, PluginMetadata> = {
5757
url: "http://localhost:3012/remoteEntry.js",
5858
type: "distributor",
5959
},
60+
"@curatedotfun/farcaster": {
61+
url: "http://localhost:3013/remoteEntry.js",
62+
type: "distributor",
63+
},
6064
};
6165

6266
export function getPluginByName(name: string): PluginMetadata | undefined {

apps/plugin-manager/frontend/src/lib/plugin-context.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,10 @@ export const PLUGIN_DEFAULTS: Record<string, Record<string, unknown>> = {
8383
botToken: "{DISCORD_BOT_TOKEN}",
8484
channelId: "123456789012345678",
8585
},
86+
"@curatedotfun/farcaster": {
87+
apiKey: "{FARCASTER_API_KEY}",
88+
signerUuid: "{FARCASTER_SIGNER_PRIVATE_KEY}",
89+
},
8690
};
8791

8892
// Create the context with default values
@@ -95,7 +99,7 @@ const PluginContext = createContext<PluginContextType>({
9599
pluginDefaults: PLUGIN_DEFAULTS,
96100
loading: true,
97101
error: null,
98-
refreshRegistry: async () => {},
102+
refreshRegistry: async () => { },
99103
});
100104

101105
// Provider component

bun.lock

Lines changed: 290 additions & 16 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
---
2+
sidebar_position: 9
3+
---
4+
5+
# 🟣 Farcaster Plugin
6+
7+
The Farcaster plugin enables distribution of curated content to [Farcaster](https://farcaster.xyz) using the Neynar API.
8+
9+
## 🔧 Setup Guide
10+
11+
1. **Get Neynar API Credentials**
12+
- Sign up at [Neynar](https://neynar.com) and obtain your API key.
13+
- Create a signer in the Neynar dashboard and copy the `signerUuid`.
14+
15+
2. **Add the Plugin to Your Configuration**
16+
17+
In your `curate.config.json` or via the Plugin Manager UI, add the Farcaster plugin:
18+
19+
```json
20+
{
21+
"outputs": {
22+
"stream": {
23+
"enabled": true,
24+
"distribute": [
25+
{
26+
"plugin": "@curatedotfun/farcaster",
27+
"config": {
28+
"apiKey": "{FARCASTER_API_KEY}",
29+
"signerUuid": "{FARCASTER_SIGNER_UUID}"
30+
}
31+
}
32+
]
33+
}
34+
}
35+
}
36+
```
37+
38+
> **Tip:** Use environment variable placeholders (e.g., `{FARCASTER_API_KEY}`) and set them in your `.env` file for security.
39+
40+
## ⚙️ Configuration
41+
42+
| Parameter | Type | Required | Description |
43+
|------------|--------|----------|---------------------------------------------|
44+
| apiKey | string | Yes | Your Neynar API key |
45+
| signerUuid | string | Yes | The UUID of your Neynar signer |
46+
47+
- Get your API key and signer UUID from the [Neynar dashboard](https://neynar.com).
48+
49+
## 🚀 Usage
50+
51+
Once configured, the plugin will post content to Farcaster using the Neynar API whenever a distribution action is triggered.
52+
53+
No additional setup is required after configuration. The plugin will use the provided credentials to publish casts.
54+
55+
## 🛡️ Security Notes
56+
57+
- **Never commit your API key or signer UUID to version control.**
58+
- Store sensitive credentials in environment variables or a secure configuration management system.
59+
60+
## 📝 Example
61+
62+
```typescript
63+
import { FarcasterPlugin } from '@curatedotfun/farcaster';
64+
65+
const plugin = new FarcasterPlugin();
66+
await plugin.initialize({
67+
apiKey: process.env.FARCASTER_API_KEY!,
68+
signerUuid: process.env.FARCASTER_SIGNER_UUID!
69+
});
70+
await plugin.distribute({ input: 'Hello, Farcaster!', config: plugin["config"] });
71+
```
72+
73+
## 🔗 Resources
74+
- [Farcaster](https://farcaster.xyz)
75+
- [Neynar API](https://docs.neynar.com/)

docs/docs/plugins/distributors/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ Distributors are configured in your `curate.config.json` under the feed's output
4949
- [RSS](./rss.md) - Generate RSS feeds
5050
- [Supabase](./supabase.md) - Store content in Supabase
5151
- [NEAR Social](./near-social.md) - Post content to NEAR Social
52+
- [Farcaster](./farcaster.md) - Post content to Farcaster
5253

5354
## 🔒 Type Safety
5455

packages/farcaster/README.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# @curatedotfun/farcaster
2+
3+
A Farcaster plugin for curatedotfun that allows posting content to the Farcaster network using the Neynar API.
4+
5+
## Installation
6+
7+
```bash
8+
npm install @curatedotfun/farcaster
9+
```
10+
11+
## Usage
12+
13+
```typescript
14+
import { FarcasterPlugin } from '@curatedotfun/farcaster';
15+
16+
// Initialize the plugin with your Neynar API credentials
17+
const farcasterPlugin = new FarcasterPlugin({
18+
apiKey: 'your-neynar-api-key', // Get from https://neynar.com
19+
signerUuid: 'your-signer-uuid' // Get from Neynar dashboard
20+
});
21+
22+
// Initialize the plugin
23+
await farcasterPlugin.initialize();
24+
25+
// Post content to Farcaster
26+
await farcasterPlugin.post('Hello, Farcaster!');
27+
28+
// Cleanup when done
29+
await farcasterPlugin.shutdown();
30+
```
31+
32+
## Configuration
33+
34+
The plugin requires the following configuration:
35+
36+
- `apiKey`: Your Neynar API key (get it from https://neynar.com)
37+
- `signerUuid`: Your signer UUID from Neynar dashboard
38+
39+
## Prerequisites
40+
41+
1. A Neynar API key (get it from https://neynar.com)
42+
2. A signer UUID from your Neynar dashboard
43+
44+
## Security Notes
45+
46+
- Never commit your API key or signer UUID to version control
47+
- Store sensitive credentials in environment variables or a secure configuration management system
48+
49+
## License
50+
51+
MIT

packages/farcaster/package.json

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
{
2+
"name": "@curatedotfun/farcaster",
3+
"version": "0.0.1",
4+
"description": "Farcaster plugin for curatedotfun",
5+
"main": "./dist/index.js",
6+
"types": "./dist/index.d.ts",
7+
"type": "module",
8+
"exports": {
9+
".": {
10+
"types": "./dist/index.d.ts",
11+
"require": "./dist/index.js",
12+
"default": "./dist/index.js"
13+
}
14+
},
15+
"license": "MIT",
16+
"publishConfig": {
17+
"access": "public"
18+
},
19+
"files": [
20+
"dist",
21+
"package.json"
22+
],
23+
"repository": {
24+
"type": "git",
25+
"url": "https://github.com/potlock/curatedotfun-plugins.git",
26+
"directory": "packages/farcaster"
27+
},
28+
"keywords": [
29+
"curatedotfun",
30+
"farcaster",
31+
"distribute",
32+
"plugin"
33+
],
34+
"scripts": {
35+
"build": "rspack build && tsc -p tsconfig.build.json",
36+
"dev": "rspack serve",
37+
"lint": "tsc --noEmit",
38+
"test": "vitest"
39+
},
40+
"devDependencies": {
41+
"@curatedotfun/types": "workspace:*",
42+
"@curatedotfun/utils": "workspace:*",
43+
"@rspack/cli": "latest",
44+
"@types/jest": "^29.5.14",
45+
"typescript": "^5.0.0",
46+
"vitest": "^3.0.7"
47+
},
48+
"dependencies": {
49+
"@module-federation/node": "^2.7.2",
50+
"@neynar/nodejs-sdk": "^2.46.0"
51+
}
52+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
const path = require("path");
2+
const { rspack } = require("@rspack/core");
3+
const pkg = require("./package.json");
4+
const { getNormalizedRemoteName } = require("@curatedotfun/utils");
5+
6+
module.exports = {
7+
entry: "./src/index",
8+
mode: process.env.NODE_ENV === "development" ? "development" : "production",
9+
target: "async-node",
10+
devtool: "source-map",
11+
output: {
12+
uniqueName: getNormalizedRemoteName(pkg.name),
13+
publicPath: "auto",
14+
path: path.resolve(__dirname, "dist"),
15+
clean: true,
16+
library: { type: "commonjs-module" },
17+
},
18+
devServer: {
19+
static: path.join(__dirname, "dist"),
20+
hot: true,
21+
port: 3013, // Using a different port than translate plugin
22+
devMiddleware: {
23+
writeToDisk: true,
24+
},
25+
},
26+
module: {
27+
rules: [
28+
{
29+
test: /\.tsx?$/,
30+
use: "builtin:swc-loader",
31+
exclude: /node_modules/,
32+
},
33+
],
34+
},
35+
resolve: {
36+
extensions: [".tsx", ".ts", ".js"],
37+
},
38+
plugins: [
39+
new rspack.container.ModuleFederationPlugin({
40+
name: getNormalizedRemoteName(pkg.name),
41+
filename: "remoteEntry.js",
42+
runtimePlugins: [
43+
require.resolve("@module-federation/node/runtimePlugin"),
44+
],
45+
library: { type: "commonjs-module" },
46+
exposes: {
47+
"./plugin": "./src/index.ts",
48+
},
49+
shared: {},
50+
}),
51+
],
52+
};

packages/farcaster/src/index.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { NeynarAPIClient } from '@neynar/nodejs-sdk';
2+
import type { DistributorPlugin, ActionArgs } from '@curatedotfun/types';
3+
4+
interface FarcasterConfig extends Record<string, unknown> {
5+
apiKey: string;
6+
signerUuid: string;
7+
}
8+
9+
export class FarcasterPlugin implements DistributorPlugin<string, FarcasterConfig> {
10+
readonly type = "distributor" as const;
11+
private client!: NeynarAPIClient;
12+
private config!: FarcasterConfig;
13+
14+
constructor() {
15+
// Initialize without config, will be set in initialize()
16+
}
17+
18+
async initialize(config?: FarcasterConfig): Promise<void> {
19+
if (!config?.apiKey) {
20+
throw new Error('Neynar API key is required');
21+
}
22+
if (!config?.signerUuid) {
23+
throw new Error('Signer UUID is required');
24+
}
25+
26+
this.config = config;
27+
this.client = new NeynarAPIClient({
28+
apiKey: this.config.apiKey,
29+
});
30+
}
31+
32+
async distribute({
33+
input,
34+
}: ActionArgs<unknown, FarcasterConfig>): Promise<void> {
35+
if (!input) {
36+
throw new Error('Input is required');
37+
}
38+
39+
try {
40+
await this.client.publishCast({
41+
signerUuid: this.config.signerUuid,
42+
text: (input as { content: string })?.content
43+
});
44+
} catch (error) {
45+
console.error('Error posting to Farcaster:', error);
46+
throw error;
47+
}
48+
}
49+
50+
async shutdown(): Promise<void> {
51+
// No cleanup needed
52+
}
53+
}
54+
55+
export default FarcasterPlugin;
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"extends": "./tsconfig.json",
3+
"exclude": ["**/*.test.ts", "**/*.spec.ts"]
4+
}

0 commit comments

Comments
 (0)