Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ let pluginRegistry: Record<string, PluginMetadata> = {
url: "http://localhost:3012/remoteEntry.js",
type: "distributor",
},
"@curatedotfun/farcaster": {
url: "http://localhost:3013/remoteEntry.js",
type: "distributor",
},
};

export function getPluginByName(name: string): PluginMetadata | undefined {
Expand Down
4 changes: 4 additions & 0 deletions apps/plugin-manager/frontend/src/lib/plugin-context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ export const PLUGIN_DEFAULTS: Record<string, Record<string, unknown>> = {
botToken: "{DISCORD_BOT_TOKEN}",
channelId: "123456789012345678",
},
"@curatedotfun/farcaster": {
apiKey: "{FARCASTER_API_KEY}",
signerUuid: "{FARCASTER_SIGNER_UUID}",
},
};

// Create the context with default values
Expand Down
306 changes: 290 additions & 16 deletions bun.lock

Large diffs are not rendered by default.

75 changes: 75 additions & 0 deletions docs/docs/plugins/distributors/farcaster.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
---
sidebar_position: 9
---

# 🟣 Farcaster Plugin

The Farcaster plugin enables distribution of curated content to [Farcaster](https://farcaster.xyz) using the Neynar API.

## 🔧 Setup Guide

1. **Get Neynar API Credentials**
- Sign up at [Neynar](https://neynar.com) and obtain your API key.
- Create a signer in the Neynar dashboard and copy the `signerUuid`.

2. **Add the Plugin to Your Configuration**

In your `curate.config.json` or via the Plugin Manager UI, add the Farcaster plugin:

```json
{
"outputs": {
"stream": {
"enabled": true,
"distribute": [
{
"plugin": "@curatedotfun/farcaster",
"config": {
"apiKey": "{FARCASTER_API_KEY}",
"signerUuid": "{FARCASTER_SIGNER_UUID}"
}
}
]
}
}
}
```

> **Tip:** Use environment variable placeholders (e.g., `{FARCASTER_API_KEY}`) and set them in your `.env` file for security.

## ⚙️ Configuration

| Parameter | Type | Required | Description |
|------------|--------|----------|---------------------------------------------|
| apiKey | string | Yes | Your Neynar API key |
| signerUuid | string | Yes | The UUID of your Neynar signer |

- Get your API key and signer UUID from the [Neynar dashboard](https://neynar.com).

## 🚀 Usage

Once configured, the plugin will post content to Farcaster using the Neynar API whenever a distribution action is triggered.

No additional setup is required after configuration. The plugin will use the provided credentials to publish casts.

## 🛡️ Security Notes

- **Never commit your API key or signer UUID to version control.**
- Store sensitive credentials in environment variables or a secure configuration management system.

## 📝 Example

```typescript
import { FarcasterPlugin } from '@curatedotfun/farcaster';

const plugin = new FarcasterPlugin();
await plugin.initialize({
apiKey: process.env.FARCASTER_API_KEY!,
signerUuid: process.env.FARCASTER_SIGNER_UUID!
});
await plugin.distribute({ input: 'Hello, Farcaster!', config: plugin["config"] });
```

## 🔗 Resources
- [Farcaster](https://farcaster.xyz)
- [Neynar API](https://docs.neynar.com/)
1 change: 1 addition & 0 deletions docs/docs/plugins/distributors/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ Distributors are configured in your `curate.config.json` under the feed's output
- [RSS](./rss.md) - Generate RSS feeds
- [Supabase](./supabase.md) - Store content in Supabase
- [NEAR Social](./near-social.md) - Post content to NEAR Social
- [Farcaster](./farcaster.md) - Post content to Farcaster

## 🔒 Type Safety

Expand Down
51 changes: 51 additions & 0 deletions packages/farcaster/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# @curatedotfun/farcaster

A Farcaster plugin for curatedotfun that allows posting content to the Farcaster network using the Neynar API.

## Installation

```bash
npm install @curatedotfun/farcaster
```

## Usage

```typescript
import { FarcasterPlugin } from '@curatedotfun/farcaster';

// Initialize the plugin with your Neynar API credentials
const farcasterPlugin = new FarcasterPlugin({
apiKey: 'your-neynar-api-key', // Get from https://neynar.com
signerUuid: 'your-signer-uuid' // Get from Neynar dashboard
});

// Initialize the plugin
await farcasterPlugin.initialize();

// Post content to Farcaster
await farcasterPlugin.post('Hello, Farcaster!');

// Cleanup when done
await farcasterPlugin.shutdown();
```
Comment on lines +13 to +30
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix the incorrect usage example.

The usage example contains several errors that don't match the actual implementation:

  1. Constructor usage: The constructor doesn't accept parameters, but the example shows passing config object
  2. Method name: farcasterPlugin.post() doesn't exist - should be farcasterPlugin.distribute()
  3. Initialization pattern: The initialize() method should receive the config, not the constructor

Apply this diff to correct the usage example:

 ```typescript
 import { FarcasterPlugin } from '@curatedotfun/farcaster';

-// Initialize the plugin with your Neynar API credentials
-const farcasterPlugin = new FarcasterPlugin({
-  apiKey: 'your-neynar-api-key', // Get from https://neynar.com
-  signerUuid: 'your-signer-uuid' // Get from Neynar dashboard
-});
+// Create the plugin instance
+const farcasterPlugin = new FarcasterPlugin();

-// Initialize the plugin
-await farcasterPlugin.initialize();
+// Initialize the plugin with your Neynar API credentials
+await farcasterPlugin.initialize({
+  apiKey: 'your-neynar-api-key', // Get from https://neynar.com
+  signerUuid: 'your-signer-uuid' // Get from Neynar dashboard
+});

 // Post content to Farcaster
-await farcasterPlugin.post('Hello, Farcaster!');
+await farcasterPlugin.distribute({
+  input: 'Hello, Farcaster!'
+});

 // Cleanup when done
 await farcasterPlugin.shutdown();

<details>
<summary>🤖 Prompt for AI Agents</summary>

In packages/farcaster/README.md between lines 13 and 30, the usage example
incorrectly passes config to the constructor, uses a non-existent post() method,
and calls initialize() without parameters. Fix this by instantiating
FarcasterPlugin without arguments, passing the config object to initialize(),
and replacing post() with distribute() called with an input object containing
the message string.


</details>

<!-- This is an auto-generated comment by CodeRabbit -->


## Configuration

The plugin requires the following configuration:

- `apiKey`: Your Neynar API key (get it from https://neynar.com)
- `signerUuid`: Your signer UUID from Neynar dashboard

## Prerequisites

1. A Neynar API key (get it from https://neynar.com)
2. A signer UUID from your Neynar dashboard

## Security Notes

- Never commit your API key or signer UUID to version control
- Store sensitive credentials in environment variables or a secure configuration management system

## License

MIT
52 changes: 52 additions & 0 deletions packages/farcaster/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
{
"name": "@curatedotfun/farcaster",
"version": "0.0.1",
"description": "Farcaster plugin for curatedotfun",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"type": "module",
"exports": {
".": {
"types": "./dist/index.d.ts",
"require": "./dist/index.js",
"default": "./dist/index.js"
}
},
"license": "MIT",
"publishConfig": {
"access": "public"
},
"files": [
"dist",
"package.json"
],
"repository": {
"type": "git",
"url": "https://github.com/potlock/curatedotfun-plugins.git",
"directory": "packages/farcaster"
},
"keywords": [
"curatedotfun",
"farcaster",
"distribute",
"plugin"
],
"scripts": {
"build": "rspack build && tsc -p tsconfig.build.json",
"dev": "rspack serve",
"lint": "tsc --noEmit",
"test": "vitest"
},
"devDependencies": {
"@curatedotfun/types": "workspace:*",
"@curatedotfun/utils": "workspace:*",
"@rspack/cli": "latest",
"@types/jest": "^29.5.14",
"typescript": "^5.0.0",
"vitest": "^3.0.7"
},
"dependencies": {
"@module-federation/node": "^2.7.2",
"@neynar/nodejs-sdk": "^2.46.0"
}
}
52 changes: 52 additions & 0 deletions packages/farcaster/rspack.config.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
const path = require("path");
const { rspack } = require("@rspack/core");
const pkg = require("./package.json");
const { getNormalizedRemoteName } = require("@curatedotfun/utils");

module.exports = {
entry: "./src/index",
mode: process.env.NODE_ENV === "development" ? "development" : "production",
target: "async-node",
devtool: "source-map",
output: {
uniqueName: getNormalizedRemoteName(pkg.name),
publicPath: "auto",
path: path.resolve(__dirname, "dist"),
clean: true,
library: { type: "commonjs-module" },
},
devServer: {
static: path.join(__dirname, "dist"),
hot: true,
port: 3013, // Using a different port than translate plugin
devMiddleware: {
writeToDisk: true,
},
},
module: {
rules: [
{
test: /\.tsx?$/,
use: "builtin:swc-loader",
exclude: /node_modules/,
},
],
},
resolve: {
extensions: [".tsx", ".ts", ".js"],
},
plugins: [
new rspack.container.ModuleFederationPlugin({
name: getNormalizedRemoteName(pkg.name),
filename: "remoteEntry.js",
runtimePlugins: [
require.resolve("@module-federation/node/runtimePlugin"),
],
library: { type: "commonjs-module" },
exposes: {
"./plugin": "./src/index.ts",
},
shared: {},
}),
],
};
57 changes: 57 additions & 0 deletions packages/farcaster/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { NeynarAPIClient } from "@neynar/nodejs-sdk";
import type { DistributorPlugin, ActionArgs } from "@curatedotfun/types";

interface FarcasterConfig extends Record<string, unknown> {
apiKey: string;
signerUuid: string;
}

export class FarcasterPlugin
implements DistributorPlugin<{ content: string }, FarcasterConfig>
{
readonly type = "distributor" as const;
private client!: NeynarAPIClient;
private config!: FarcasterConfig;

constructor() {
// Initialize without config, will be set in initialize()
}

async initialize(config?: FarcasterConfig): Promise<void> {
if (!config?.apiKey) {
throw new Error("Neynar API key is required");
}
if (!config?.signerUuid) {
throw new Error("Signer UUID is required");
}

this.config = config;
this.client = new NeynarAPIClient({
apiKey: this.config.apiKey,
});
}

async distribute({
input,
}: ActionArgs<{ content: string }, FarcasterConfig>): Promise<void> {
if (!input) {
throw new Error("Input is required");
}

try {
await this.client.publishCast({
signerUuid: this.config.signerUuid,
text: input.content,
});
} catch (error) {
console.error("Error posting to Farcaster:", error);
throw error;
}
}

async shutdown(): Promise<void> {
// No cleanup needed
}
}

export default FarcasterPlugin;
4 changes: 4 additions & 0 deletions packages/farcaster/tsconfig.build.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"extends": "./tsconfig.json",
"exclude": ["**/*.test.ts", "**/*.spec.ts"]
}
13 changes: 13 additions & 0 deletions packages/farcaster/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}