9
9
using Microsoft . Extensions . Hosting ;
10
10
using OpenAI . GPT3 . ObjectModels ;
11
11
using OpenAI . GPT3 . ObjectModels . RequestModels ;
12
- using Org . BouncyCastle . Asn1 . X509 ;
13
12
14
13
namespace GPT . CLI . Chat . Discord ;
15
14
@@ -34,12 +33,14 @@ public record ChannelOptions
34
33
}
35
34
36
35
36
+
37
37
private readonly DiscordSocketClient _client ;
38
38
private readonly IConfiguration _configuration ;
39
39
private readonly OpenAILogic _openAILogic ;
40
40
private readonly GPTParameters _defaultParameters ;
41
41
private readonly ConcurrentDictionary < ulong , ChannelState > _channelBots = new ( ) ;
42
42
43
+
43
44
public DiscordBot ( DiscordSocketClient client , IConfiguration configuration , OpenAILogic openAILogic , GPTParameters defaultParameters )
44
45
{
45
46
_client = client ;
@@ -63,6 +64,7 @@ public async Task StartAsync(CancellationToken cancellationToken)
63
64
// Load state from discordState.json
64
65
await LoadState ( ) ;
65
66
67
+ // Login and start
66
68
await _client . LoginAsync ( TokenType . Bot , token ) ;
67
69
await _client . StartAsync ( ) ;
68
70
@@ -73,15 +75,7 @@ public async Task StartAsync(CancellationToken cancellationToken)
73
75
74
76
75
77
// Message receiver is going to run in parallel
76
- _client . MessageReceived += message =>
77
- {
78
- #pragma warning disable CS4014
79
- return Task . Run ( async ( ) =>
80
- #pragma warning restore CS4014
81
- {
82
- await HandleMessageReceivedAsync ( message ) ;
83
- } , cancellationToken ) ;
84
- } ;
78
+ _client . MessageReceived += HandleMessageReceivedAsync ;
85
79
86
80
_client . MessageUpdated += async ( oldMessage , newMessage , channel ) =>
87
81
{
@@ -96,25 +90,17 @@ public async Task StartAsync(CancellationToken cancellationToken)
96
90
97
91
98
92
99
- _client . Ready += ( ) =>
93
+ _client . Ready += async ( ) =>
100
94
{
101
- Console . WriteLine ( "Client is ready!" ) ;
102
- return Task . CompletedTask ;
95
+ await Console . Out . WriteLineAsync ( "Client is ready!" ) ;
103
96
} ;
104
97
105
- _client . MessageCommandExecuted += ( command ) =>
98
+ _client . MessageCommandExecuted += async ( command ) =>
106
99
{
107
- Console . WriteLine ( $ "Command { command . CommandName } executed with result { command . Data . Message . Content } ") ;
108
- return Task . CompletedTask ;
100
+ await Console . Out . WriteLineAsync ( $ "Command { command . CommandName } executed with result { command . Data . Message . Content } ") ;
109
101
} ;
110
102
}
111
103
112
- private async Task AddStandardReactions ( IMessage message )
113
- {
114
- await message . AddReactionAsync ( new Emoji ( "📌" ) ) ;
115
- await message . AddReactionAsync ( new Emoji ( "🔄" ) ) ;
116
- }
117
-
118
104
private async Task HandleReactionAsync ( Cacheable < IUserMessage , ulong > userMessage , Cacheable < IMessageChannel , ulong > messageChannel , SocketReaction reaction )
119
105
{
120
106
if ( reaction . UserId == _client . CurrentUser . Id )
@@ -125,6 +111,7 @@ private async Task HandleReactionAsync(Cacheable<IUserMessage, ulong> userMessag
125
111
var message = await userMessage . GetOrDownloadAsync ( ) ;
126
112
var channel = await messageChannel . GetOrDownloadAsync ( ) ;
127
113
114
+
128
115
switch ( reaction . Emote . Name )
129
116
{
130
117
case "📌" :
@@ -135,28 +122,30 @@ private async Task HandleReactionAsync(Cacheable<IUserMessage, ulong> userMessag
135
122
var chatBot = channelState . Chat ;
136
123
chatBot . AddInstruction ( new ChatMessage ( StaticValues . ChatMessageRoles . User , message . Content ) ) ;
137
124
125
+
138
126
using var typingState = channel . EnterTypingState ( ) ;
139
127
await message . RemoveReactionAsync ( reaction . Emote , reaction . UserId ) ;
140
128
await message . ReplyAsync ( "Instruction added." ) ;
141
-
142
- Console . WriteLine (
143
- $ "{ reaction . User . Value . Username } reacted with an arrow up. Message promoted to instruction: { message . Content } ") ;
144
129
}
130
+
145
131
break ;
146
132
}
147
133
case "🔄" :
148
134
{
149
- // remove the emoji
150
- await message . RemoveReactionAsync ( reaction . Emote , reaction . UserId ) ;
135
+ // If the message is from the bot, ignore it. These aren't prompts.
136
+ if ( message . Author . Id == _client . CurrentUser . Id )
137
+ {
138
+ return ;
139
+ }
151
140
152
- await message . ReplyAsync ( "Using as a prompt." ) ;
141
+ // remove the emoji
142
+ await message . RemoveReactionAsync ( reaction . Emote , reaction . User . Value ) ;
153
143
154
144
// Replay the message as a new message
155
- #pragma warning disable CS4014
156
- Task . Run ( async ( ) => await HandleMessageReceivedAsync ( message , reaction . Emote ) ) ;
157
- #pragma warning restore CS4014
145
+ await HandleMessageReceivedAsync ( message as SocketMessage ) ;
158
146
break ;
159
147
}
148
+
160
149
}
161
150
}
162
151
@@ -222,7 +211,6 @@ private async Task<ChannelState> ReadAsync(ulong channelId, Stream stream)
222
211
try
223
212
{
224
213
var str = await new StreamReader ( stream ) . ReadToEndAsync ( ) ;
225
- await Console . Out . WriteLineAsync ( $ "State read for { channelId } : { str } ") ;
226
214
// Deserialize channelState from stream
227
215
var channelState = JsonSerializer . Deserialize < ChannelState > ( str ) ;
228
216
@@ -252,18 +240,16 @@ private async Task LogAsync(LogMessage log)
252
240
await Console . Out . WriteLineAsync ( log . ToString ( ) ) ;
253
241
}
254
242
255
- private async Task HandleMessageReceivedAsync ( IMessage message , IEmote emote = null )
243
+ private async Task HandleMessageReceivedAsync ( SocketMessage message )
256
244
{
257
- if ( message . Author . Id == _client . CurrentUser . Id && emote == null )
245
+ if ( message == null || message . Author . Id == _client . CurrentUser . Id )
258
246
return ;
259
247
260
248
// Handle the received message here
261
249
// ...
262
- if ( ! _channelBots . TryGetValue ( message . Channel . Id , out var channel ) )
263
- {
264
- channel = InitializeChannel ( message . Channel . Id ) ;
265
- }
266
- else if ( channel . Chat . State . PrimeDirectives . Count != _defaultPrimeDirective . Count || channel . Chat . State . PrimeDirectives [ 0 ] . Content != _defaultPrimeDirective [ 0 ] . Content )
250
+ var channel = _channelBots . GetOrAdd ( message . Channel . Id , InitializeChannel ) ;
251
+
252
+ if ( channel . Chat . State . PrimeDirectives . Count != _defaultPrimeDirective . Count || channel . Chat . State . PrimeDirectives [ 0 ] . Content != _defaultPrimeDirective [ 0 ] . Content )
267
253
{
268
254
channel . Chat . State . PrimeDirectives = PrimeDirective . ToList ( ) ;
269
255
}
@@ -274,74 +260,58 @@ private async Task HandleMessageReceivedAsync(IMessage message, IEmote emote = n
274
260
if ( message . Content . StartsWith ( "!ignore" ) )
275
261
return ;
276
262
277
- await message . AddReactionAsync ( new Emoji ( "🤔" ) ) ;
278
-
279
- await AddStandardReactions ( message ) ;
280
263
281
264
// Add this message as a chat log
282
265
await channel . Chat . AddMessage ( new ChatMessage ( StaticValues . ChatMessageRoles . User , $ "<{ message . Author . Username } > { message . Content } ") ) ;
283
266
284
267
if ( ! channel . Options . Muted )
285
268
{
286
- try
269
+ using var typingState = message . Channel . EnterTypingState ( ) ;
270
+ // Get the response from the bot
271
+ var responses = channel . Chat . GetResponseAsync ( ) ;
272
+ // Add the response as a chat
273
+
274
+ var sb = new StringBuilder ( ) ;
275
+ // Send the response to the channel
276
+ await foreach ( var response in responses )
287
277
{
288
-
289
- // Get the response from the bot
290
- var responses = channel . Chat . GetResponseAsync ( ) ;
291
- // Add the response as a chat
292
-
293
- using var typingState = message . Channel . EnterTypingState ( ) ;
294
-
295
- var sb = new StringBuilder ( ) ;
296
- // Send the response to the channel
297
- await foreach ( var response in responses )
278
+ if ( response . Successful )
298
279
{
299
- if ( response . Successful )
280
+ var content = response . Choices . FirstOrDefault ( ) ? . Message . Content ;
281
+ if ( content is not null )
300
282
{
301
- var content = response ? . Choices ? . FirstOrDefault ( ) ? . Message . Content ;
302
- if ( content is not null )
303
- {
304
- sb . Append ( content ) ;
305
- }
306
- }
307
- else
308
- {
309
- await Console . Out . WriteLineAsync (
310
- $ "Error code { response . Error ? . Code } : { response . Error ? . Message } ") ;
283
+ sb . Append ( content ) ;
311
284
}
312
285
}
313
-
314
- int chunkSize = 2000 ;
315
- int currentPosition = 0 ;
316
-
317
- while ( currentPosition < sb . Length )
286
+ else
318
287
{
319
- var size = Math . Min ( chunkSize , sb . Length - currentPosition ) ;
320
- var chunk = sb . ToString ( currentPosition , size ) ;
321
- currentPosition += size ;
288
+ await Console . Out . WriteLineAsync (
289
+ $ "Error code { response . Error ? . Code } : { response . Error ? . Message } ") ;
290
+ }
291
+ }
322
292
323
- var responseMessage = new ChatMessage ( StaticValues . ChatMessageRoles . Assistant , chunk ) ;
293
+ int chunkSize = 2000 ;
294
+ int currentPosition = 0 ;
324
295
325
- await channel . Chat . AddMessage ( responseMessage ) ;
326
- // Convert message to SocketMessage
327
- IMessage newMessage ;
296
+ while ( currentPosition < sb . Length )
297
+ {
298
+ var size = Math . Min ( chunkSize , sb . Length - currentPosition ) ;
299
+ var chunk = sb . ToString ( currentPosition , size ) ;
300
+ currentPosition += size ;
328
301
329
- if ( message is IUserMessage userMessage )
330
- {
331
- newMessage = await userMessage . ReplyAsync ( responseMessage . Content ) ;
332
- }
333
- else
334
- {
302
+ var responseMessage = new ChatMessage ( StaticValues . ChatMessageRoles . Assistant , chunk ) ;
335
303
336
- newMessage = await message . Channel . SendMessageAsync ( responseMessage . Content ) ;
337
- }
304
+ await channel . Chat . AddMessage ( responseMessage ) ;
305
+ // Convert message to SocketMessage
338
306
339
- await AddStandardReactions ( newMessage ) ;
307
+ if ( message is IUserMessage userMessage )
308
+ {
309
+ _ = await userMessage . ReplyAsync ( responseMessage . Content ) ;
310
+ }
311
+ else
312
+ {
313
+ _ = await message . Channel . SendMessageAsync ( responseMessage . Content ) ;
340
314
}
341
- }
342
- finally
343
- {
344
- await message . RemoveReactionAsync ( new Emoji ( "🤔" ) , _client . CurrentUser ) ;
345
315
}
346
316
}
347
317
@@ -354,7 +324,7 @@ await Console.Out.WriteLineAsync(
354
324
( StaticValues . ChatMessageRoles . System ,
355
325
"This is the Prime Directive: This is a chat bot running in [GPT-CLI](https://github.com/kainazzzo/GPT-CLI). Answer questions and" +
356
326
" provide responses in Discord message formatting. Encourage users to add instructions with /gptcli or by using the :up_arrow:" +
357
- " emoji reaction on any message. Instructions are like 'sticky' chat messages that provide upfront context to the bot." )
327
+ " emoji reaction on any message. Instructions are like 'sticky' chat messages that provide upfront context to the bot. The the 📌 emoji reaction is for pinning a message to instructions. The 🔄 emoji reaction is for replaying a message as a new prompt. " )
358
328
} ;
359
329
360
330
private IEnumerable < ChatMessage > PrimeDirective => _defaultPrimeDirective ;
0 commit comments