Skip to content

Commit

Permalink
Merge branch 'main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
fifahuihua authored Jan 27, 2025
2 parents 5ee66a1 + 2c667b4 commit acdc66e
Show file tree
Hide file tree
Showing 40 changed files with 22,540 additions and 12 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
dist/
node_modules/
.env
.env
*.lock
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@ Function
- Used to control which functions are called by the workers, based on each worker's low-level plan
- This can be any executable

## Core Features
## Usage

Request for a GAME API key in the Game Console. https://console.game.virtuals.io/
If you have any trouble, contact Virtuals support or DevRel team members via Discord or Telegram.

### 1. Functions and Executables

Expand Down
3 changes: 3 additions & 0 deletions plugins/discordPlugin/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
dist/
node_modules/
coverage/
2 changes: 2 additions & 0 deletions plugins/discordPlugin/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
src
tsconfig.json
103 changes: 103 additions & 0 deletions plugins/discordPlugin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# Discord Plugin for Virtuals Game

This plugin allows you to integrate Discord functionalities into your Virtuals Game. With this plugin, you can send message, pin message, add reaction and delete message in Discord.

## Installation

To install the plugin, use npm or yarn:

```bash
npm install @virtuals-protocol/game-discord-plugin
```

or

```bash
yarn add @virtuals-protocol/game-discord-plugin
```

## Usage

### Importing the Plugin

First, import the `DiscordPlugin` class from the plugin:

```typescript
import DiscordPlugin from "@virtuals-protocol/game-discord-plugin";
```

### Creating a Worker

Create a worker with the necessary DiscordPlugin credentials:

```typescript
const discordPlugin = new DiscordPlugin({
credentials: {
botToken: "<BOT TOKEN>"
},
});
```

### 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: "Discord Bot",
goal: "increase engagement and grow follower count",
description: "A bot that can reply message, add reaction, pin message and delete message in Discord.",
workers: [
discordPlugin.getWorker({
// Define the functions that the worker can perform, by default it will use the all functions defined in the plugin
functions: [
discordPlugin.sendMessageFunction,
discordPlugin.addReactionFunction,
discordPlugin.pinMessageFunction,
discordPlugin.deleteMessageFunction,
],
}),
],
});
```

### Running the Agent

Initialize and run the agent:

```typescript
(async () => {
await agent.init();

while (true) {
await agent.step({
verbose: true,
});
}
})();
```

## Available Functions

The `DiscordPlugin` provides several functions that can be used by the agent:

- `sendMessageFunction`: Send a message in discord
- `addReactionFunction`: Add a reaction in discord message.
- `pinMessageFunction`: Pin a message in discord message.
- `deleteMessageFunction`: Delete a message in discord.

## Event Handlers
The plugin also supports custom handlers for the following Discord events:
### Handling Incoming Messages
To handle incoming messages, use the `onMessage` method to listen on:
```typescript
discordPlugin.onMessage((msg) => {
console.log("Received message:", msg);
});
```

## License

This project is licensed under the MIT License.
173 changes: 173 additions & 0 deletions plugins/discordPlugin/__test__/DiscordPlugin.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
import DiscordPlugin from '../src/discordPlugin';
import { Client } from 'discord.js';
import { ExecutableGameFunctionStatus } from "@virtuals-protocol/game";

// Mock discord.js
jest.mock('discord.js', () => ({
Client: jest.fn().mockImplementation(() => ({
login: jest.fn().mockImplementation(() => ({
then: jest.fn((successCallback) => {
successCallback();
return {
catch: jest.fn()
};
}),
catch: jest.fn()
})),
on: jest.fn().mockImplementation(() => {}),
once: jest.fn().mockImplementation(() => {}),
// Mock the channels property
channels: {
fetch: jest.fn().mockResolvedValue({
isTextBased: jest.fn().mockReturnValue(true),
send: jest.fn().mockResolvedValue({}),
// Mock the messages property
messages: {
fetch: jest.fn().mockResolvedValue({
react: jest.fn(),
pin: jest.fn(),
delete: jest.fn(),
send: jest.fn()
})
}
})
}
})),
GatewayIntentBits: {
Guilds: 'Guilds',
GuildMessages: 'GuildMessages',
MessageContent: 'MessageContent',
GuildMessageReactions: 'GuildMessageReactions',
DirectMessages: 'DirectMessages',
DirectMessageReactions: 'DirectMessageReactions',
DirectMessageTyping: 'DirectMessageTyping'
}
}));

describe('DiscordPlugin', () => {
const discordPlugin = new DiscordPlugin({
credentials: { botToken: 'mock-token' }
});

describe('Constructor', () => {
it('should initialize with default values', () => {
expect(discordPlugin['id']).toBe('discord_worker');
expect(discordPlugin['name']).toBe('Discord Worker');
expect(discordPlugin['description']).toBeDefined();
});

it('should create a Discord client with correct intents', () => {
expect(Client).toHaveBeenCalledWith(expect.objectContaining({
intents: expect.any(Array)
}));
});
});

describe('getWorker', () => {
it('should return a GameWorker with default functions', () => {
const worker = discordPlugin.getWorker();

expect(worker).toBeDefined();
expect(worker.functions).toHaveLength(4);
expect(worker.functions.map(f => f.name)).toEqual([
'send_message',
'add_reaction',
'pin_message',
'delete_message'
]);
});
});

describe('Game Functions', () => {

describe('sendMessageFunction', () => {
it('should send message successfully when valid arguments passed in', async () => {
const logger = jest.fn();
const result = await discordPlugin.sendMessageFunction.executable(
{ channel_id: '123', content: 'Send this message' },
logger
);

expect(result.status).toBe(ExecutableGameFunctionStatus.Done);
expect(result.feedback).toContain('Message sent successfully.');
});

it('should fail if channel_id or content is missing', async () => {
const result = await discordPlugin.sendMessageFunction.executable(
{ channel_id: '', content: '' },
jest.fn()
);

expect(result.status).toBe(ExecutableGameFunctionStatus.Failed);
expect(result.feedback).toContain('Both channel_id and content are required');
});
});

describe('addReactionFunction', () => {
it('should add reaction successfully when required argument is passed', async () => {
const result = await discordPlugin.addReactionFunction.executable(
{ channel_id: '123', message_id: '321', emoji: '😊' },
jest.fn()
);

expect(result.status).toBe(ExecutableGameFunctionStatus.Done);
expect(result.feedback).toContain('Reaction added successfully.');
});

it('should fail if any required argument is missing', async () => {
const result = await discordPlugin.addReactionFunction.executable(
{ channel_id: '', message_id: '', emoji: '' },
jest.fn()
);

expect(result.status).toBe(ExecutableGameFunctionStatus.Failed);
expect(result.feedback).toContain('channel_id, message_id, and emoji are required');
});
});

describe('pinMessageFunction', () => {
it('should success pin message if required arguments is passed', async () => {
const result = await discordPlugin.pinMessageFunction.executable(
{ channel_id: '123', message_id: '321' },
jest.fn()
);

expect(result.status).toBe(ExecutableGameFunctionStatus.Done);
expect(result.feedback).toContain('Message pinned successfully.');
});

it('should fail if channel_id or message_id is missing', async () => {
const result = await discordPlugin.pinMessageFunction.executable(
{ channel_id: '', message_id: '' },
jest.fn()
);

expect(result.status).toBe(ExecutableGameFunctionStatus.Failed);
expect(result.feedback).toContain('Both channel_id and message_id are required');
});
});

describe('deleteMessageFunction', () => {
it('should delete message successfully', async () => {
const result = await discordPlugin.deleteMessageFunction.executable(
{ channel_id: '123', message_id: '321' },
jest.fn()
);

expect(result.status).toBe(ExecutableGameFunctionStatus.Done);
expect(result.feedback).toContain('Message deleted successfully.');
});

it('should fail if channel_id or message_id is missing', async () => {
const result = await discordPlugin.deleteMessageFunction.executable(
{ channel_id: '', message_id: '' },
jest.fn()
);

expect(result.status).toBe(ExecutableGameFunctionStatus.Failed);
expect(result.feedback).toContain('Both channel_id and message_id are required');
});
});

});
});
4 changes: 4 additions & 0 deletions plugins/discordPlugin/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
};
Loading

0 comments on commit acdc66e

Please sign in to comment.