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