Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add instagram client #1964

Open
wants to merge 17 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 13 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
13 changes: 13 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -600,3 +600,16 @@ AKASH_MANIFEST_VALIDATION_LEVEL=strict
# Quai Network Ecosystem
QUAI_PRIVATE_KEY=
QUAI_RPC_URL=https://rpc.quai.network

# Instagram Configuration
INSTAGRAM_DRY_RUN=false
INSTAGRAM_USERNAME= # Account username
INSTAGRAM_PASSWORD= # Account password
INSTAGRAM_APP_ID= # Instagram App ID is required
INSTAGRAM_APP_SECRET= # Instagram App Secret is required
INSTAGRAM_BUSINESS_ACCOUNT_ID= # Optional Business Account ID for additional features
INSTAGRAM_POST_INTERVAL_MIN=60 # Default: 60 minutes
INSTAGRAM_POST_INTERVAL_MAX=120 # Default: 120 minutes
INSTAGRAM_ENABLE_ACTION_PROCESSING=false # Enable/disable action processing
INSTAGRAM_ACTION_INTERVAL=5 # Interval between actions in minutes
INSTAGRAM_MAX_ACTIONS=1 # Maximum number of actions to process at once
1 change: 1 addition & 0 deletions agent/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"@elizaos/client-lens": "workspace:*",
"@elizaos/client-telegram": "workspace:*",
"@elizaos/client-twitter": "workspace:*",
"@elizaos/client-instagram": "workspace:*",
"@elizaos/client-slack": "workspace:*",
"@elizaos/core": "workspace:*",
"@elizaos/plugin-0g": "workspace:*",
Expand Down
10 changes: 9 additions & 1 deletion agent/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { SupabaseDatabaseAdapter } from "@elizaos/adapter-supabase";
import { AutoClientInterface } from "@elizaos/client-auto";
import { DiscordClientInterface } from "@elizaos/client-discord";
import { FarcasterAgentClient } from "@elizaos/client-farcaster";
import { InstagramClientInterface } from "@elizaos/client-instagram";
import { LensAgentClient } from "@elizaos/client-lens";
import { SlackClientInterface } from "@elizaos/client-slack";
import { TelegramClientInterface } from "@elizaos/client-telegram";
Expand Down Expand Up @@ -86,6 +87,7 @@ import { quaiPlugin } from "@elizaos/plugin-quai";
import { sgxPlugin } from "@elizaos/plugin-sgx";
import { solanaPlugin } from "@elizaos/plugin-solana";
import { solanaAgentkitPlguin } from "@elizaos/plugin-solana-agentkit";
import { squidRouterPlugin } from "@elizaos/plugin-squid-router";
import { stargazePlugin } from "@elizaos/plugin-stargaze";
import { storyPlugin } from "@elizaos/plugin-story";
import { suiPlugin } from "@elizaos/plugin-sui";
Expand All @@ -95,7 +97,6 @@ import { teeMarlinPlugin } from "@elizaos/plugin-tee-marlin";
import { verifiableLogPlugin } from "@elizaos/plugin-tee-verifiable-log";
import { thirdwebPlugin } from "@elizaos/plugin-thirdweb";
import { tonPlugin } from "@elizaos/plugin-ton";
import { squidRouterPlugin } from "@elizaos/plugin-squid-router";
import { webSearchPlugin } from "@elizaos/plugin-web-search";
import { zksyncEraPlugin } from "@elizaos/plugin-zksync-era";
import Database from "better-sqlite3";
Expand Down Expand Up @@ -583,6 +584,13 @@ export async function initializeClients(
}
}

if (clientTypes.includes(Clients.INSTAGRAM)) {
const instagramClient = await InstagramClientInterface.start(runtime);
if (instagramClient) {
clients.instagram = instagramClient;
}
}

if (clientTypes.includes(Clients.FARCASTER)) {
// why is this one different :(
const farcasterClient = new FarcasterAgentClient(runtime);
Expand Down
113 changes: 113 additions & 0 deletions packages/client-instagram/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# @elizaos/client-instagram

An Instagram client implementation for ElizaOS, enabling Instagram integration with support for media posting, comment handling, and interaction management.

## Features

- Instagram API integration using instagram-private-api
- Media post creation and scheduling
- Comment and interaction handling
- Profile management
- Media processing utilities
- Rate limiting and request queuing
- Session management and caching

## Installation

As this is a workspace package, it's installed as part of the ElizaOS monorepo:

```bash
pnpm install
```

## Configuration

The client requires the following environment variables:

```bash
# Instagram Credentials
INSTAGRAM_USERNAME=your_username
INSTAGRAM_PASSWORD=your_password
INSTAGRAM_APP_ID=your_app_id
INSTAGRAM_APP_SECRET=your_app_secret

# Optional Business Account
INSTAGRAM_BUSINESS_ACCOUNT_ID=your_business_account_id

# Posting Configuration
POST_INTERVAL_MIN=90 # Minimum interval between posts (minutes)
POST_INTERVAL_MAX=180 # Maximum interval between posts (minutes)
ENABLE_ACTION_PROCESSING=true
ACTION_INTERVAL=5 # Minutes between action processing
MAX_ACTIONS_PROCESSING=1 # Maximum actions to process per interval
```

## Usage

### Basic Initialization

```typescript
import { InstagramClientInterface } from '@elizaos/client-instagram';

// Initialize the client
const instagramManager = await InstagramClientInterface.start(runtime);
```

### Posting Content

All posts on Instagram must include media (image, video, or carousel):

```typescript
// Post a single image
await instagramManager.post.createPost({
media: [{
type: 'IMAGE',
url: 'path/to/image.jpg'
}],
caption: 'Hello Instagram!'
});

// Post a carousel
await instagramManager.post.createPost({
media: [
{ type: 'IMAGE', url: 'path/to/image1.jpg' },
{ type: 'IMAGE', url: 'path/to/image2.jpg' }
],
caption: 'Check out these photos!'
});
```

### Handling Interactions

```typescript
// Handle comments
await instagramManager.interaction.handleComment({
mediaId: 'media-123',
comment: 'Great post!',
userId: 'user-123'
});

// Like media
await instagramManager.interaction.likeMedia('media-123');
```

## Key Components

1. ClientBase
- Handles authentication and session management
- Manages API rate limiting
- Provides core API functionality


2. PostClient
- Manages media uploads
- Handles post scheduling
- Processes media before upload


3. InteractionClient
- Handles comments and likes
- Manages user interactions
- Processes notifications


Binary file added packages/client-instagram/assets/goku.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
37 changes: 37 additions & 0 deletions packages/client-instagram/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"name": "@elizaos/client-instagram",
"version": "0.1.0",
"type": "module",
"main": "dist/index.js",
"module": "dist/index.js",
"types": "dist/index.d.ts",
"exports": {
"./package.json": "./package.json",
".": {
"import": {
"@elizaos/source": "./src/index.ts",
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
}
},
"files": [
"dist"
],
"dependencies": {
"@elizaos/core": "workspace:*",
"zod": "3.23.8",
"instagram-private-api": "^1.45.3",
"sharp": "^0.33.2",
"glob": "11.0.0"
},
"devDependencies": {
"tsup": "8.3.5",
"@types/sharp": "^0.32.0"
},
"scripts": {
"build": "tsup --format esm --dts",
"dev": "tsup --format esm --dts --watch",
"lint": "eslint --fix --cache ."
}
}
124 changes: 124 additions & 0 deletions packages/client-instagram/src/environment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import {
IAgentRuntime,
parseBooleanFromText,
} from "@elizaos/core";
import { z } from "zod";

export const DEFAULT_POST_INTERVAL_MIN = 60;
export const DEFAULT_POST_INTERVAL_MAX = 120;
export const DEFAULT_ACTION_INTERVAL = 5;
export const DEFAULT_MAX_ACTIONS = 1;

// Define validation schemas for Instagram usernames and other fields
const instagramUsernameSchema = z
.string()
.min(1, "An Instagram Username must be at least 1 character long")
.max(30, "An Instagram Username cannot exceed 30 characters")
.refine((username) => {
// Instagram usernames can contain letters, numbers, periods, and underscores
return /^[A-Za-z0-9._]+$/.test(username);
}, "An Instagram Username can only contain letters, numbers, periods, and underscores");

/**
* Environment configuration schema for Instagram client
*/
export const instagramEnvSchema = z.object({
INSTAGRAM_DRY_RUN: z.boolean(),
INSTAGRAM_USERNAME: instagramUsernameSchema,
INSTAGRAM_PASSWORD: z.string().min(1, "Instagram password is required"),

// Instagram API credentials
INSTAGRAM_APP_ID: z.string().min(1, "Instagram App ID is required"),
INSTAGRAM_APP_SECRET: z.string().min(1, "Instagram App Secret is required"),

// Optional Business Account ID for additional features
INSTAGRAM_BUSINESS_ACCOUNT_ID: z.string().optional(),

// Posting configuration
INSTAGRAM_POST_INTERVAL_MIN: z.number().int().default(DEFAULT_POST_INTERVAL_MIN),
INSTAGRAM_POST_INTERVAL_MAX: z.number().int().default(DEFAULT_POST_INTERVAL_MAX),

// Action processing configuration
INSTAGRAM_ENABLE_ACTION_PROCESSING: z.boolean().default(false),
INSTAGRAM_ACTION_INTERVAL: z.number().int().default(DEFAULT_ACTION_INTERVAL),
INSTAGRAM_MAX_ACTIONS: z.number().int().default(DEFAULT_MAX_ACTIONS),
});

export type InstagramConfig = z.infer<typeof instagramEnvSchema>;

/**
* Validates and constructs an InstagramConfig object using zod,
* taking values from the IAgentRuntime or process.env as needed.
*/
export async function validateInstagramConfig(
runtime: IAgentRuntime
): Promise<InstagramConfig> {
try {
const instagramConfig = {
INSTAGRAM_DRY_RUN: parseBooleanFromText(
runtime.getSetting("INSTAGRAM_DRY_RUN") ||
process.env.INSTAGRAM_DRY_RUN
) ?? false,

INSTAGRAM_USERNAME: runtime.getSetting("INSTAGRAM_USERNAME") ||
process.env.INSTAGRAM_USERNAME,

INSTAGRAM_PASSWORD: runtime.getSetting("INSTAGRAM_PASSWORD") ||
process.env.INSTAGRAM_PASSWORD,

INSTAGRAM_APP_ID: runtime.getSetting("INSTAGRAM_APP_ID") ||
process.env.INSTAGRAM_APP_ID,

INSTAGRAM_APP_SECRET: runtime.getSetting("INSTAGRAM_APP_SECRET") ||
process.env.INSTAGRAM_APP_SECRET,

INSTAGRAM_BUSINESS_ACCOUNT_ID: runtime.getSetting("INSTAGRAM_BUSINESS_ACCOUNT_ID") ||
process.env.INSTAGRAM_BUSINESS_ACCOUNT_ID,

INSTAGRAM_POST_INTERVAL_MIN: parseInt(
runtime.getSetting("INSTAGRAM_POST_INTERVAL_MIN") ||
process.env.INSTAGRAM_POST_INTERVAL_MIN ||
DEFAULT_POST_INTERVAL_MIN.toString(),
10
),

INSTAGRAM_POST_INTERVAL_MAX: parseInt(
runtime.getSetting("INSTAGRAM_POST_INTERVAL_MAX") ||
process.env.INSTAGRAM_POST_INTERVAL_MAX ||
DEFAULT_POST_INTERVAL_MAX.toString(),
10
),

INSTAGRAM_ENABLE_ACTION_PROCESSING: parseBooleanFromText(
runtime.getSetting("ENABLE_ACTION_PROCESSING") ||
process.env.ENABLE_ACTION_PROCESSING
) ?? false,

INSTAGRAM_ACTION_INTERVAL: parseInt(
runtime.getSetting("ACTION_INTERVAL") ||
process.env.ACTION_INTERVAL ||
DEFAULT_ACTION_INTERVAL.toString(),
10
),

INSTAGRAM_MAX_ACTIONS: parseInt(
runtime.getSetting("MAX_ACTIONS_PROCESSING") ||
process.env.MAX_ACTIONS_PROCESSING ||
DEFAULT_MAX_ACTIONS.toString(),
10
),
};

return instagramEnvSchema.parse(instagramConfig);
} catch (error) {
if (error instanceof z.ZodError) {
const errorMessages = error.errors
.map((err) => `${err.path.join(".")}: ${err.message}`)
.join("\n");
throw new Error(
`Instagram configuration validation failed:\n${errorMessages}`
);
}
throw error;
}
}
55 changes: 55 additions & 0 deletions packages/client-instagram/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// src/index.ts
import { Client, IAgentRuntime, elizaLogger } from "@elizaos/core";
import { validateInstagramConfig } from "./environment";
import { initializeClient } from "./lib/auth";
import { InstagramInteractionService } from "./services/interaction";
import { InstagramPostService } from "./services/post";

export const InstagramClientInterface: Client = {
async start(runtime: IAgentRuntime) {
try {
// Validate configuration
const config = await validateInstagramConfig(runtime);
elizaLogger.log("Instagram client configuration validated");

// Initialize client and get initial state
const state = await initializeClient(runtime, config);
elizaLogger.log("Instagram client initialized");

// Create services
const postService = new InstagramPostService(runtime, state);
const interactionService = new InstagramInteractionService(runtime, state);

// Start services
if (!config.INSTAGRAM_DRY_RUN) {
await postService.start();
elizaLogger.log("Instagram post service started");

if (config.ENABLE_ACTION_PROCESSING) {
Lukapetro marked this conversation as resolved.
Show resolved Hide resolved
await interactionService.start();
elizaLogger.log("Instagram interaction service started");
}
} else {
elizaLogger.log("Instagram client running in dry-run mode");
}

// Return manager instance
return {
post: postService,
interaction: interactionService,
state
};

} catch (error) {
elizaLogger.error("Failed to start Instagram client:", error);
throw error;
}
},

async stop(runtime: IAgentRuntime) {
elizaLogger.log("Stopping Instagram client services...");
// Cleanup will be handled by the services themselves
}
};

export default InstagramClientInterface;
Loading
Loading