1 module mastodon;
2 
3 // import std.net.curl:HTTP, post, get, patch, del, No;
4 import std.net.curl;
5 import std.json:JSONValue, parseJSON;
6 import std.conv:to;
7 
8 ///
9 ClientConfig createApp(in string url, in string clientName, in string scopes, in string redirectUris = "urn:ietf:wg:oauth:2.0:oob"){
10     auto res = post(url ~ "/api/v1/apps", [ "client_name"   : clientName,
11                                             "redirect_uris" : redirectUris,
12                                             "scope"         : scopes]).parseJSON;
13     auto result = ClientConfig();
14     result.url = url;
15     result.id = res["client_id"].str;
16     result.secret = res["client_secret"].str;
17     return result;
18 }
19 
20 ///
21 JSONValue signIn(in string url, in string clientId, in string clientIdSeclet, in string email, in string password, in string scopes = "read write follow"){
22     string[string] option = ["client_id" : clientId, 
23                    "client_secret" : clientIdSeclet, 
24                    "grant_type" : "password", 
25                    "username" : email, 
26                    "password" : password,
27                    "scope" : scopes];
28     auto response = post(url ~ "/oauth/token", option).parseJSON;
29     return response;
30 }
31 
32 ///
33 JSONValue signIn(in JSONValue clientConfig, in string email, in string password){
34     import std.algorithm:filter;
35     import std.array:array;
36     return signIn(clientConfig["url"].str.filter!(e => e!='\\').to!string, clientConfig["client_id"].str, clientConfig["client_secret"].str, email, password);
37 }
38 
39 JSONValue signIn(in ClientConfig clientConfig, in string email, in string password){
40     return signIn(clientConfig.url, clientConfig.id, clientConfig.secret, email, password);
41 }
42 
43 /++
44 +/
45 struct ClientConfig {
46     public{
47         this(in string url, in string id, in string secret){
48             this.url    = url;
49             this.id     = id;
50             this.secret = secret;
51         }
52         this(JSONValue v){
53             url    = v["url"].str;
54             id     = v["client_id"].str;
55             secret = v["client_secret"].str;
56         }
57         string url;
58         string id;
59         string secret;
60     }//public
61 
62     private{
63     }//private
64 }//struct ClientConfig
65 
66 /++
67 +/
68 enum StreamingType {
69     User   = "user",
70     Public = "public",
71     Hashta = "hashtag"
72 }//enum StreamingType
73 
74 /++
75 +/
76 class Client {
77     private alias This = typeof(this);
78     /++
79         +/
80     private enum Method {
81         GET, POST, DELETE, PATCH
82     }//enum Method
83     public{
84         this(in ClientConfig clientToken){
85             _clientToken = clientToken;
86         }
87 
88         This signIn(in string email, in string password){
89             _userToken = _clientToken.signIn(email, password);
90             return this;
91         };
92 
93         JSONValue request(Method M, T)(in string endPoint, T arg = null){
94             auto http = HTTP(_clientToken.url);
95             http.addRequestHeader("Authorization", "Bearer " ~ _userToken["access_token"].str);
96             // http.handle.set(CurlOption.ssl_verifypeer, false);
97             string url = _clientToken.url ~ endPoint;
98             JSONValue response;
99 
100             static if(M == Method.GET){
101                 response = get(url, http).parseJSON;
102             }
103             static if(M == Method.POST){
104                 response = post(url, arg, http).parseJSON;
105             }
106             static if(M == Method.PATCH){
107                 response = patch(url, arg, http).parseJSON;
108             }
109             static if(M == Method.DELETE){
110                 del(url, http);
111             }
112         
113             return response;
114         }
115 
116         ///
117         JSONValue account(in uint id){
118             return request!(Method.GET)("/api/v1/accounts/" ~ id.to!string);
119         }
120 
121         JSONValue verifyAccountCredentials(){
122             return request!(Method.GET)("/api/v1/accounts/verify_credentials");
123         }
124 
125         JSONValue updateAccountCredentials(in string arg){
126         // TODO doesn't work
127             return request!(Method.PATCH)("/api/v1/accounts/update_credentials", arg);
128         }
129 
130         ///
131         JSONValue accountFollowers(in uint id){
132             return request!(Method.GET)("/api/v1/accounts/" ~ id.to!string ~ "/followers");
133         }
134 
135         /// 
136         JSONValue accountFollowing(in uint id){
137             return request!(Method.GET)("/api/v1/accounts/" ~ id.to!string ~ "/following");
138         }
139 
140         ///
141         JSONValue accountStatuses(in uint id){
142             return request!(Method.GET)("/api/v1/accounts/" ~ id.to!string ~ "/statuses");
143         }
144 
145         ///
146         JSONValue followAccount(in uint id){
147             return request!(Method.POST)("/api/v1/accounts/" ~ id.to!string ~ "/follow");
148         }
149 
150         ///
151         JSONValue unfollowAccount(in uint id){
152             return request!(Method.POST)("/api/v1/accounts/" ~ id.to!string ~ "/unfollow");
153         }
154 
155         ///
156         // GET /api/v1/accounts/:id/block
157         JSONValue blockAccount(in uint id){
158             return request!(Method.POST)("/api/v1/accounts/" ~ id.to!string ~ "/block");
159         }
160 
161         ///
162         JSONValue unblockAccount(in uint id){
163             return request!(Method.POST)("/api/v1/accounts/" ~ id.to!string ~ "/unblock");
164         }
165 
166         ///
167         JSONValue muteAccount(in uint id){
168             return request!(Method.POST)("/api/v1/accounts/" ~ id.to!string ~ "/mute");
169         }
170 
171         ///
172         JSONValue unmuteAccount(in uint id){
173             return request!(Method.POST)("/api/v1/accounts/" ~ id.to!string ~ "/unmute");
174         }
175 
176         /// 
177         JSONValue accountRelationships(in uint[] id...){
178             return accountRelationships(id);
179         }
180 
181         ///
182         JSONValue accountRelationships(in uint[] arr){
183             import std.algorithm:map;
184             import std.string:join;
185             string qArray = arr.map!(e => "id[]=" ~ e.to!string).join("&");
186             return request!(Method.GET)("/api/v1/accounts/relationships/?" ~ qArray);
187         }
188 
189         ///
190         JSONValue searchAccount(in string q, in uint limit = 40){
191             return request!(Method.GET)("/api/v1/accounts/search/?q="~q~"&limit"~limit.to!string);
192         }
193 
194         ///
195         JSONValue blocks(){
196             return request!(Method.GET)("/api/v1/blocks");
197         }
198 
199         ///
200         JSONValue favourites(){
201             return request!(Method.GET)("/api/v1/favourites");
202         }
203 
204         ///
205         JSONValue followRequests(){
206             return request!(Method.GET)("/api/v1/follow_requests");
207         }
208 
209         ///
210         JSONValue authorizeFollowRequest(in uint id){
211             return request!(Method.POST)("/api/v1/follow_requests/authorize", ["id":id.to!string]);
212         }
213 
214         ///
215         JSONValue rejectFollowRequest(in uint id){
216             return request!(Method.POST)("/api/v1/follow_requests/reject", ["id":id.to!string]);
217         }
218 
219         ///
220         JSONValue followRemoteUser(in string uri){
221             return request!(Method.POST)("/api/v1/follows", ["uri":uri.to!string]);
222         }
223 
224         ///
225         JSONValue instance(){
226             return request!(Method.GET)("/api/v1/instance");
227         }
228 
229         /// TODO
230         // POST /api/v1/media
231 
232         ///
233         JSONValue mutes(){
234             return request!(Method.GET)("/api/v1/mutes");
235         }
236 
237         ///
238         JSONValue notifications(){
239             return request!(Method.GET)("/api/v1/notifications");
240         }
241 
242         ///
243         JSONValue notifications(in uint id){
244             return request!(Method.GET)("/api/v1/notifications/" ~ id.to!string);
245         }
246 
247         ///
248         JSONValue clearNotifications(){
249             return request!(Method.GET)("/api/v1/notifications/clear");
250         }
251 
252         ///
253         JSONValue reports(){
254             return request!(Method.GET)("/api/v1/reports");
255         }
256 
257         /// TODO
258         // POST /api/v1/reports
259 
260         /// TODO
261         // GET /api/v1/search
262         // JSONValue search(in string q, bool resolve = false){
263         //     import std.conv:to;
264         //     string qArray = "q="~q ~ resolve?"&resolve":"";
265         //     return request!(Method.GET)("/api/v1/search/?" ~ qArray);
266         // }
267 
268         ///
269         JSONValue status(in uint id){
270             return request!(Method.GET)("/api/v1/statuses/" ~ id.to!string);
271         }
272 
273         ///
274         JSONValue statusContext(in uint id){
275             return request!(Method.GET)("/api/v1/statuses/" ~ id.to!string ~ "/context");
276         }
277 
278         ///
279         JSONValue statusCard(in uint id){
280             return request!(Method.GET)("/api/v1/statuses/" ~ id.to!string ~ "/card");
281         }
282 
283         ///
284         JSONValue rebloggedBy(in uint id){
285             return request!(Method.GET)("/api/v1/statuses/" ~ id.to!string ~ "/reblogged_by");
286         }
287 
288 
289         ///
290         JSONValue favouritedBy(in uint id){
291             return request!(Method.GET)("/api/v1/statuses/" ~ id.to!string ~ "/favourited_by");
292         }
293 
294         /// TODO add params
295         JSONValue postStatus(in string status){
296             string[string] arg = ["status" : status];
297             return request!(Method.POST)("/api/v1/statuses", arg);
298         }
299 
300         ///
301         JSONValue deleteStatus(in uint statusId){
302             return request!(Method.DELETE)("/api/v1/statuses/"~statusId.to!string);
303         }
304 
305         ///
306         JSONValue reblog(in uint statusId){
307             return request!(Method.POST)("/api/v1/statuses/"~statusId.to!string~"/reblog");
308         }
309 
310         ///
311         JSONValue unreblog(in uint statusId){
312             return request!(Method.POST)("/api/v1/statuses/"~statusId.to!string~"/unreblog");
313         }
314 
315         ///
316         JSONValue favourite(in uint statusId){
317             return request!(Method.POST)("/api/v1/statuses/"~statusId.to!string~"/favourite");
318         }
319 
320         ///
321         JSONValue unfavourite(in uint statusId){
322             return request!(Method.POST)("/api/v1/statuses/"~statusId.to!string~"/unfavourite");
323         }
324 
325         ///
326         JSONValue timelineHome(){
327             return request!(Method.GET)("/api/v1/timelines/home");
328         }
329 
330         ///
331         JSONValue timelinePublic(){
332             return request!(Method.GET)("/api/v1/timelines/public");
333         }
334 
335         ///
336         JSONValue timelineHashtag(in string tag){
337             return request!(Method.GET)("/api/v1/timelines/tag/" ~ tag);
338         }
339 
340         ///
341         auto stream(in StreamingType type){
342             import std.net.curl;
343             auto http = HTTP(_clientToken.url);
344             http.addRequestHeader("Authorization", "Bearer " ~ _userToken["access_token"].str);
345             http.method = HTTP.Method.get;
346             string url = _clientToken.url ~ "/api/v1/streaming/" ~ type;
347             auto stream = byLineAsync(
348                     url, 
349                     No.keepTerminator,
350                     '\x0a', 
351                     10, 
352                     http
353                     ); 
354 
355             import std.algorithm:filter, map;
356             string lastLineType;
357             string lastEventType;
358             foreach (e; stream.filter!"a.length != 0") {
359                 import std.json;
360                 import std.stdio;
361                 if(e[0..6] == "event:"){
362                     lastLineType = "event";
363                     lastEventType = e[7..$].to!string;
364                 }else{
365                     if(lastLineType == "event"){
366                         switch (lastEventType){
367                             case "update":
368                                 "################".writeln;
369                                 e[6..$].parseJSON(JSONOptions.none)["content"].writeln;
370                                 "################".writeln;
371                                 break;
372                             case "delete":
373                                 //TODO
374                                 break;
375                             case "notification":
376                                 //TODO
377                                 break;
378                             default:
379                                 //TODO
380                                 break;
381                         }
382                     }
383                     lastLineType = "data";
384                 }
385             }
386             // return stream;
387         }
388 
389         ///
390         // void startStreaming(in StreamingType type){
391         //     string url = _clientToken.url ~ "/api/v1/streaming/" ~ type;
392         //     import std.stdio;
393         //     url.writeln;
394         //     _streams[type] = new Stream(url, _userToken["access_token"].str);
395         //     // _streams[type].addRequestHeader("Authorization", "Bearer " ~ _userToken["access_token"].str);
396         //     // _streams[type].method = HTTP.Method.get;
397         //
398         //     // return request!(Method.GET)("/api/v1/streaming/user");
399         // }
400 
401         ///
402         // JSONValue[] streaming(in StreamingType type){
403         // }
404     }//public
405 
406     private{
407         ClientConfig _clientToken;
408         JSONValue _userToken;
409         Stream[StreamingType] _streams;
410 
411     }//private
412 }//class Client
413 
414 /++
415 +/
416 private class Stream {
417     // public{
418     //     this(in string url, in string accessToken){
419     //         auto http = HTTP(url);
420     //         http.addRequestHeader("Authorization", "Bearer " ~ accessToken);
421     //         http.method = HTTP.Method.get;
422     //         _http = &http;
423     //         _url = url;
424     //         // _http.addRequestHeader("Authorization", "Bearer " ~ accessToken);
425     //         // _http.method = HTTP.Method.get;
426     //     }
427     //
428     //     start(){
429     //         _stream = byLineAsync(_url); 
430     //     }
431     //
432     //     JSONValue[] pop(){
433     //         JSONValue[] events;
434     //         foreach (e; _stream) {
435     //             events = e.parseJSON;
436     //         }
437     //         _stream = [];
438     //         return events;
439     //     }
440     // }//public
441     //
442     // private{
443     //     HTTP* _http;
444     //     string _url;
445     //     char[] _stream;
446     //
447     // }//private
448 }//class Streamk
449 
450 // /++
451 // +/
452 // struct Account {
453 //     // TODO
454 // }//struct Account
455 //
456 // /++
457 // +/
458 // struct Application {
459 //     // TODO
460 // }//struct Application
461 //
462 // /++
463 // +/
464 // struct Attachment {
465 //     // TODO
466 // }//struct Attachment
467 //
468 // /++
469 // +/
470 // struct Card {
471 //     // TODO
472 // }//struct Card
473 //
474 // /++
475 // +/
476 // struct Context {
477 //     // TODO
478 // }//struct Context
479 //
480 // /++
481 // +/
482 // struct Error {
483 //     // TODO
484 // }//struct Error
485 //
486 // /++
487 // +/
488 // struct Instance {
489 //     // TODO
490 // }//struct Instance
491 //
492 // /++
493 // +/
494 // struct Mention {
495 //     // TODO
496 // }//struct Mention
497 //
498 // /++
499 // +/
500 // struct Notification {
501 //     // TODO
502 // }//struct Notification
503 //
504 // /++
505 // +/
506 // struct Relationship {
507 //     // TODO
508 // }//struct Relationship
509 //
510 // /++
511 // +/
512 // struct Report {
513 //     // TODO
514 // }//struct Report
515 //
516 // /++
517 // +/
518 // struct Result {
519 //     // TODO
520 // }//struct Result
521 //
522 // /++
523 // +/
524 // struct Status {
525 //     // TODO
526 // }//struct Status
527 //
528 // /++
529 // +/
530 // struct Tag {
531 //     // TODO
532 // }//struct Tag