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         // auto stream(in StreamingType type){
341         //     import std.net.curl;
342         //     auto http = HTTP(_clientToken.url);
343         //     http.addRequestHeader("Authorization", "Bearer " ~ _userToken["access_token"].str);
344         //     http.method = HTTP.Method.get;
345         //     string url = _clientToken.url ~ "/api/v1/streaming/" ~ type;
346         //     auto stream = byLineAsync(
347         //             url, 
348         //             No.keepTerminator,
349         //             '\x0a', 
350         //             10, 
351         //             http
352         //             ); 
353         //     return stream;
354         // }
355     }//public
356 
357     private{
358         ClientConfig _clientToken;
359         JSONValue _userToken;
360     }//private
361 }//class Client
362 
363 // /++
364 // +/
365 // struct Account {
366 //     // TODO
367 // }//struct Account
368 //
369 // /++
370 // +/
371 // struct Application {
372 //     // TODO
373 // }//struct Application
374 //
375 // /++
376 // +/
377 // struct Attachment {
378 //     // TODO
379 // }//struct Attachment
380 //
381 // /++
382 // +/
383 // struct Card {
384 //     // TODO
385 // }//struct Card
386 //
387 // /++
388 // +/
389 // struct Context {
390 //     // TODO
391 // }//struct Context
392 //
393 // /++
394 // +/
395 // struct Error {
396 //     // TODO
397 // }//struct Error
398 //
399 // /++
400 // +/
401 // struct Instance {
402 //     // TODO
403 // }//struct Instance
404 //
405 // /++
406 // +/
407 // struct Mention {
408 //     // TODO
409 // }//struct Mention
410 //
411 // /++
412 // +/
413 // struct Notification {
414 //     // TODO
415 // }//struct Notification
416 //
417 // /++
418 // +/
419 // struct Relationship {
420 //     // TODO
421 // }//struct Relationship
422 //
423 // /++
424 // +/
425 // struct Report {
426 //     // TODO
427 // }//struct Report
428 //
429 // /++
430 // +/
431 // struct Result {
432 //     // TODO
433 // }//struct Result
434 //
435 // /++
436 // +/
437 // struct Status {
438 //     // TODO
439 // }//struct Status
440 //
441 // /++
442 // +/
443 // struct Tag {
444 //     // TODO
445 // }//struct Tag
446