@@ -28,7 +28,37 @@ public GptImageCommand(CommandContext ctx, string prompt)
2828 public string Prompt { get ; }
2929}
3030
31- public class GptImageHandler : IRequestHandler < GptImageCommand >
31+ public class GptImageAvatarCommand : CommandRequest
32+ {
33+ public GptImageAvatarCommand ( CommandContext ctx , DiscordUser user , string prompt )
34+ : base ( ctx )
35+ {
36+ Prompt = prompt ;
37+ User = user ;
38+ }
39+
40+ public string Prompt { get ; }
41+
42+ public DiscordUser User { get ; }
43+ }
44+
45+ public class GptImageEmoteCommand : CommandRequest
46+ {
47+ public GptImageEmoteCommand ( CommandContext ctx , DiscordEmoji emoji , string prompt )
48+ : base ( ctx )
49+ {
50+ Prompt = prompt ;
51+ Emoji = emoji ;
52+ }
53+
54+ public string Prompt { get ; }
55+
56+ public DiscordEmoji Emoji { get ; }
57+ }
58+
59+ public class GptImageHandler : IRequestHandler < GptImageCommand > ,
60+ IRequestHandler < GptImageAvatarCommand > ,
61+ IRequestHandler < GptImageEmoteCommand >
3262{
3363 private const string GptImageModel = "gpt-image-1" ;
3464 private const string Moderation = "low" ;
@@ -38,17 +68,20 @@ public class GptImageHandler : IRequestHandler<GptImageCommand>
3868
3969 private readonly GptImagesHttpClient _gptImagesHttpClient ;
4070 private readonly ImageService _imageService ;
71+ private readonly DiscordResolver _discordResolver ;
4172 private readonly ILogger < GptImageHandler > _logger ;
4273
4374 private readonly string [ ] _supportedImageFormats = [ "png" , "jpg" , "webp" ] ;
4475
4576 public GptImageHandler (
4677 GptImagesHttpClient gptImagesHttpClient ,
4778 ImageService imageService ,
79+ DiscordResolver discordResolver ,
4880 ILogger < GptImageHandler > logger )
4981 {
5082 _gptImagesHttpClient = gptImagesHttpClient ;
5183 _imageService = imageService ;
84+ _discordResolver = discordResolver ;
5285 _logger = logger ;
5386 }
5487
@@ -66,6 +99,7 @@ public async Task Handle(GptImageCommand request, CancellationToken cancellation
6699 string ? imageUrl = await discordMessage . GetImageUrlFromMessage ( _logger ) ;
67100
68101 CreateImageResponse response ;
102+ var userId = request . Ctx . User . Id . ToString ( ) ;
69103
70104 if ( imageUrl == null )
71105 {
@@ -75,40 +109,68 @@ public async Task Handle(GptImageCommand request, CancellationToken cancellation
75109 Model = GptImageModel ,
76110 Quality = Quality ,
77111 Moderation = Moderation ,
78- User = request . Ctx . User . Id . ToString ( ) ,
112+ User = userId ,
79113 } ;
80114
81115 response = await _gptImagesHttpClient . CreateImage ( imageRequest , cancellationToken ) ;
82116 }
83117 else
84118 {
85- Image editImage = await _imageService . DownloadImage ( imageUrl ) ;
119+ response = await CreateImageEdit ( imageUrl , prompt , userId , cancellationToken ) ;
120+ }
86121
87- Image imageInSupportedFormat =
88- await ImageService . EnsureSupportedImageFormatOrPng ( editImage , _supportedImageFormats ) ;
122+ if ( response . Data == null || ! response . Data . Any ( ) ) throw new Exception ( "Empty result" ) ;
89123
90- Image resizedImage = await ImageService . EnsureMaxImageFileSize (
91- imageInSupportedFormat ,
92- MaxImageSizeInMb ) ;
124+ ImageResponseData imageData = response . Data . First ( ) ;
93125
94- var tempFileName = $ " { Guid . NewGuid ( ) } .png" ;
126+ Image image = ImageService . ConvertBase64ToImage ( imageData . B64Json ) ;
95127
96- ImageStreamResult inputImageStream = await ImageService . GetImageStream ( resizedImage ) ;
128+ ImageStreamResult imageStream = await ImageService . GetImageStream ( image ) ;
97129
98- var editImageRequest = new CreateImageEditRequest
99- {
100- Prompt = prompt ,
101- Image = inputImageStream . MemoryStream . ToArray ( ) ,
102- Model = GptImageModel ,
103- Quality = Quality ,
104- User = request . Ctx . User . Id . ToString ( ) ,
105- } ;
130+ string fileName = prompt . ToSafeFilename ( imageStream . FileExtension ) ;
106131
107- response = await _gptImagesHttpClient . CreateImageEdit (
108- editImageRequest ,
109- tempFileName ,
110- cancellationToken ) ;
111- }
132+ string truncatedPrompt = prompt . Length > DiscordConstants . MaxEmbedTitleLength
133+ ? $ "{ prompt [ ..( DiscordConstants . MaxEmbedTitleLength - 3 ) ] } ..."
134+ : prompt ;
135+
136+ DiscordEmbedBuilder eb = new DiscordEmbedBuilder ( )
137+ . WithTitle ( truncatedPrompt )
138+ . WithImageUrl ( $ "attachment://{ fileName } ") ;
139+
140+ DiscordMessageBuilder mb = new DiscordMessageBuilder ( )
141+ . AddFile ( fileName , imageStream . MemoryStream )
142+ . AddEmbed ( eb )
143+ . WithContent ( $ "There you go { request . Ctx . Member ? . Mention ?? "Unknown user" } ") ;
144+
145+ await request . Ctx . RespondAsync ( mb ) ;
146+ }
147+ finally
148+ {
149+ await ackMessage . DeleteAsync ( ) ;
150+ }
151+ }
152+
153+ public async Task Handle ( GptImageAvatarCommand request , CancellationToken cancellationToken )
154+ {
155+ CommandContext ctx = request . Ctx ;
156+ DiscordMessage discordMessage = ctx . Message ;
157+ DiscordUser user = request . User ;
158+ DiscordGuild guild = ctx . Guild ;
159+
160+ DiscordMessage ackMessage = await request . Ctx . RespondAsync ( "Working on it" ) ;
161+
162+ try
163+ {
164+ string prompt = discordMessage . SubstituteMentions ( request . Prompt ) ;
165+
166+ DiscordMember userAsMember = await _discordResolver . ResolveGuildMember ( guild , user . Id ) ??
167+ throw new Exception ( "Invalid user" ) ;
168+
169+ string imageUrl = userAsMember . GuildAvatarUrl ?? userAsMember . AvatarUrl ;
170+
171+ var userId = request . Ctx . User . Id . ToString ( ) ;
172+
173+ CreateImageResponse response = await CreateImageEdit ( imageUrl , prompt , userId , cancellationToken ) ;
112174
113175 if ( response . Data == null || ! response . Data . Any ( ) ) throw new Exception ( "Empty result" ) ;
114176
@@ -140,4 +202,90 @@ public async Task Handle(GptImageCommand request, CancellationToken cancellation
140202 await ackMessage . DeleteAsync ( ) ;
141203 }
142204 }
205+
206+ public async Task Handle ( GptImageEmoteCommand request , CancellationToken cancellationToken )
207+ {
208+ CommandContext ctx = request . Ctx ;
209+ DiscordMessage discordMessage = ctx . Message ;
210+
211+ DiscordEmoji emoji = request . Emoji ;
212+
213+ DiscordMessage ackMessage = await request . Ctx . RespondAsync ( "Working on it" ) ;
214+
215+ try
216+ {
217+ string prompt = discordMessage . SubstituteMentions ( request . Prompt ) ;
218+
219+ string imageUrl = emoji . GetEmojiImageUrl ( ) ;
220+
221+ var userId = request . Ctx . User . Id . ToString ( ) ;
222+
223+ CreateImageResponse response = await CreateImageEdit ( imageUrl , prompt , userId , cancellationToken ) ;
224+
225+ if ( response . Data == null || ! response . Data . Any ( ) ) throw new Exception ( "Empty result" ) ;
226+
227+ ImageResponseData imageData = response . Data . First ( ) ;
228+
229+ Image image = ImageService . ConvertBase64ToImage ( imageData . B64Json ) ;
230+
231+ ImageStreamResult imageStream = await ImageService . GetImageStream ( image ) ;
232+
233+ string fileName = prompt . ToSafeFilename ( imageStream . FileExtension ) ;
234+
235+ string truncatedPrompt = prompt . Length > DiscordConstants . MaxEmbedTitleLength
236+ ? $ "{ prompt [ ..( DiscordConstants . MaxEmbedTitleLength - 3 ) ] } ..."
237+ : prompt ;
238+
239+ DiscordEmbedBuilder eb = new DiscordEmbedBuilder ( )
240+ . WithTitle ( truncatedPrompt )
241+ . WithImageUrl ( $ "attachment://{ fileName } ") ;
242+
243+ DiscordMessageBuilder mb = new DiscordMessageBuilder ( )
244+ . AddFile ( fileName , imageStream . MemoryStream )
245+ . AddEmbed ( eb )
246+ . WithContent ( $ "There you go { request . Ctx . Member ? . Mention ?? "Unknown user" } ") ;
247+
248+ await request . Ctx . RespondAsync ( mb ) ;
249+ }
250+ finally
251+ {
252+ await ackMessage . DeleteAsync ( ) ;
253+ }
254+ }
255+
256+ private async Task < CreateImageResponse > CreateImageEdit (
257+ string imageUrl ,
258+ string prompt ,
259+ string userId ,
260+ CancellationToken cancellationToken )
261+ {
262+ Image editImage = await _imageService . DownloadImage ( imageUrl ) ;
263+
264+ Image imageInSupportedFormat =
265+ await ImageService . EnsureSupportedImageFormatOrPng ( editImage , _supportedImageFormats ) ;
266+
267+ Image resizedImage = await ImageService . EnsureMaxImageFileSize (
268+ imageInSupportedFormat ,
269+ MaxImageSizeInMb ) ;
270+
271+ var tempFileName = $ "{ Guid . NewGuid ( ) } .png";
272+
273+ ImageStreamResult inputImageStream = await ImageService . GetImageStream ( resizedImage ) ;
274+
275+ var editImageRequest = new CreateImageEditRequest
276+ {
277+ Prompt = prompt ,
278+ Image = inputImageStream . MemoryStream . ToArray ( ) ,
279+ Model = GptImageModel ,
280+ Quality = Quality ,
281+ User = userId ,
282+ } ;
283+
284+ CreateImageResponse response = await _gptImagesHttpClient . CreateImageEdit (
285+ editImageRequest ,
286+ tempFileName ,
287+ cancellationToken ) ;
288+
289+ return response ;
290+ }
143291}
0 commit comments