diff --git a/examples/README.md b/examples/README.md index dda6c3b6..b917bcbe 100644 --- a/examples/README.md +++ b/examples/README.md @@ -8,5 +8,4 @@ This directory contains example implementations showing how to use the library w 2. Run the examples using: -npm run telegram-example # for telegram bot -npm run twitter-example # for twitter bot \ No newline at end of file +please refer to each example's README.md for more information on how to run the examples. \ No newline at end of file diff --git a/examples/package.json b/examples/package.json index 5f693ccf..ad1cf93c 100644 --- a/examples/package.json +++ b/examples/package.json @@ -2,7 +2,9 @@ "name": "examples", "private": true, "dependencies": { - "@virtuals-protocol/game": "^0.1.9", + "@virtuals-protocol/game": "^0.1.11", + "@virtuals-protocol/game-telegram-plugin": "^0.1.4", + "@virtuals-protocol/game-twitter-plugin": "^0.1.8", "dotenv": "^16.4.7", "openai": "^4.81.0", "twitter-api-v2": "^1.15.0" @@ -12,6 +14,6 @@ "build": "tsc -p ./tsconfig.json", "twitter-example": "ts-node twitter-example/twitter.ts", "telegram-example": "ts-node telegram-example/tg.ts", - "state-management": "ts-node state-management/index.ts" + "state-management": "ts-node state-management/index.ts" } } diff --git a/examples/telegram-example/README.md b/examples/telegram-example/README.md new file mode 100644 index 00000000..42f87185 --- /dev/null +++ b/examples/telegram-example/README.md @@ -0,0 +1,105 @@ +# Telegram Plugin for Virtuals Game + +This plugin allows you to integrate Telegram functionalities into your Virtuals Game. + +## Installation + +To install the plugin, use npm or yarn: + +```bash +npm install @virtuals-protocol/game-telegram-plugin +``` + +or + +```bash +yarn add @virtuals-protocol/game-telegram-plugin +``` + +## Usage + +### Importing the Plugin + +First, import the `TelegramPlugin` class from the plugin: + +```typescript +import TelegramPlugin from "@virtuals-protocol/game-telegram-plugin"; +``` + +### Creating a Worker + +Create a worker with the necessary Telegram credentials: + +```typescript +const telegramPlugin = new TelegramPlugin({ + credentials: { + botToken: "", + }, +}); +``` + +### Creating an Agent + +Create an agent and add the worker to it: + +```typescript +import { GameAgent } from "@virtuals-protocol/game"; + +const agent = new GameAgent("", { + name: "Telegram Bot", + goal: "Auto reply message", + description: "A bot that can post send message and pinned message", + workers: [ + telegramPlugin.getWorker(), + ], +}); +``` + +### Running the Agent + +Initialize and run the agent: + +```typescript +(async () => { + await agent.init(); + + const agentTgWorker = agent.getWorkerById(telegramPlugin.getWorker().id); + const task = "PROMPT"; + + await agentTgWorker.runTask(task, { + verbose: true, // Optional: Set to true to log each step + }); +})(); +``` + +## Available Functions + +The `TelegramPlugin` provides several functions that can be used by the agent: + +- `sendMessageFunction`: Send message. +- `sendMediaFunction`: Send media. +- `createPollFunction`: Create poll. +- `pinnedMessageFunction`: Pinned message. +- `unPinnedMessageFunction`: Unpinned message. +- `deleteMessageFunction`: Delete message. + +## Event Handlers +The plugin also supports custom handlers for the following Telegram events: +### Handling Incoming Messages +To handle incoming messages, use the `onMessage` method to listen on: +```typescript +telegramPlugin.onMessage((msg) => { + console.log("Received message:", msg); +}); +``` +### Handling Poll Answers +To handle poll answers, use the `onPollAnswer` method: +```typescript +telegramPlugin.onPollAnswer((pollAnswer) => { + console.log("Received poll answer:", pollAnswer); +}); +``` + +## License + +This project is licensed under the MIT License. diff --git a/examples/telegram-example/tg.ts b/examples/telegram-example/tg.ts index 57a12445..88d7fd16 100644 --- a/examples/telegram-example/tg.ts +++ b/examples/telegram-example/tg.ts @@ -1,108 +1,144 @@ -import { - ExecutableGameFunctionResponse, - ExecutableGameFunctionStatus, - GameAgent, - GameFunction, - GameWorker, -} from "@virtuals-protocol/game"; -import dotenv from 'dotenv'; -import path from 'path'; +import { GameAgent } from "@virtuals-protocol/game"; +import TelegramPlugin from "@virtuals-protocol/game-telegram-plugin"; -// Load environment variables from the correct location -dotenv.config({ path: path.join(__dirname, '.env') }); - -const generateImageFunction = new GameFunction({ - name: "generate_image", - description: "Generate an image", - args: [ - { - name: "image_description", - description: "The description of the image to generate", - }, - ] as const, - executable: async (args, logger) => { - try { - // TODO: Implement generate image with url - logger(`Generating image with description: ${args.image_description}`); - - return new ExecutableGameFunctionResponse( - ExecutableGameFunctionStatus.Done, - "Image generated with URL: https://example.com/image.png" - ); - } catch (e) { - return new ExecutableGameFunctionResponse( - ExecutableGameFunctionStatus.Failed, - "Failed to generate image" - ); - } +// Create a worker with the functions +// Replace with your Telegram bot token +const telegramPlugin = new TelegramPlugin({ + credentials: { + botToken: "", }, }); -const replyMessageFunction = new GameFunction({ - name: "reply_message", - description: "Reply to a message", - args: [ - { name: "message", description: "The message to reply" }, - { - name: "media_url", - description: "The media url to attach to the message", - optional: true, - }, - ] as const, - - executable: async (args, logger) => { - try { - // TODO: Implement replying to message with image - if (args.media_url) { - logger(`Reply with media: ${args.media_url}`); - } - - // TODO: Implement replying to message - logger(`Replying to message: ${args.message}`); - - return new ExecutableGameFunctionResponse( - ExecutableGameFunctionStatus.Done, - `Replied with message: ${args.message}` - ); - } catch (e) { - return new ExecutableGameFunctionResponse( - ExecutableGameFunctionStatus.Failed, - "Failed to reply to message" - ); - } - }, +telegramPlugin.onMessage(async (msg) => { + console.log('Custom message handler:', msg); }); -const telegramWorker = new GameWorker({ - id: "telegram", - name: "telegram", - description: "Telegram worker", - functions: [generateImageFunction, replyMessageFunction], +telegramPlugin.onPollAnswer((pollAnswer) => { + console.log('Custom poll answer handler:', pollAnswer); + // You can process the poll answer as needed }); -const agent = new GameAgent(process.env.API_KEY!, { +/** + * The agent will be able to send messages and pin messages automatically + * Replace with your API token + */ +const autoReplyAgent = new GameAgent("", { name: "Telegram Bot", - goal: "Interact with Telegram", - description: "Telegram agent", - workers: [telegramWorker], + goal: "Auto reply message", + description: "This agent will auto reply to messages", + workers: [ + telegramPlugin.getWorker({ + // Define the functions that the worker can perform, by default it will use the all functions defined in the plugin + functions: [ + telegramPlugin.sendMessageFunction, + telegramPlugin.pinnedMessageFunction, + telegramPlugin.unPinnedMessageFunction, + telegramPlugin.createPollFunction, + telegramPlugin.sendMediaFunction, + telegramPlugin.deleteMessageFunction, + ], + }), + ], +}); + +/** + * Initialize the agent and start listening for messages + * The agent will automatically reply to messages + */ +(async () => { + autoReplyAgent.setLogger((autoReplyAgent, message) => { + console.log(`-----[${autoReplyAgent.name}]-----`); + console.log(message); + console.log("\n"); + }); + + await autoReplyAgent.init(); + telegramPlugin.onMessage(async (msg) => { + const agentTgWorker = autoReplyAgent.getWorkerById(telegramPlugin.getWorker().id); + const task = "Reply to chat id: " + msg.chat.id + " and the incoming is message: " + msg.text + " and the message id is: " + msg.message_id; + + await agentTgWorker.runTask(task, { + verbose: true, // Optional: Set to true to log each step + }); + }); +})(); + +/** + * The agent is a Financial Advisor designed to provide financial advice and assistance + */ +const financialAdvisorAgent = new GameAgent("", { + name: "Financial Advisor Bot", + goal: "Provide financial advice and assistance", + description: "A smart bot designed to answer financial questions, provide investment tips, assist with budgeting, and manage financial tasks like pinning important messages or deleting outdated ones for better organization.", + workers: [ + telegramPlugin.getWorker({ + // Define the functions that the worker can perform, by default it will use the all functions defined in the plugin + functions: [ + telegramPlugin.sendMessageFunction, + telegramPlugin.pinnedMessageFunction, + telegramPlugin.unPinnedMessageFunction, + telegramPlugin.createPollFunction, + telegramPlugin.sendMediaFunction, + telegramPlugin.deleteMessageFunction, + ], + }), + ], }); (async () => { - // define custom logger - agent.setLogger((agent, msg) => { - console.log(`-----[${agent.name}]-----`); - console.log(msg); + financialAdvisorAgent.setLogger((financialAdvisorAgent, message) => { + console.log(`-----[${financialAdvisorAgent.name}]-----`); + console.log(message); console.log("\n"); }); - await agent.init(); + await financialAdvisorAgent.init(); + telegramPlugin.onMessage(async (msg) => { + const agentTgWorker = financialAdvisorAgent.getWorkerById(telegramPlugin.getWorker().id); + const task = "Reply to chat id: " + msg.chat.id + " and the incoming is message: " + msg.text + " and the message id is: " + msg.message_id; + + await agentTgWorker.runTask(task, { + verbose: true, // Optional: Set to true to log each step + }); + }); +})(); + +/** + * The agent is a Nutritionist Bot designed for nutritional counseling and support + */ +const nutritionistAgent = new GameAgent("", { + name: "Nutritionist Bot", + goal: "Provide evidence-based information and guidance about the impacts of food and nutrition on the health and wellbeing of humans.", + description: "A smart bot designed to answer food and nutrition questions, provide personalized nutrition plans, nutritional counseling, motivate and support users in achieving their health goals.", + workers: [ + telegramPlugin.getWorker({ + // Define the functions that the worker can perform, by default it will use the all functions defined in the plugin + functions: [ + telegramPlugin.sendMessageFunction, + telegramPlugin.pinnedMessageFunction, + telegramPlugin.unPinnedMessageFunction, + telegramPlugin.createPollFunction, + telegramPlugin.sendMediaFunction, + telegramPlugin.deleteMessageFunction, + ], + }), + ], +}); - const agentTgWorker = agent.getWorkerById(telegramWorker.id); +(async () => { + nutritionistAgent.setLogger((nutritionistAgent, message) => { + console.log(`-----[${nutritionistAgent.name}]-----`); + console.log(message); + console.log("\n"); + }); - const task = - "Gotten a message from user. Message content: hey! i will need help with my project, i need an image of a cat hugging AI. Can you help me with that? Give me something that cool and cute!"; + await nutritionistAgent.init(); + telegramPlugin.onMessage(async (msg) => { + const agentTgWorker = nutritionistAgent.getWorkerById(telegramPlugin.getWorker().id); + const task = "Reply professionally to chat id: " + msg.chat.id + " and the incoming is message: " + msg.text + " and the message id is: " + msg.message_id; - await agentTgWorker.runTask(task, { - verbose: true, // Optional: Set to true to log each step + await agentTgWorker.runTask(task, { + verbose: true, // Optional: Set to true to log each step + }); }); })(); diff --git a/examples/twitter-example/README.md b/examples/twitter-example/README.md new file mode 100644 index 00000000..01514261 --- /dev/null +++ b/examples/twitter-example/README.md @@ -0,0 +1,139 @@ +# Twitter Plugin for Virtuals Game + +This plugin allows you to integrate Twitter functionalities into your Virtuals Game. With this plugin, you can post tweets, reply to tweets, like tweets, and more. + +## Installation + +To install the plugin, use npm or yarn: + +```bash +npm install @virtuals-protocol/game-twitter-plugin +``` + +or + +```bash +yarn add @virtuals-protocol/game-twitter-plugin +``` + +## Usage + +### Importing the Plugin + +First, import the `TwitterPlugin` class from the plugin: + +```typescript +import TwitterPlugin from "@virtuals-protocol/game-twitter-plugin"; +``` + +### Selection a twitter plugin + +### Selecting a Twitter Plugin + +There are two options for selecting a Twitter plugin based on your needs: + +1. **Game Twitter Client**: This client is designed specifically for integration with the Virtuals Game environment. It provides seamless interaction with the game and allows for enhanced functionalities tailored to game-specific requirements. + +```typescript +import { GameTwitterClient } from "@virtuals-protocol/game-twitter-plugin"; + +const gameTwitterClient = new GameTwitterClient({ + accessToken: "your_game_access_token", +}); +``` + +To get the access token, run the following command: + +```bash +npx @virtuals-protocol/game-twitter-plugin auth -k +``` + +Here is an example run: + +```bash +npx @virtuals-protocol/game-twitter-plugin auth -k apt-xxxxxxxxxx +``` + +You will see the following output: + +``` +Waiting for authentication... + +Visit the following URL to authenticate: +https://x.com/i/oauth2/authorize?response_type=code&client_id=VVdyZ0t4WFFRMjBlMzVaczZyMzU6MTpjaQ&redirect_uri=http%3A%2F%2Flocalhost%3A8714%2Fcallback&state=866c82c0-e3f6-444e-a2de-e58bcc95f08b&code_challenge=K47t-0Mcl8B99ufyqmwJYZFB56fiXiZf7f3euQ4H2_0&code_challenge_method=s256&scope=tweet.read%20tweet.write%20users.read%20offline.access +``` + +After authenticating, you will receive the following message: + +``` +Authenticated! Here's your access token: +apx-613f64069424d88c6fbf2e75c0c80a34 +``` + +2. **Native Twitter Client**: This client is a more general-purpose Twitter client that can be used outside of the game context. It provides standard Twitter functionalities and can be used in various applications. + +```typescript +import { TwitterClient } from "@virtuals-protocol/game-twitter-plugin"; + +const nativeTwitterClient = new TwitterClient({ + apiKey: "your_api_key", + apiSecretKey: "your_api_secret_key", + accessToken: "your_access_token", + accessTokenSecret: "your_access_token_secret", +}); +``` + +### Creating a Worker + +Create a worker with the necessary Twitter credentials: + +```typescript +const twitterPlugin = new TwitterPlugin({ + twitterClient: gameTwitterClient || nativeTwitterClient, // choose either 1 client +}); +``` + +### Creating an Agent + +Create an agent and add the worker to it: + +```typescript +import { GameAgent } from "@virtuals-protocol/game"; + +const agent = new GameAgent("API_KEY", { + name: "Twitter Bot", + goal: "Increase engagement and grow follower count", + description: "A bot that can post tweets, reply to tweets, and like tweets", + workers: [twitterPlugin.getWorker()], +}); +``` + +### Running the Agent + +Initialize and run the agent: + +```typescript +(async () => { + await agent.init(); + + while (true) { + await agent.step({ + verbose: true, + }); + } +})(); +``` + +## Available Functions + +The `TwitterPlugin` provides several functions that can be used by the agent: + +- `searchTweetsFunction`: Search for tweets based on a query. +- `replyTweetFunction`: Reply to a tweet. +- `postTweetFunction`: Post a new tweet. +- `likeTweetFunction`: Like a tweet. +- `quoteTweetFunction`: Quote a tweet with your own commentary. + +## License + +This project is licensed under the MIT License. diff --git a/examples/twitter-example/twitter.ts b/examples/twitter-example/twitter.ts index 91dbf3e0..a1c91592 100644 --- a/examples/twitter-example/twitter.ts +++ b/examples/twitter-example/twitter.ts @@ -1,145 +1,65 @@ -import { - ExecutableGameFunctionResponse, - ExecutableGameFunctionStatus, - GameAgent, - GameFunction, - GameWorker, - LLMModel, -} from "@virtuals-protocol/game"; -import dotenv from 'dotenv'; -import path from 'path'; +import { GameAgent } from "@virtuals-protocol/game"; +import TwitterPlugin, { + GameTwitterClient, + TwitterClient, +} from "@virtuals-protocol/game-twitter-plugin"; -// Load environment variables from the correct location -dotenv.config({ path: path.join(__dirname, '.env') }); - -const postTweetFunction = new GameFunction({ - name: "post_tweet", - description: "Post a tweet", - args: [ - { name: "tweet", description: "The tweet content" }, - { name: "tweet_reasoning", description: "The reasoning behind the tweet" }, - ] as const, - executable: async (args, logger) => { - try { - // TODO: Implement posting tweet - logger(`Posting tweet: ${args.tweet}`); - logger(`Reasoning: ${args.tweet_reasoning}`); - - return new ExecutableGameFunctionResponse( - ExecutableGameFunctionStatus.Done, - "Tweet posted" - ); - } catch (e) { - return new ExecutableGameFunctionResponse( - ExecutableGameFunctionStatus.Failed, - "Failed to post tweet" - ); - } - }, +const gameTwitterClient = new GameTwitterClient({ + accessToken: "xxxxxxxxxx", }); -const searchTweetsFunction = new GameFunction({ - name: "search_tweets", - description: "Search tweets and return results", - args: [ - { name: "query", description: "The query to search for" }, - { name: "reasoning", description: "The reasoning behind the search" }, - ] as const, - executable: async (args, logger) => { - try { - const query = args.query; - - //TODO: Implement searching tweets - logger(`Searching tweets for query: ${query}`); - - return new ExecutableGameFunctionResponse( - ExecutableGameFunctionStatus.Done, - // Return the search results as a string - "Tweets searched here are the results: [{tweetId: 1, content: 'Hello World'}, {tweetId: 2, content: 'Goodbye World'}]" - ); - } catch (e) { - return new ExecutableGameFunctionResponse( - ExecutableGameFunctionStatus.Failed, - "Failed to search tweets" - ); - } - }, -}); - -const replyToTweetFunction = new GameFunction({ - name: "reply_to_tweet", - description: "Reply to a tweet", - args: [ - { name: "tweet_id", description: "The tweet id to reply to" }, - { name: "reply", description: "The reply content" }, - ] as const, - executable: async (args, logger) => { - try { - const tweetId = args.tweet_id; - const reply = args.reply; - - //TODO: Implement replying to tweet - logger(`Replying to tweet ${tweetId} with ${reply}`); - - return new ExecutableGameFunctionResponse( - ExecutableGameFunctionStatus.Done, - `Replied to tweet ${tweetId} with ${reply}` - ); - } catch (e) { - return new ExecutableGameFunctionResponse( - ExecutableGameFunctionStatus.Failed, - "Failed to reply to tweet" - ); - } - }, +const nativeTwitterClient = new TwitterClient({ + apiKey: "xxxxxxx", + apiSecretKey: "xxxxxxx", + accessToken: "xxxxxxx", + accessTokenSecret: "xxxxxxxxx", }); // Create a worker with the functions -const postTweetWorker = new GameWorker({ - id: "twitter_main_worker", - name: "Twitter main worker", - description: "Worker that posts tweets", - functions: [searchTweetsFunction, replyToTweetFunction, postTweetFunction], - // Optional: Get the environment - getEnvironment: async () => { - return { - tweet_limit: 15, - }; - }, +const twitterPlugin = new TwitterPlugin({ + id: "twitter_worker", + name: "Twitter Worker", + description: + "A worker that will execute tasks within the Twitter Social Platforms. It is capable of posting, reply, quote and like tweets.", + // twitterClient: nativeTwitterClient, + twitterClient: gameTwitterClient, // Use this if you want to use the game client }); // Create an agent with the worker -const agent = new GameAgent(process.env.API_KEY!, { +const agent = new GameAgent(process.env.GAME_API_KEY || "", { name: "Twitter Bot", - goal: "Search and reply to tweets", - description: "A bot that searches for tweets and replies to them", - workers: [postTweetWorker], - llmModel: LLMModel.DeepSeek_R1, // Optional: Set the LLM model default (LLMModel.Llama_3_1_405B_Instruct) - // Optional: Get the agent state - getAgentState: async () => { - return { - username: "twitter_bot", - follower_count: 1000, - tweet_count: 10, - }; - }, + goal: "increase engagement and grow follower count", + description: "A bot that can post tweets, reply to tweets, and like tweets", + workers: [ + twitterPlugin.getWorker({ + // Define the functions that the worker can perform, by default it will use the all functions defined in the plugin + // functions: [ + // twitterPlugin.searchTweetsFunction, + // twitterPlugin.replyTweetFunction, + // twitterPlugin.postTweetFunction, + // ], + // Define the environment variables that the worker can access, by default it will use the metrics defined in the plugin + // getEnvironment: async () => ({ + // ...(await twitterPlugin.getMetrics()), + // username: "virtualsprotocol", + // token_price: "$100.00", + // }), + }), + ], }); (async () => { - // define custom logger - agent.setLogger((agent, msg) => { + agent.setLogger((agent, message) => { console.log(`-----[${agent.name}]-----`); - console.log(msg); + console.log(message); console.log("\n"); }); - // Initialize the agent await agent.init(); - // Run the agent for with 60 seconds interval - // this will stop when agent decides to wait - await agent.run(60, { verbose: true }); // verbose will give you more information about the agent's actions - - // if you need more control over the agent, you can use the step method - // await agent.step(); + while (true) { + await agent.step({ + verbose: true, + }); + } })(); diff --git a/plugins/farcasterPlugin/package.json b/plugins/farcasterPlugin/package.json index a97df2c6..8403c638 100644 --- a/plugins/farcasterPlugin/package.json +++ b/plugins/farcasterPlugin/package.json @@ -1,5 +1,5 @@ { - "name": "@virtuals-protocol/game-twitter-plugin", + "name": "@virtuals-protocol/game-farcaster-plugin", "version": "0.1.3", "description": "", "main": "./dist/index.js", diff --git a/plugins/rss3Plugin/package.json b/plugins/rss3Plugin/package.json index 989341fa..98f20bff 100644 --- a/plugins/rss3Plugin/package.json +++ b/plugins/rss3Plugin/package.json @@ -1,6 +1,6 @@ { "name": "@virtuals-protocol/game-rss3-plugin", - "version": "0.1", + "version": "0.1.0", "description": "", "main": "./dist/index.js", "module": "./dist/index.mjs",