1
1
package org .togetherjava .tjbot .features .filesharing ;
2
2
3
- import com .fasterxml .jackson .core .JsonProcessingException ;
4
- import com .fasterxml .jackson .databind .ObjectMapper ;
5
3
import net .dv8tion .jda .api .entities .Member ;
6
4
import net .dv8tion .jda .api .entities .Message ;
7
5
import net .dv8tion .jda .api .entities .Role ;
8
6
import net .dv8tion .jda .api .entities .User ;
9
7
import net .dv8tion .jda .api .entities .channel .ChannelType ;
10
8
import net .dv8tion .jda .api .entities .channel .concrete .ThreadChannel ;
11
- import net .dv8tion .jda .api .entities .emoji .Emoji ;
12
9
import net .dv8tion .jda .api .events .interaction .ModalInteractionEvent ;
13
10
import net .dv8tion .jda .api .events .interaction .component .ButtonInteractionEvent ;
14
11
import net .dv8tion .jda .api .events .interaction .component .SelectMenuInteractionEvent ;
15
12
import net .dv8tion .jda .api .events .message .MessageReceivedEvent ;
16
- import net .dv8tion .jda .api .interactions .components .ActionRow ;
17
13
import net .dv8tion .jda .api .interactions .components .buttons .Button ;
14
+ import org .kohsuke .github .GHGist ;
15
+ import org .kohsuke .github .GHGistBuilder ;
16
+ import org .kohsuke .github .GitHubBuilder ;
18
17
import org .slf4j .Logger ;
19
18
import org .slf4j .LoggerFactory ;
20
19
28
27
import java .io .IOException ;
29
28
import java .io .InputStream ;
30
29
import java .io .UncheckedIOException ;
31
- import java .net .HttpURLConnection ;
32
- import java .net .URI ;
33
- import java .net .http .HttpClient ;
34
- import java .net .http .HttpRequest ;
35
- import java .net .http .HttpResponse ;
36
30
import java .nio .charset .StandardCharsets ;
37
31
import java .util .ArrayList ;
38
32
import java .util .List ;
39
- import java .util .Map ;
40
33
import java .util .Set ;
41
34
import java .util .concurrent .CompletableFuture ;
42
- import java .util .concurrent .ConcurrentHashMap ;
43
35
import java .util .function .Predicate ;
44
36
import java .util .regex .Pattern ;
45
37
48
40
* contains a file with the given extension in the
49
41
* {@link FileSharingMessageListener#extensionFilter}.
50
42
*/
51
- public class FileSharingMessageListener extends MessageReceiverAdapter implements UserInteractor {
52
-
53
- private static final Logger LOGGER = LoggerFactory .getLogger (FileSharingMessageListener .class );
54
- private static final ObjectMapper JSON = new ObjectMapper ();
43
+ public final class FileSharingMessageListener extends MessageReceiverAdapter
44
+ implements UserInteractor {
45
+ private static final Logger logger = LoggerFactory .getLogger (FileSharingMessageListener .class );
55
46
56
47
private final ComponentIdInteractor componentIdInteractor =
57
48
new ComponentIdInteractor (getInteractionType (), getName ());
58
49
59
- private static final String SHARE_API = "https://api.github.com/gists" ;
60
- private static final HttpClient CLIENT = HttpClient .newHttpClient ();
61
-
62
50
private final String gistApiKey ;
63
51
private final Set <String > extensionFilter = Set .of ("txt" , "java" , "gradle" , "xml" , "kt" , "json" ,
64
52
"fxml" , "css" , "c" , "h" , "cpp" , "py" , "yml" );
@@ -82,11 +70,8 @@ public FileSharingMessageListener(Config config) {
82
70
@ Override
83
71
public void onMessageReceived (MessageReceivedEvent event ) {
84
72
User author = event .getAuthor ();
85
- if (author .isBot () || event .isWebhookMessage ()) {
86
- return ;
87
- }
88
73
89
- if (!isHelpThread (event )) {
74
+ if (author . isBot () || event . isWebhookMessage () || !isHelpThread (event )) {
90
75
return ;
91
76
}
92
77
@@ -104,13 +89,36 @@ public void onMessageReceived(MessageReceivedEvent event) {
104
89
try {
105
90
processAttachments (event , attachments );
106
91
} catch (Exception e ) {
107
- LOGGER .error (
92
+ logger .error (
108
93
"Unknown error while processing attachments. Channel: {}, Author: {}, Message ID: {}." ,
109
94
event .getChannel ().getName (), author .getId (), event .getMessageId (), e );
110
95
}
111
96
});
112
97
}
113
98
99
+ @ Override
100
+ public void onButtonClick (ButtonInteractionEvent event , List <String > args ) {
101
+ Member interactionUser = event .getMember ();
102
+ String gistAuthorId = args .get (0 );
103
+ boolean hasSoftModPermissions =
104
+ interactionUser .getRoles ().stream ().map (Role ::getName ).anyMatch (isSoftModRole );
105
+
106
+ if (!gistAuthorId .equals (interactionUser .getId ()) && !hasSoftModPermissions ) {
107
+ event .reply ("You do not have permission for this action." ).setEphemeral (true ).queue ();
108
+ return ;
109
+ }
110
+
111
+ String gistId = args .get (1 );
112
+
113
+ try {
114
+ new GitHubBuilder ().withOAuthToken (gistApiKey ).build ().getGist (gistId ).delete ();
115
+
116
+ event .getMessage ().delete ().queue ();
117
+ } catch (IOException e ) {
118
+ logger .warn ("Failed to delete gist with id {}" , gistId , e );
119
+ }
120
+ }
121
+
114
122
private boolean isAttachmentRelevant (Message .Attachment attachment ) {
115
123
String extension = attachment .getFileExtension ();
116
124
if (extension == null ) {
@@ -120,29 +128,28 @@ private boolean isAttachmentRelevant(Message.Attachment attachment) {
120
128
}
121
129
122
130
private void processAttachments (MessageReceivedEvent event ,
123
- List <Message .Attachment > attachments ) {
124
-
125
- Map <String , GistFile > nameToFile = new ConcurrentHashMap <>();
131
+ List <Message .Attachment > attachments ) throws IOException {
132
+ GHGistBuilder gistBuilder = new GitHubBuilder ().withOAuthToken (gistApiKey )
133
+ .build ()
134
+ .createGist ()
135
+ .public_ (false )
136
+ .description ("Uploaded by " + event .getAuthor ().getAsTag ());
126
137
127
138
List <CompletableFuture <Void >> tasks = new ArrayList <>();
139
+
128
140
for (Message .Attachment attachment : attachments ) {
129
141
CompletableFuture <Void > task = attachment .getProxy ()
130
142
.download ()
131
143
.thenApply (this ::readAttachment )
132
- .thenAccept (
133
- content -> nameToFile .put (getNameOf (attachment ), new GistFile (content )));
144
+ .thenAccept (content -> gistBuilder .file (getNameOf (attachment ), content ));
134
145
135
146
tasks .add (task );
136
147
}
137
148
138
149
tasks .forEach (CompletableFuture ::join );
139
150
140
- GistFiles files = new GistFiles (nameToFile );
141
- GistRequest request = new GistRequest (event .getAuthor ().getName (), false , files );
142
- GistResponse response = uploadToGist (request );
143
- String url = response .getHtmlUrl ();
144
- String gistId = response .getGistId ();
145
- sendResponse (event , url , gistId );
151
+ GHGist gist = gistBuilder .create ();
152
+ sendResponse (event , gist .getHtmlUrl ().toString (), gist .getGistId ());
146
153
}
147
154
148
155
private String readAttachment (InputStream stream ) {
@@ -173,62 +180,15 @@ private String getNameOf(Message.Attachment attachment) {
173
180
return fileName ;
174
181
}
175
182
176
- private GistResponse uploadToGist (GistRequest jsonRequest ) {
177
- String body ;
178
- try {
179
- body = JSON .writeValueAsString (jsonRequest );
180
- } catch (JsonProcessingException e ) {
181
- throw new IllegalStateException (
182
- "Attempting to upload a file to gist, but unable to create the JSON request." ,
183
- e );
184
- }
185
-
186
- HttpRequest request = HttpRequest .newBuilder ()
187
- .uri (URI .create (SHARE_API ))
188
- .header ("Accept" , "application/json" )
189
- .header ("Authorization" , "token " + gistApiKey )
190
- .POST (HttpRequest .BodyPublishers .ofString (body ))
191
- .build ();
192
-
193
- HttpResponse <String > apiResponse ;
194
- try {
195
- apiResponse = CLIENT .send (request , HttpResponse .BodyHandlers .ofString ());
196
- } catch (IOException e ) {
197
- throw new UncheckedIOException (e );
198
- } catch (InterruptedException e ) {
199
- Thread .currentThread ().interrupt ();
200
- throw new IllegalStateException (
201
- "Attempting to upload a file to gist, but the request got interrupted." , e );
202
- }
203
-
204
- int statusCode = apiResponse .statusCode ();
205
-
206
- if (statusCode < HttpURLConnection .HTTP_OK
207
- || statusCode >= HttpURLConnection .HTTP_MULT_CHOICE ) {
208
- throw new IllegalStateException ("Gist API unexpected response: %s. Request JSON: %s"
209
- .formatted (apiResponse .body (), body ));
210
- }
211
-
212
- GistResponse gistResponse ;
213
- try {
214
- gistResponse = JSON .readValue (apiResponse .body (), GistResponse .class );
215
- } catch (JsonProcessingException e ) {
216
- throw new IllegalStateException (
217
- "Attempting to upload file to gist, but unable to parse its JSON response." , e );
218
- }
219
- return gistResponse ;
220
- }
221
-
222
183
private void sendResponse (MessageReceivedEvent event , String url , String gistId ) {
223
184
Message message = event .getMessage ();
224
- String messageContent =
225
- "I uploaded your attachments as **gist**. That way, they are easier to read for everyone, especially mobile users 👍" ;
185
+ String messageContent = "I uploaded your attachments as **Gist**." ;
226
186
227
- Button gist = Button .link (url , "gist " );
187
+ Button gist = Button .link (url , "Gist " );
228
188
229
189
Button delete = Button .danger (
230
190
componentIdInteractor .generateComponentId (message .getAuthor ().getId (), gistId ),
231
- Emoji . fromUnicode ( "🗑️" ) );
191
+ "Dismiss" );
232
192
233
193
message .reply (messageContent ).setActionRow (gist , delete ).queue ();
234
194
}
@@ -243,32 +203,6 @@ private boolean isHelpThread(MessageReceivedEvent event) {
243
203
return isHelpForumName .test (rootChannelName );
244
204
}
245
205
246
- private void deleteGist (String gistId ) {
247
- HttpRequest request = HttpRequest .newBuilder ()
248
- .uri (URI .create (SHARE_API + "/" + gistId ))
249
- .header ("Accept" , "application/json" )
250
- .header ("Authorization" , "token " + gistApiKey )
251
- .DELETE ()
252
- .build ();
253
-
254
- HttpResponse <String > apiResponse ;
255
- try {
256
- apiResponse = CLIENT .send (request , HttpResponse .BodyHandlers .ofString ());
257
- } catch (IOException e ) {
258
- throw new UncheckedIOException (e );
259
- } catch (InterruptedException e ) {
260
- Thread .currentThread ().interrupt ();
261
- throw new IllegalStateException (
262
- "Attempting to delete a gist, but the request got interrupted." , e );
263
- }
264
-
265
- int status = apiResponse .statusCode ();
266
- if (status == 404 ) {
267
- String responseBody = apiResponse .body ();
268
- LOGGER .warn ("Gist API unexpected response while deleting gist: {}." , responseBody );
269
- }
270
- }
271
-
272
206
@ Override
273
207
public String getName () {
274
208
return "filesharing" ;
@@ -284,27 +218,6 @@ public UserInteractionType getInteractionType() {
284
218
return UserInteractionType .OTHER ;
285
219
}
286
220
287
- @ Override
288
- public void onButtonClick (ButtonInteractionEvent event , List <String > args ) {
289
- Member interactionUser = event .getMember ();
290
- String gistAuthorId = args .get (0 );
291
- boolean hasSoftModPermissions =
292
- interactionUser .getRoles ().stream ().map (Role ::getName ).anyMatch (isSoftModRole );
293
-
294
- if (!gistAuthorId .equals (interactionUser .getId ()) && !hasSoftModPermissions ) {
295
- event .reply ("You do not have permission for this action." ).setEphemeral (true ).queue ();
296
- return ;
297
- }
298
-
299
- Message message = event .getMessage ();
300
- List <Button > buttons = message .getButtons ();
301
- event .editComponents (ActionRow .of (buttons .stream ().map (Button ::asDisabled ).toList ()))
302
- .queue ();
303
-
304
- String gistId = args .get (1 );
305
- deleteGist (gistId );
306
- }
307
-
308
221
@ Override
309
222
public void onSelectMenuSelection (SelectMenuInteractionEvent event , List <String > args ) {
310
223
throw new UnsupportedOperationException ("Not used" );
@@ -314,5 +227,4 @@ public void onSelectMenuSelection(SelectMenuInteractionEvent event, List<String>
314
227
public void onModalSubmitted (ModalInteractionEvent event , List <String > args ) {
315
228
throw new UnsupportedOperationException ("Not used" );
316
229
}
317
-
318
230
}
0 commit comments