|
11 | 11 | using Fritz.StreamLib.Core;
|
12 | 12 | using Microsoft.Extensions.Configuration;
|
13 | 13 | using Newtonsoft.Json;
|
| 14 | +using Newtonsoft.Json.Linq; |
14 | 15 |
|
15 | 16 | namespace Fritz.Chatbot.Commands
|
16 | 17 | {
|
17 |
| - public class ImageDescriptorCommand : IExtendedCommand |
18 |
| - { |
19 |
| - public string Name => "Image"; |
20 |
| - public string Description => "Inspect images and report to the chat room what they contain using Vision API"; |
21 |
| - public int Order => 10; |
22 |
| - public bool Final => false; |
23 |
| - |
24 |
| - private readonly string _AzureUrl; |
25 |
| - private readonly string _AzureApiKey; |
26 |
| - private string ImageUrl; |
27 |
| - private string v1; |
28 |
| - private string v2; |
29 |
| - |
30 |
| - public TimeSpan? Cooldown => null; |
31 |
| - |
32 |
| - private static readonly Regex _UrlCheck = new Regex(@"http(s)?:?(\/\/[^""']*\.(?:png|jpg|jpeg|gif))", RegexOptions.IgnoreCase | RegexOptions.Compiled); |
33 |
| - private readonly IHttpClientFactory _ClientFactory; |
34 |
| - |
35 |
| - public ImageDescriptorCommand(IConfiguration config, IHttpClientFactory clientFactory) : this(config["FritzBot:VisionApiBaseUrl"], config["FritzBot:VisionApiKey"]) |
| 18 | + public class ImageDescriptorCommand : IExtendedCommand |
36 | 19 | {
|
37 |
| - _ClientFactory = clientFactory; |
38 |
| - } |
| 20 | + public string Name => "Image"; |
| 21 | + public string Description => "Inspect images and report to the chat room what they contain using Vision API"; |
| 22 | + public int Order => 10; |
| 23 | + public bool Final => true; |
| 24 | + |
| 25 | + private readonly string _AzureUrl; |
| 26 | + private readonly string _AzureApiKey; |
| 27 | + private string ImageUrl; |
| 28 | + private (string owner, string) _InstagramInfo; |
| 29 | + private ImageProviders Provider = ImageProviders.None; |
| 30 | + private string v1; |
| 31 | + private string v2; |
| 32 | + |
| 33 | + public TimeSpan? Cooldown => null; |
| 34 | + |
| 35 | + private static readonly Regex _UrlCheck = new Regex(@"http(s)?:?(\/\/[^""']*\.(?:png|jpg|jpeg|gif))", RegexOptions.IgnoreCase | RegexOptions.Compiled); |
| 36 | + internal static readonly Regex _InstagramCheck = new Regex(@"(?<InstagramUrl>https?:\/\/(www.)?instagram.com\/p\/[^\/]+\/?)", RegexOptions.IgnoreCase | RegexOptions.Compiled); |
| 37 | + private readonly IHttpClientFactory _ClientFactory; |
| 38 | + |
| 39 | + public ImageDescriptorCommand(IConfiguration config, IHttpClientFactory clientFactory) : this(config["FritzBot:VisionApiBaseUrl"], config["FritzBot:VisionApiKey"]) |
| 40 | + { |
| 41 | + _ClientFactory = clientFactory; |
| 42 | + } |
39 | 43 |
|
40 |
| - public ImageDescriptorCommand(string azureUrl, string azureKey) |
41 |
| - { |
42 |
| - _AzureUrl = azureUrl; |
43 |
| - _AzureApiKey = azureKey; |
44 |
| - } |
| 44 | + public ImageDescriptorCommand(string azureUrl, string azureKey) |
| 45 | + { |
| 46 | + _AzureUrl = azureUrl; |
| 47 | + _AzureApiKey = azureKey; |
| 48 | + } |
45 | 49 |
|
46 |
| - public bool CanExecute(string userName, string fullCommandText) |
47 |
| - { |
| 50 | + public bool CanExecute(string userName, string fullCommandText) |
| 51 | + { |
48 | 52 |
|
49 |
| - // Match the regular expression pattern against a text string. |
50 |
| - var imageCheck = _UrlCheck.Match(fullCommandText); |
51 |
| - if (imageCheck.Captures.Count == 0) |
52 |
| - return false; |
53 |
| - this.ImageUrl = imageCheck.Captures[0].Value; |
| 53 | + // TODO: Check the cooldown on the image descriptor |
54 | 54 |
|
55 |
| - return (!ImageUrl.Contains('#') && imageCheck.Captures.Count > 0) && ValidImageType(ImageUrl).GetAwaiter().GetResult(); |
| 55 | + // Match the regular expression pattern against a text string. |
| 56 | + var imageCheck = _UrlCheck.Match(fullCommandText); |
| 57 | + var instagramCheck = _InstagramCheck.Match(fullCommandText); |
| 58 | + if (imageCheck.Captures.Count == 0 && !instagramCheck.Success) |
| 59 | + return false; |
56 | 60 |
|
57 |
| - async Task<bool> ValidImageType(string url) { |
| 61 | + if (imageCheck.Captures.Count != 0) this.ImageUrl = imageCheck.Captures[0].Value; |
| 62 | + if (instagramCheck.Captures.Count != 0) |
| 63 | + { |
| 64 | + this.Provider = ImageProviders.Instagram; |
| 65 | + this.ImageUrl = HandleInstagram(instagramCheck).GetAwaiter().GetResult(); |
| 66 | + return !string.IsNullOrEmpty(this.ImageUrl); |
| 67 | + } |
58 | 68 |
|
59 |
| - HttpResponseMessage response; |
| 69 | + return (!ImageUrl.Contains('#') && imageCheck.Captures.Count > 0) && ValidImageType(ImageUrl).GetAwaiter().GetResult(); |
60 | 70 |
|
61 |
| - using (var client = _ClientFactory.CreateClient("ImageDescriptor")) |
| 71 | + async Task<bool> ValidImageType(string url) |
62 | 72 | {
|
63 |
| - try |
| 73 | + |
| 74 | + HttpResponseMessage response; |
| 75 | + |
| 76 | + using (var client = _ClientFactory.CreateClient("ImageDescriptor")) |
64 | 77 | {
|
65 |
| - var request = new HttpRequestMessage |
| 78 | + try |
66 | 79 | {
|
67 |
| - Method = HttpMethod.Head, |
68 |
| - RequestUri = new Uri(url) |
69 |
| - }; |
70 |
| - response = await client.SendAsync(request, cancellationToken: CancellationToken.None); |
71 |
| - response.EnsureSuccessStatusCode(); |
72 |
| - } catch |
73 |
| - { |
74 |
| - return false; |
75 |
| - } |
| 80 | + var request = new HttpRequestMessage |
| 81 | + { |
| 82 | + Method = HttpMethod.Head, |
| 83 | + RequestUri = new Uri(url) |
| 84 | + }; |
| 85 | + response = await client.SendAsync(request, cancellationToken: CancellationToken.None); |
| 86 | + response.EnsureSuccessStatusCode(); |
| 87 | + } |
| 88 | + catch |
| 89 | + { |
| 90 | + return false; |
| 91 | + } |
| 92 | + |
| 93 | + return (response.Content.Headers.ContentType.MediaType.ToLowerInvariant().StartsWith("image/")); |
76 | 94 |
|
77 |
| - return (response.Content.Headers.ContentType.MediaType.ToLowerInvariant().StartsWith("image/")); |
| 95 | + } |
78 | 96 |
|
79 | 97 | }
|
80 | 98 |
|
81 | 99 | }
|
82 | 100 |
|
83 |
| - } |
84 | 101 |
|
85 |
| - /// param name="fullCommandText" (this is the URL of the image we already found) |
86 |
| - public async Task Execute(IChatService chatService, string userName, string fullCommandText) |
87 |
| - { |
| 102 | + /// param name="fullCommandText" (this is the URL of the image we already found) |
| 103 | + public async Task Execute(IChatService chatService, string userName, string fullCommandText) |
| 104 | + { |
| 105 | + |
| 106 | + // Cheer 100 themichaeljolley 01/3/19 |
| 107 | + // Cheer 300 electrichavoc 01/3/19 |
| 108 | + // Cheer 300 devlead 01/3/19 |
| 109 | + // Cheer 100 brandonsatrom 01/3/19 |
| 110 | + // Cheer 642 cpayette 01/3/19 |
| 111 | + // Cheer 500 robertables 01/3/19 |
| 112 | + // Cheer 100 johanb 01/3/19 |
| 113 | + // Cheer 1000 bobtabor 01/3/19 |
| 114 | + |
| 115 | + var result = string.Empty; |
| 116 | + |
| 117 | + // TODO: Pull from ASP.NET Core Dependency Injection |
| 118 | + using (var client = _ClientFactory.CreateClient("ImageDescriptor")) |
| 119 | + { |
| 120 | + client.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", _AzureApiKey); |
88 | 121 |
|
89 |
| - // Cheer 100 themichaeljolley 01/3/19 |
90 |
| - // Cheer 300 electrichavoc 01/3/19 |
91 |
| - // Cheer 300 devlead 01/3/19 |
92 |
| - // Cheer 100 brandonsatrom 01/3/19 |
93 |
| - // Cheer 642 cpayette 01/3/19 |
94 |
| - // Cheer 500 robertables 01/3/19 |
95 |
| - // Cheer 100 johanb 01/3/19 |
96 |
| - // Cheer 1000 bobtabor 01/3/19 |
| 122 | + var requestParameters = "visualFeatures=Categories,Description,Color,Adult&language=en"; |
| 123 | + var uri = _AzureUrl + "?" + requestParameters; |
97 | 124 |
|
98 |
| - var result = string.Empty; |
| 125 | + var body = JsonConvert.SerializeObject(new { url = ImageUrl }); |
| 126 | + var content = new StringContent(body, Encoding.UTF8, "application/json"); |
99 | 127 |
|
100 |
| - // TODO: Pull from ASP.NET Core Dependency Injection |
101 |
| - using (var client = _ClientFactory.CreateClient("ImageDescriptor")) |
102 |
| - { |
103 |
| - client.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", _AzureApiKey); |
| 128 | + var apiResponse = await client.PostAsync(uri, content); |
104 | 129 |
|
105 |
| - var requestParameters = "visualFeatures=Categories,Description,Color,Adult&language=en"; |
106 |
| - var uri = _AzureUrl + "?" + requestParameters; |
| 130 | + try |
| 131 | + { |
| 132 | + apiResponse.EnsureSuccessStatusCode(); |
| 133 | + } |
| 134 | + catch (Exception) |
| 135 | + { |
| 136 | + await chatService.SendMessageAsync($"Unable to inspect the image from {userName}"); |
| 137 | + return; |
| 138 | + } |
| 139 | + result = await apiResponse.Content.ReadAsStringAsync(); |
| 140 | + apiResponse.Dispose(); |
| 141 | + } |
107 | 142 |
|
108 |
| - var body = JsonConvert.SerializeObject(new { url = ImageUrl }); |
109 |
| - var content = new StringContent(body, Encoding.UTF8, "application/json"); |
| 143 | + var visionDescription = JsonConvert.DeserializeObject<VisionDescription>(result); |
110 | 144 |
|
111 |
| - var apiResponse = await client.PostAsync(uri, content); |
| 145 | + if (visionDescription.adult.isAdultContent && visionDescription.adult.adultScore > 0.85F) |
| 146 | + { |
| 147 | + await chatService.SendMessageAsync($"Hey {userName} - we don't like adult content here!"); |
| 148 | + // TODO: Timeout / Ban user |
| 149 | + return; |
| 150 | + } |
112 | 151 |
|
113 |
| - try |
| 152 | + if (visionDescription.adult.isRacyContent) |
114 | 153 | {
|
115 |
| - apiResponse.EnsureSuccessStatusCode(); |
| 154 | + await chatService.SendMessageAsync($"Hey {userName} - that's too racy ({visionDescription.adult.racyScore,0:P2}) for our chat room!"); |
| 155 | + // TODO: Timeout user |
| 156 | + return; |
116 | 157 | }
|
117 |
| - catch (Exception) |
| 158 | + |
| 159 | + if (visionDescription.description.captions.Length == 0 && visionDescription.categories.Length > 0) |
118 | 160 | {
|
119 |
| - await chatService.SendMessageAsync($"Unable to inspect the image from {userName}"); |
| 161 | + await chatService.SendMessageAsync($"No caption for the image submitted by {userName}, but it is: '{string.Join(',', visionDescription.categories.Select(c => c.name))}'"); |
120 | 162 | return;
|
121 | 163 | }
|
122 |
| - result = await apiResponse.Content.ReadAsStringAsync(); |
123 |
| - apiResponse.Dispose(); |
124 |
| - } |
125 | 164 |
|
126 |
| - var visionDescription = JsonConvert.DeserializeObject<VisionDescription>(result); |
| 165 | + var description = string.Empty; |
| 166 | + if (Provider == ImageProviders.Instagram) |
| 167 | + { |
| 168 | + description = $"{userName} Instagram({_InstagramInfo.owner}) ({visionDescription.description.captions[0].confidence,0:P2}): {visionDescription.description.captions[0].text}"; |
| 169 | + } else |
| 170 | + { |
| 171 | + description = $"{userName} Photo ({visionDescription.description.captions[0].confidence,0:P2}): {visionDescription.description.captions[0].text}"; |
| 172 | + } |
| 173 | + |
| 174 | + await chatService.SendMessageAsync(description); |
| 175 | + |
| 176 | + } |
| 177 | + |
| 178 | + |
| 179 | + private async Task<string> HandleInstagram(Match instagramCheck) |
| 180 | + { |
| 181 | + |
| 182 | + // Cheer 200 phrakberg 03/3/19 |
| 183 | + |
| 184 | + var url = instagramCheck.Groups["InstagramUrl"].Value + "?__a=1"; |
| 185 | + var imageUrl = string.Empty; |
| 186 | + using (var client = _ClientFactory.CreateClient("ImageDescriptor")) |
| 187 | + { |
127 | 188 |
|
128 |
| - if (visionDescription.adult.isAdultContent && visionDescription.adult.adultScore > 0.85F) |
129 |
| - { |
130 |
| - await chatService.SendMessageAsync($"Hey {userName} - we don't like adult content here!"); |
131 |
| - // TODO: Timeout / Ban user |
132 |
| - return; |
133 |
| - } |
| 189 | + var json = await client.GetStringAsync(url); |
| 190 | + var theResponse = JObject.Parse(json); |
134 | 191 |
|
135 |
| - if (visionDescription.adult.isRacyContent) |
136 |
| - { |
137 |
| - await chatService.SendMessageAsync($"Hey {userName} - that's too racy ({visionDescription.adult.racyScore,0:P2}) for our chat room!"); |
138 |
| - // TODO: Timeout user |
139 |
| - return; |
140 |
| - } |
| 192 | + imageUrl = theResponse["graphql"]["shortcode_media"]["display_url"].Value<string>(); |
| 193 | + _InstagramInfo = (theResponse["graphql"]["shortcode_media"]["owner"]["full_name"].Value<string>(), |
| 194 | + ""); |
141 | 195 |
|
142 |
| - if (visionDescription.description.captions.Length == 0 && visionDescription.categories.Length > 0) |
143 |
| - { |
144 |
| - await chatService.SendMessageAsync($"No caption for the image submitted by {userName}, but it is: '{string.Join(',', visionDescription.categories.Select(c => c.name))}'"); |
145 |
| - return; |
146 |
| - } |
| 196 | + } |
147 | 197 |
|
148 |
| - var description = $"{userName} Photo ({visionDescription.description.captions[0].confidence,0:P2}): {visionDescription.description.captions[0].text}"; |
| 198 | + return imageUrl; |
149 | 199 |
|
150 |
| - await chatService.SendMessageAsync(description); |
| 200 | + } |
151 | 201 |
|
152 | 202 | }
|
153 |
| - } |
| 203 | + |
| 204 | + public enum ImageProviders |
| 205 | + { |
| 206 | + None = 0, |
| 207 | + Instagram |
| 208 | + } |
154 | 209 | }
|
0 commit comments