├── .gitignore ├── .vscode └── launch.json ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md └── src ├── main.rs ├── types.rs └── util.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | downloads 4 | export 5 | credentials.toml 6 | /*.html 7 | /*.json -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "lldb", 9 | "request": "launch", 10 | "name": "Debug executable 'tumblr-likes'", 11 | "cargo": { 12 | "args": [ 13 | "build", 14 | "--bin=tumblr-likes", 15 | "--package=tumblr-likes" 16 | ], 17 | "filter": { 18 | "kind": "bin" 19 | } 20 | }, 21 | "args": [], 22 | "cwd": "${workspaceFolder}" 23 | }, 24 | { 25 | "type": "lldb", 26 | "request": "launch", 27 | "name": "Debug unit tests in executable 'tumblr-likes'", 28 | "cargo": { 29 | "args": [ 30 | "test", 31 | "--no-run", 32 | "--bin=tumblr-likes", 33 | "--package=tumblr-likes" 34 | ], 35 | "filter": { 36 | "kind": "bin" 37 | } 38 | }, 39 | "args": [], 40 | "cwd": "${workspaceFolder}" 41 | } 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "0.7.18" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "atty" 16 | version = "0.2.11" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652" 19 | dependencies = [ 20 | "libc", 21 | "termion", 22 | "winapi", 23 | ] 24 | 25 | [[package]] 26 | name = "autocfg" 27 | version = "1.1.0" 28 | source = "registry+https://github.com/rust-lang/crates.io-index" 29 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 30 | 31 | [[package]] 32 | name = "base64" 33 | version = "0.13.0" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" 36 | 37 | [[package]] 38 | name = "bitflags" 39 | version = "1.3.2" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 42 | 43 | [[package]] 44 | name = "bumpalo" 45 | version = "3.10.0" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" 48 | 49 | [[package]] 50 | name = "bytes" 51 | version = "1.2.1" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" 54 | 55 | [[package]] 56 | name = "cc" 57 | version = "1.0.25" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "f159dfd43363c4d08055a07703eb7a3406b0dac4d0584d96965a3262db3c9d16" 60 | 61 | [[package]] 62 | name = "cfg-if" 63 | version = "0.1.6" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "082bb9b28e00d3c9d39cc03e64ce4cea0f1bb9b3fde493f0cbc008472d22bdf4" 66 | 67 | [[package]] 68 | name = "cfg-if" 69 | version = "1.0.0" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 72 | 73 | [[package]] 74 | name = "clap" 75 | version = "3.2.17" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "29e724a68d9319343bb3328c9cc2dfde263f4b3142ee1059a9980580171c954b" 78 | dependencies = [ 79 | "atty", 80 | "bitflags", 81 | "clap_lex", 82 | "indexmap", 83 | "once_cell", 84 | "strsim", 85 | "termcolor", 86 | "textwrap", 87 | ] 88 | 89 | [[package]] 90 | name = "clap_lex" 91 | version = "0.2.4" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" 94 | dependencies = [ 95 | "os_str_bytes", 96 | ] 97 | 98 | [[package]] 99 | name = "console" 100 | version = "0.15.1" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "89eab4d20ce20cea182308bca13088fecea9c05f6776cf287205d41a0ed3c847" 103 | dependencies = [ 104 | "encode_unicode", 105 | "libc", 106 | "once_cell", 107 | "terminal_size", 108 | "unicode-width", 109 | "winapi", 110 | ] 111 | 112 | [[package]] 113 | name = "core-foundation" 114 | version = "0.9.3" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" 117 | dependencies = [ 118 | "core-foundation-sys", 119 | "libc", 120 | ] 121 | 122 | [[package]] 123 | name = "core-foundation-sys" 124 | version = "0.8.3" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" 127 | 128 | [[package]] 129 | name = "encode_unicode" 130 | version = "0.3.6" 131 | source = "registry+https://github.com/rust-lang/crates.io-index" 132 | checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" 133 | 134 | [[package]] 135 | name = "encoding_rs" 136 | version = "0.8.13" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "1a8fa54e6689eb2549c4efed8d00d7f3b2b994a064555b0e8df4ae3764bcc4be" 139 | dependencies = [ 140 | "cfg-if 0.1.6", 141 | ] 142 | 143 | [[package]] 144 | name = "fastrand" 145 | version = "1.8.0" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" 148 | dependencies = [ 149 | "instant", 150 | ] 151 | 152 | [[package]] 153 | name = "fnv" 154 | version = "1.0.6" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | checksum = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" 157 | 158 | [[package]] 159 | name = "foreign-types" 160 | version = "0.3.2" 161 | source = "registry+https://github.com/rust-lang/crates.io-index" 162 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 163 | dependencies = [ 164 | "foreign-types-shared", 165 | ] 166 | 167 | [[package]] 168 | name = "foreign-types-shared" 169 | version = "0.1.1" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 172 | 173 | [[package]] 174 | name = "form_urlencoded" 175 | version = "1.0.1" 176 | source = "registry+https://github.com/rust-lang/crates.io-index" 177 | checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" 178 | dependencies = [ 179 | "matches", 180 | "percent-encoding", 181 | ] 182 | 183 | [[package]] 184 | name = "futures-channel" 185 | version = "0.3.21" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" 188 | dependencies = [ 189 | "futures-core", 190 | ] 191 | 192 | [[package]] 193 | name = "futures-core" 194 | version = "0.3.21" 195 | source = "registry+https://github.com/rust-lang/crates.io-index" 196 | checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" 197 | 198 | [[package]] 199 | name = "futures-sink" 200 | version = "0.3.21" 201 | source = "registry+https://github.com/rust-lang/crates.io-index" 202 | checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" 203 | 204 | [[package]] 205 | name = "futures-task" 206 | version = "0.3.21" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" 209 | 210 | [[package]] 211 | name = "futures-util" 212 | version = "0.3.21" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" 215 | dependencies = [ 216 | "futures-core", 217 | "futures-task", 218 | "pin-project-lite", 219 | "pin-utils", 220 | ] 221 | 222 | [[package]] 223 | name = "h2" 224 | version = "0.3.13" 225 | source = "registry+https://github.com/rust-lang/crates.io-index" 226 | checksum = "37a82c6d637fc9515a4694bbf1cb2457b79d81ce52b3108bdeea58b07dd34a57" 227 | dependencies = [ 228 | "bytes", 229 | "fnv", 230 | "futures-core", 231 | "futures-sink", 232 | "futures-util", 233 | "http", 234 | "indexmap", 235 | "slab", 236 | "tokio", 237 | "tokio-util", 238 | "tracing", 239 | ] 240 | 241 | [[package]] 242 | name = "hashbrown" 243 | version = "0.12.3" 244 | source = "registry+https://github.com/rust-lang/crates.io-index" 245 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 246 | 247 | [[package]] 248 | name = "hermit-abi" 249 | version = "0.1.19" 250 | source = "registry+https://github.com/rust-lang/crates.io-index" 251 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 252 | dependencies = [ 253 | "libc", 254 | ] 255 | 256 | [[package]] 257 | name = "http" 258 | version = "0.2.8" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" 261 | dependencies = [ 262 | "bytes", 263 | "fnv", 264 | "itoa", 265 | ] 266 | 267 | [[package]] 268 | name = "http-body" 269 | version = "0.4.5" 270 | source = "registry+https://github.com/rust-lang/crates.io-index" 271 | checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" 272 | dependencies = [ 273 | "bytes", 274 | "http", 275 | "pin-project-lite", 276 | ] 277 | 278 | [[package]] 279 | name = "httparse" 280 | version = "1.7.1" 281 | source = "registry+https://github.com/rust-lang/crates.io-index" 282 | checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" 283 | 284 | [[package]] 285 | name = "httpdate" 286 | version = "1.0.2" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" 289 | 290 | [[package]] 291 | name = "hyper" 292 | version = "0.14.20" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | checksum = "02c929dc5c39e335a03c405292728118860721b10190d98c2a0f0efd5baafbac" 295 | dependencies = [ 296 | "bytes", 297 | "futures-channel", 298 | "futures-core", 299 | "futures-util", 300 | "h2", 301 | "http", 302 | "http-body", 303 | "httparse", 304 | "httpdate", 305 | "itoa", 306 | "pin-project-lite", 307 | "socket2", 308 | "tokio", 309 | "tower-service", 310 | "tracing", 311 | "want", 312 | ] 313 | 314 | [[package]] 315 | name = "hyper-tls" 316 | version = "0.5.0" 317 | source = "registry+https://github.com/rust-lang/crates.io-index" 318 | checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" 319 | dependencies = [ 320 | "bytes", 321 | "hyper", 322 | "native-tls", 323 | "tokio", 324 | "tokio-native-tls", 325 | ] 326 | 327 | [[package]] 328 | name = "idna" 329 | version = "0.2.3" 330 | source = "registry+https://github.com/rust-lang/crates.io-index" 331 | checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" 332 | dependencies = [ 333 | "matches", 334 | "unicode-bidi", 335 | "unicode-normalization", 336 | ] 337 | 338 | [[package]] 339 | name = "indexmap" 340 | version = "1.9.1" 341 | source = "registry+https://github.com/rust-lang/crates.io-index" 342 | checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" 343 | dependencies = [ 344 | "autocfg", 345 | "hashbrown", 346 | ] 347 | 348 | [[package]] 349 | name = "indicatif" 350 | version = "0.17.0" 351 | source = "registry+https://github.com/rust-lang/crates.io-index" 352 | checksum = "fcc42b206e70d86ec03285b123e65a5458c92027d1fb2ae3555878b8113b3ddf" 353 | dependencies = [ 354 | "console", 355 | "number_prefix", 356 | "unicode-width", 357 | ] 358 | 359 | [[package]] 360 | name = "instant" 361 | version = "0.1.12" 362 | source = "registry+https://github.com/rust-lang/crates.io-index" 363 | checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" 364 | dependencies = [ 365 | "cfg-if 1.0.0", 366 | ] 367 | 368 | [[package]] 369 | name = "ipnet" 370 | version = "2.5.0" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" 373 | 374 | [[package]] 375 | name = "itoa" 376 | version = "1.0.3" 377 | source = "registry+https://github.com/rust-lang/crates.io-index" 378 | checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" 379 | 380 | [[package]] 381 | name = "js-sys" 382 | version = "0.3.59" 383 | source = "registry+https://github.com/rust-lang/crates.io-index" 384 | checksum = "258451ab10b34f8af53416d1fdab72c22e805f0c92a1136d59470ec0b11138b2" 385 | dependencies = [ 386 | "wasm-bindgen", 387 | ] 388 | 389 | [[package]] 390 | name = "lazy_static" 391 | version = "1.4.0" 392 | source = "registry+https://github.com/rust-lang/crates.io-index" 393 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 394 | 395 | [[package]] 396 | name = "libc" 397 | version = "0.2.131" 398 | source = "registry+https://github.com/rust-lang/crates.io-index" 399 | checksum = "04c3b4822ccebfa39c02fc03d1534441b22ead323fa0f48bb7ddd8e6ba076a40" 400 | 401 | [[package]] 402 | name = "lock_api" 403 | version = "0.4.7" 404 | source = "registry+https://github.com/rust-lang/crates.io-index" 405 | checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" 406 | dependencies = [ 407 | "autocfg", 408 | "scopeguard", 409 | ] 410 | 411 | [[package]] 412 | name = "log" 413 | version = "0.4.17" 414 | source = "registry+https://github.com/rust-lang/crates.io-index" 415 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 416 | dependencies = [ 417 | "cfg-if 1.0.0", 418 | ] 419 | 420 | [[package]] 421 | name = "matches" 422 | version = "0.1.8" 423 | source = "registry+https://github.com/rust-lang/crates.io-index" 424 | checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" 425 | 426 | [[package]] 427 | name = "memchr" 428 | version = "2.5.0" 429 | source = "registry+https://github.com/rust-lang/crates.io-index" 430 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 431 | 432 | [[package]] 433 | name = "mime" 434 | version = "0.3.16" 435 | source = "registry+https://github.com/rust-lang/crates.io-index" 436 | checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" 437 | 438 | [[package]] 439 | name = "mio" 440 | version = "0.8.4" 441 | source = "registry+https://github.com/rust-lang/crates.io-index" 442 | checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" 443 | dependencies = [ 444 | "libc", 445 | "log", 446 | "wasi", 447 | "windows-sys", 448 | ] 449 | 450 | [[package]] 451 | name = "native-tls" 452 | version = "0.2.10" 453 | source = "registry+https://github.com/rust-lang/crates.io-index" 454 | checksum = "fd7e2f3618557f980e0b17e8856252eee3c97fa12c54dff0ca290fb6266ca4a9" 455 | dependencies = [ 456 | "lazy_static", 457 | "libc", 458 | "log", 459 | "openssl", 460 | "openssl-probe", 461 | "openssl-sys", 462 | "schannel", 463 | "security-framework", 464 | "security-framework-sys", 465 | "tempfile", 466 | ] 467 | 468 | [[package]] 469 | name = "num_cpus" 470 | version = "1.13.1" 471 | source = "registry+https://github.com/rust-lang/crates.io-index" 472 | checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" 473 | dependencies = [ 474 | "hermit-abi", 475 | "libc", 476 | ] 477 | 478 | [[package]] 479 | name = "number_prefix" 480 | version = "0.4.0" 481 | source = "registry+https://github.com/rust-lang/crates.io-index" 482 | checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" 483 | 484 | [[package]] 485 | name = "once_cell" 486 | version = "1.13.0" 487 | source = "registry+https://github.com/rust-lang/crates.io-index" 488 | checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" 489 | 490 | [[package]] 491 | name = "openssl" 492 | version = "0.10.41" 493 | source = "registry+https://github.com/rust-lang/crates.io-index" 494 | checksum = "618febf65336490dfcf20b73f885f5651a0c89c64c2d4a8c3662585a70bf5bd0" 495 | dependencies = [ 496 | "bitflags", 497 | "cfg-if 1.0.0", 498 | "foreign-types", 499 | "libc", 500 | "once_cell", 501 | "openssl-macros", 502 | "openssl-sys", 503 | ] 504 | 505 | [[package]] 506 | name = "openssl-macros" 507 | version = "0.1.0" 508 | source = "registry+https://github.com/rust-lang/crates.io-index" 509 | checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" 510 | dependencies = [ 511 | "proc-macro2", 512 | "quote", 513 | "syn", 514 | ] 515 | 516 | [[package]] 517 | name = "openssl-probe" 518 | version = "0.1.2" 519 | source = "registry+https://github.com/rust-lang/crates.io-index" 520 | checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" 521 | 522 | [[package]] 523 | name = "openssl-sys" 524 | version = "0.9.75" 525 | source = "registry+https://github.com/rust-lang/crates.io-index" 526 | checksum = "e5f9bd0c2710541a3cda73d6f9ac4f1b240de4ae261065d309dbe73d9dceb42f" 527 | dependencies = [ 528 | "autocfg", 529 | "cc", 530 | "libc", 531 | "pkg-config", 532 | "vcpkg", 533 | ] 534 | 535 | [[package]] 536 | name = "os_str_bytes" 537 | version = "6.2.0" 538 | source = "registry+https://github.com/rust-lang/crates.io-index" 539 | checksum = "648001efe5d5c0102d8cea768e348da85d90af8ba91f0bea908f157951493cd4" 540 | 541 | [[package]] 542 | name = "parking_lot" 543 | version = "0.12.1" 544 | source = "registry+https://github.com/rust-lang/crates.io-index" 545 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" 546 | dependencies = [ 547 | "lock_api", 548 | "parking_lot_core", 549 | ] 550 | 551 | [[package]] 552 | name = "parking_lot_core" 553 | version = "0.9.3" 554 | source = "registry+https://github.com/rust-lang/crates.io-index" 555 | checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" 556 | dependencies = [ 557 | "cfg-if 1.0.0", 558 | "libc", 559 | "redox_syscall 0.2.16", 560 | "smallvec", 561 | "windows-sys", 562 | ] 563 | 564 | [[package]] 565 | name = "percent-encoding" 566 | version = "2.1.0" 567 | source = "registry+https://github.com/rust-lang/crates.io-index" 568 | checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" 569 | 570 | [[package]] 571 | name = "pin-project-lite" 572 | version = "0.2.9" 573 | source = "registry+https://github.com/rust-lang/crates.io-index" 574 | checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" 575 | 576 | [[package]] 577 | name = "pin-utils" 578 | version = "0.1.0" 579 | source = "registry+https://github.com/rust-lang/crates.io-index" 580 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 581 | 582 | [[package]] 583 | name = "pkg-config" 584 | version = "0.3.14" 585 | source = "registry+https://github.com/rust-lang/crates.io-index" 586 | checksum = "676e8eb2b1b4c9043511a9b7bea0915320d7e502b0a079fb03f9635a5252b18c" 587 | 588 | [[package]] 589 | name = "proc-macro2" 590 | version = "1.0.43" 591 | source = "registry+https://github.com/rust-lang/crates.io-index" 592 | checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" 593 | dependencies = [ 594 | "unicode-ident", 595 | ] 596 | 597 | [[package]] 598 | name = "quote" 599 | version = "1.0.21" 600 | source = "registry+https://github.com/rust-lang/crates.io-index" 601 | checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" 602 | dependencies = [ 603 | "proc-macro2", 604 | ] 605 | 606 | [[package]] 607 | name = "redox_syscall" 608 | version = "0.1.43" 609 | source = "registry+https://github.com/rust-lang/crates.io-index" 610 | checksum = "679da7508e9a6390aeaf7fbd02a800fdc64b73fe2204dd2c8ae66d22d9d5ad5d" 611 | 612 | [[package]] 613 | name = "redox_syscall" 614 | version = "0.2.16" 615 | source = "registry+https://github.com/rust-lang/crates.io-index" 616 | checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" 617 | dependencies = [ 618 | "bitflags", 619 | ] 620 | 621 | [[package]] 622 | name = "redox_termios" 623 | version = "0.1.1" 624 | source = "registry+https://github.com/rust-lang/crates.io-index" 625 | checksum = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" 626 | dependencies = [ 627 | "redox_syscall 0.1.43", 628 | ] 629 | 630 | [[package]] 631 | name = "regex" 632 | version = "1.6.0" 633 | source = "registry+https://github.com/rust-lang/crates.io-index" 634 | checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" 635 | dependencies = [ 636 | "aho-corasick", 637 | "memchr", 638 | "regex-syntax", 639 | ] 640 | 641 | [[package]] 642 | name = "regex-syntax" 643 | version = "0.6.27" 644 | source = "registry+https://github.com/rust-lang/crates.io-index" 645 | checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" 646 | 647 | [[package]] 648 | name = "remove_dir_all" 649 | version = "0.5.1" 650 | source = "registry+https://github.com/rust-lang/crates.io-index" 651 | checksum = "3488ba1b9a2084d38645c4c08276a1752dcbf2c7130d74f1569681ad5d2799c5" 652 | dependencies = [ 653 | "winapi", 654 | ] 655 | 656 | [[package]] 657 | name = "reqwest" 658 | version = "0.11.11" 659 | source = "registry+https://github.com/rust-lang/crates.io-index" 660 | checksum = "b75aa69a3f06bbcc66ede33af2af253c6f7a86b1ca0033f60c580a27074fbf92" 661 | dependencies = [ 662 | "base64", 663 | "bytes", 664 | "encoding_rs", 665 | "futures-core", 666 | "futures-util", 667 | "h2", 668 | "http", 669 | "http-body", 670 | "hyper", 671 | "hyper-tls", 672 | "ipnet", 673 | "js-sys", 674 | "lazy_static", 675 | "log", 676 | "mime", 677 | "native-tls", 678 | "percent-encoding", 679 | "pin-project-lite", 680 | "serde", 681 | "serde_json", 682 | "serde_urlencoded", 683 | "tokio", 684 | "tokio-native-tls", 685 | "tower-service", 686 | "url", 687 | "wasm-bindgen", 688 | "wasm-bindgen-futures", 689 | "web-sys", 690 | "winreg", 691 | ] 692 | 693 | [[package]] 694 | name = "ryu" 695 | version = "1.0.11" 696 | source = "registry+https://github.com/rust-lang/crates.io-index" 697 | checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" 698 | 699 | [[package]] 700 | name = "schannel" 701 | version = "0.1.20" 702 | source = "registry+https://github.com/rust-lang/crates.io-index" 703 | checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" 704 | dependencies = [ 705 | "lazy_static", 706 | "windows-sys", 707 | ] 708 | 709 | [[package]] 710 | name = "scopeguard" 711 | version = "1.1.0" 712 | source = "registry+https://github.com/rust-lang/crates.io-index" 713 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 714 | 715 | [[package]] 716 | name = "security-framework" 717 | version = "2.6.1" 718 | source = "registry+https://github.com/rust-lang/crates.io-index" 719 | checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc" 720 | dependencies = [ 721 | "bitflags", 722 | "core-foundation", 723 | "core-foundation-sys", 724 | "libc", 725 | "security-framework-sys", 726 | ] 727 | 728 | [[package]] 729 | name = "security-framework-sys" 730 | version = "2.6.1" 731 | source = "registry+https://github.com/rust-lang/crates.io-index" 732 | checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" 733 | dependencies = [ 734 | "core-foundation-sys", 735 | "libc", 736 | ] 737 | 738 | [[package]] 739 | name = "serde" 740 | version = "1.0.143" 741 | source = "registry+https://github.com/rust-lang/crates.io-index" 742 | checksum = "53e8e5d5b70924f74ff5c6d64d9a5acd91422117c60f48c4e07855238a254553" 743 | 744 | [[package]] 745 | name = "serde_derive" 746 | version = "1.0.143" 747 | source = "registry+https://github.com/rust-lang/crates.io-index" 748 | checksum = "d3d8e8de557aee63c26b85b947f5e59b690d0454c753f3adeb5cd7835ab88391" 749 | dependencies = [ 750 | "proc-macro2", 751 | "quote", 752 | "syn", 753 | ] 754 | 755 | [[package]] 756 | name = "serde_json" 757 | version = "1.0.83" 758 | source = "registry+https://github.com/rust-lang/crates.io-index" 759 | checksum = "38dd04e3c8279e75b31ef29dbdceebfe5ad89f4d0937213c53f7d49d01b3d5a7" 760 | dependencies = [ 761 | "itoa", 762 | "ryu", 763 | "serde", 764 | ] 765 | 766 | [[package]] 767 | name = "serde_urlencoded" 768 | version = "0.7.1" 769 | source = "registry+https://github.com/rust-lang/crates.io-index" 770 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 771 | dependencies = [ 772 | "form_urlencoded", 773 | "itoa", 774 | "ryu", 775 | "serde", 776 | ] 777 | 778 | [[package]] 779 | name = "signal-hook-registry" 780 | version = "1.4.0" 781 | source = "registry+https://github.com/rust-lang/crates.io-index" 782 | checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" 783 | dependencies = [ 784 | "libc", 785 | ] 786 | 787 | [[package]] 788 | name = "slab" 789 | version = "0.4.7" 790 | source = "registry+https://github.com/rust-lang/crates.io-index" 791 | checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" 792 | dependencies = [ 793 | "autocfg", 794 | ] 795 | 796 | [[package]] 797 | name = "smallvec" 798 | version = "1.9.0" 799 | source = "registry+https://github.com/rust-lang/crates.io-index" 800 | checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" 801 | 802 | [[package]] 803 | name = "socket2" 804 | version = "0.4.4" 805 | source = "registry+https://github.com/rust-lang/crates.io-index" 806 | checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" 807 | dependencies = [ 808 | "libc", 809 | "winapi", 810 | ] 811 | 812 | [[package]] 813 | name = "strsim" 814 | version = "0.10.0" 815 | source = "registry+https://github.com/rust-lang/crates.io-index" 816 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 817 | 818 | [[package]] 819 | name = "syn" 820 | version = "1.0.99" 821 | source = "registry+https://github.com/rust-lang/crates.io-index" 822 | checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" 823 | dependencies = [ 824 | "proc-macro2", 825 | "quote", 826 | "unicode-ident", 827 | ] 828 | 829 | [[package]] 830 | name = "tempfile" 831 | version = "3.3.0" 832 | source = "registry+https://github.com/rust-lang/crates.io-index" 833 | checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" 834 | dependencies = [ 835 | "cfg-if 1.0.0", 836 | "fastrand", 837 | "libc", 838 | "redox_syscall 0.2.16", 839 | "remove_dir_all", 840 | "winapi", 841 | ] 842 | 843 | [[package]] 844 | name = "termcolor" 845 | version = "1.1.3" 846 | source = "registry+https://github.com/rust-lang/crates.io-index" 847 | checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" 848 | dependencies = [ 849 | "winapi-util", 850 | ] 851 | 852 | [[package]] 853 | name = "terminal_size" 854 | version = "0.1.17" 855 | source = "registry+https://github.com/rust-lang/crates.io-index" 856 | checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" 857 | dependencies = [ 858 | "libc", 859 | "winapi", 860 | ] 861 | 862 | [[package]] 863 | name = "termion" 864 | version = "1.5.1" 865 | source = "registry+https://github.com/rust-lang/crates.io-index" 866 | checksum = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" 867 | dependencies = [ 868 | "libc", 869 | "redox_syscall 0.1.43", 870 | "redox_termios", 871 | ] 872 | 873 | [[package]] 874 | name = "textwrap" 875 | version = "0.15.0" 876 | source = "registry+https://github.com/rust-lang/crates.io-index" 877 | checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" 878 | 879 | [[package]] 880 | name = "tinyvec" 881 | version = "1.6.0" 882 | source = "registry+https://github.com/rust-lang/crates.io-index" 883 | checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" 884 | dependencies = [ 885 | "tinyvec_macros", 886 | ] 887 | 888 | [[package]] 889 | name = "tinyvec_macros" 890 | version = "0.1.0" 891 | source = "registry+https://github.com/rust-lang/crates.io-index" 892 | checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" 893 | 894 | [[package]] 895 | name = "tokio" 896 | version = "1.20.1" 897 | source = "registry+https://github.com/rust-lang/crates.io-index" 898 | checksum = "7a8325f63a7d4774dd041e363b2409ed1c5cbbd0f867795e661df066b2b0a581" 899 | dependencies = [ 900 | "autocfg", 901 | "bytes", 902 | "libc", 903 | "memchr", 904 | "mio", 905 | "num_cpus", 906 | "once_cell", 907 | "parking_lot", 908 | "pin-project-lite", 909 | "signal-hook-registry", 910 | "socket2", 911 | "tokio-macros", 912 | "winapi", 913 | ] 914 | 915 | [[package]] 916 | name = "tokio-macros" 917 | version = "1.8.0" 918 | source = "registry+https://github.com/rust-lang/crates.io-index" 919 | checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" 920 | dependencies = [ 921 | "proc-macro2", 922 | "quote", 923 | "syn", 924 | ] 925 | 926 | [[package]] 927 | name = "tokio-native-tls" 928 | version = "0.3.0" 929 | source = "registry+https://github.com/rust-lang/crates.io-index" 930 | checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" 931 | dependencies = [ 932 | "native-tls", 933 | "tokio", 934 | ] 935 | 936 | [[package]] 937 | name = "tokio-util" 938 | version = "0.7.3" 939 | source = "registry+https://github.com/rust-lang/crates.io-index" 940 | checksum = "cc463cd8deddc3770d20f9852143d50bf6094e640b485cb2e189a2099085ff45" 941 | dependencies = [ 942 | "bytes", 943 | "futures-core", 944 | "futures-sink", 945 | "pin-project-lite", 946 | "tokio", 947 | "tracing", 948 | ] 949 | 950 | [[package]] 951 | name = "tower-service" 952 | version = "0.3.2" 953 | source = "registry+https://github.com/rust-lang/crates.io-index" 954 | checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" 955 | 956 | [[package]] 957 | name = "tracing" 958 | version = "0.1.36" 959 | source = "registry+https://github.com/rust-lang/crates.io-index" 960 | checksum = "2fce9567bd60a67d08a16488756721ba392f24f29006402881e43b19aac64307" 961 | dependencies = [ 962 | "cfg-if 1.0.0", 963 | "pin-project-lite", 964 | "tracing-core", 965 | ] 966 | 967 | [[package]] 968 | name = "tracing-core" 969 | version = "0.1.29" 970 | source = "registry+https://github.com/rust-lang/crates.io-index" 971 | checksum = "5aeea4303076558a00714b823f9ad67d58a3bbda1df83d8827d21193156e22f7" 972 | dependencies = [ 973 | "once_cell", 974 | ] 975 | 976 | [[package]] 977 | name = "try-lock" 978 | version = "0.2.2" 979 | source = "registry+https://github.com/rust-lang/crates.io-index" 980 | checksum = "e604eb7b43c06650e854be16a2a03155743d3752dd1c943f6829e26b7a36e382" 981 | 982 | [[package]] 983 | name = "tumblr-likes" 984 | version = "0.3.5" 985 | dependencies = [ 986 | "clap", 987 | "indicatif", 988 | "regex", 989 | "reqwest", 990 | "serde", 991 | "serde_derive", 992 | "serde_json", 993 | "tokio", 994 | ] 995 | 996 | [[package]] 997 | name = "unicode-bidi" 998 | version = "0.3.4" 999 | source = "registry+https://github.com/rust-lang/crates.io-index" 1000 | checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" 1001 | dependencies = [ 1002 | "matches", 1003 | ] 1004 | 1005 | [[package]] 1006 | name = "unicode-ident" 1007 | version = "1.0.3" 1008 | source = "registry+https://github.com/rust-lang/crates.io-index" 1009 | checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" 1010 | 1011 | [[package]] 1012 | name = "unicode-normalization" 1013 | version = "0.1.21" 1014 | source = "registry+https://github.com/rust-lang/crates.io-index" 1015 | checksum = "854cbdc4f7bc6ae19c820d44abdc3277ac3e1b2b93db20a636825d9322fb60e6" 1016 | dependencies = [ 1017 | "tinyvec", 1018 | ] 1019 | 1020 | [[package]] 1021 | name = "unicode-width" 1022 | version = "0.1.5" 1023 | source = "registry+https://github.com/rust-lang/crates.io-index" 1024 | checksum = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" 1025 | 1026 | [[package]] 1027 | name = "url" 1028 | version = "2.2.2" 1029 | source = "registry+https://github.com/rust-lang/crates.io-index" 1030 | checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" 1031 | dependencies = [ 1032 | "form_urlencoded", 1033 | "idna", 1034 | "matches", 1035 | "percent-encoding", 1036 | ] 1037 | 1038 | [[package]] 1039 | name = "vcpkg" 1040 | version = "0.2.15" 1041 | source = "registry+https://github.com/rust-lang/crates.io-index" 1042 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 1043 | 1044 | [[package]] 1045 | name = "want" 1046 | version = "0.3.0" 1047 | source = "registry+https://github.com/rust-lang/crates.io-index" 1048 | checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" 1049 | dependencies = [ 1050 | "log", 1051 | "try-lock", 1052 | ] 1053 | 1054 | [[package]] 1055 | name = "wasi" 1056 | version = "0.11.0+wasi-snapshot-preview1" 1057 | source = "registry+https://github.com/rust-lang/crates.io-index" 1058 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1059 | 1060 | [[package]] 1061 | name = "wasm-bindgen" 1062 | version = "0.2.82" 1063 | source = "registry+https://github.com/rust-lang/crates.io-index" 1064 | checksum = "fc7652e3f6c4706c8d9cd54832c4a4ccb9b5336e2c3bd154d5cccfbf1c1f5f7d" 1065 | dependencies = [ 1066 | "cfg-if 1.0.0", 1067 | "wasm-bindgen-macro", 1068 | ] 1069 | 1070 | [[package]] 1071 | name = "wasm-bindgen-backend" 1072 | version = "0.2.82" 1073 | source = "registry+https://github.com/rust-lang/crates.io-index" 1074 | checksum = "662cd44805586bd52971b9586b1df85cdbbd9112e4ef4d8f41559c334dc6ac3f" 1075 | dependencies = [ 1076 | "bumpalo", 1077 | "log", 1078 | "once_cell", 1079 | "proc-macro2", 1080 | "quote", 1081 | "syn", 1082 | "wasm-bindgen-shared", 1083 | ] 1084 | 1085 | [[package]] 1086 | name = "wasm-bindgen-futures" 1087 | version = "0.4.32" 1088 | source = "registry+https://github.com/rust-lang/crates.io-index" 1089 | checksum = "fa76fb221a1f8acddf5b54ace85912606980ad661ac7a503b4570ffd3a624dad" 1090 | dependencies = [ 1091 | "cfg-if 1.0.0", 1092 | "js-sys", 1093 | "wasm-bindgen", 1094 | "web-sys", 1095 | ] 1096 | 1097 | [[package]] 1098 | name = "wasm-bindgen-macro" 1099 | version = "0.2.82" 1100 | source = "registry+https://github.com/rust-lang/crates.io-index" 1101 | checksum = "b260f13d3012071dfb1512849c033b1925038373aea48ced3012c09df952c602" 1102 | dependencies = [ 1103 | "quote", 1104 | "wasm-bindgen-macro-support", 1105 | ] 1106 | 1107 | [[package]] 1108 | name = "wasm-bindgen-macro-support" 1109 | version = "0.2.82" 1110 | source = "registry+https://github.com/rust-lang/crates.io-index" 1111 | checksum = "5be8e654bdd9b79216c2929ab90721aa82faf65c48cdf08bdc4e7f51357b80da" 1112 | dependencies = [ 1113 | "proc-macro2", 1114 | "quote", 1115 | "syn", 1116 | "wasm-bindgen-backend", 1117 | "wasm-bindgen-shared", 1118 | ] 1119 | 1120 | [[package]] 1121 | name = "wasm-bindgen-shared" 1122 | version = "0.2.82" 1123 | source = "registry+https://github.com/rust-lang/crates.io-index" 1124 | checksum = "6598dd0bd3c7d51095ff6531a5b23e02acdc81804e30d8f07afb77b7215a140a" 1125 | 1126 | [[package]] 1127 | name = "web-sys" 1128 | version = "0.3.59" 1129 | source = "registry+https://github.com/rust-lang/crates.io-index" 1130 | checksum = "ed055ab27f941423197eb86b2035720b1a3ce40504df082cac2ecc6ed73335a1" 1131 | dependencies = [ 1132 | "js-sys", 1133 | "wasm-bindgen", 1134 | ] 1135 | 1136 | [[package]] 1137 | name = "winapi" 1138 | version = "0.3.9" 1139 | source = "registry+https://github.com/rust-lang/crates.io-index" 1140 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1141 | dependencies = [ 1142 | "winapi-i686-pc-windows-gnu", 1143 | "winapi-x86_64-pc-windows-gnu", 1144 | ] 1145 | 1146 | [[package]] 1147 | name = "winapi-i686-pc-windows-gnu" 1148 | version = "0.4.0" 1149 | source = "registry+https://github.com/rust-lang/crates.io-index" 1150 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1151 | 1152 | [[package]] 1153 | name = "winapi-util" 1154 | version = "0.1.5" 1155 | source = "registry+https://github.com/rust-lang/crates.io-index" 1156 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 1157 | dependencies = [ 1158 | "winapi", 1159 | ] 1160 | 1161 | [[package]] 1162 | name = "winapi-x86_64-pc-windows-gnu" 1163 | version = "0.4.0" 1164 | source = "registry+https://github.com/rust-lang/crates.io-index" 1165 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1166 | 1167 | [[package]] 1168 | name = "windows-sys" 1169 | version = "0.36.1" 1170 | source = "registry+https://github.com/rust-lang/crates.io-index" 1171 | checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" 1172 | dependencies = [ 1173 | "windows_aarch64_msvc", 1174 | "windows_i686_gnu", 1175 | "windows_i686_msvc", 1176 | "windows_x86_64_gnu", 1177 | "windows_x86_64_msvc", 1178 | ] 1179 | 1180 | [[package]] 1181 | name = "windows_aarch64_msvc" 1182 | version = "0.36.1" 1183 | source = "registry+https://github.com/rust-lang/crates.io-index" 1184 | checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" 1185 | 1186 | [[package]] 1187 | name = "windows_i686_gnu" 1188 | version = "0.36.1" 1189 | source = "registry+https://github.com/rust-lang/crates.io-index" 1190 | checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" 1191 | 1192 | [[package]] 1193 | name = "windows_i686_msvc" 1194 | version = "0.36.1" 1195 | source = "registry+https://github.com/rust-lang/crates.io-index" 1196 | checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" 1197 | 1198 | [[package]] 1199 | name = "windows_x86_64_gnu" 1200 | version = "0.36.1" 1201 | source = "registry+https://github.com/rust-lang/crates.io-index" 1202 | checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" 1203 | 1204 | [[package]] 1205 | name = "windows_x86_64_msvc" 1206 | version = "0.36.1" 1207 | source = "registry+https://github.com/rust-lang/crates.io-index" 1208 | checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" 1209 | 1210 | [[package]] 1211 | name = "winreg" 1212 | version = "0.10.1" 1213 | source = "registry+https://github.com/rust-lang/crates.io-index" 1214 | checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" 1215 | dependencies = [ 1216 | "winapi", 1217 | ] 1218 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tumblr-likes" 3 | version = "0.3.5" 4 | authors = ["Alex Taylor "] 5 | description = "Downloads your liked photos and videos on Tumblr." 6 | readme = "README.md" 7 | categories = ["command-line-utilities"] 8 | keywords = [ 9 | "tumblr", 10 | "downloader", 11 | "download", 12 | "photo", 13 | "video", 14 | ] 15 | homepage = "https://github.com/subnomo/tumblr-likes" 16 | repository = "https://github.com/subnomo/tumblr-likes" 17 | license = "MIT" 18 | edition = "2018" 19 | 20 | [dependencies] 21 | clap = { version = "3.2.17", features = ["cargo"] } 22 | indicatif = "0.17.0" 23 | regex = "1.6.0" 24 | reqwest = { version = "0.11", features = ["json"] } 25 | serde = "1.0.143" 26 | serde_derive = "1.0.143" 27 | serde_json = "1.0.83" 28 | tokio = { version = "1", features = ["full"] } 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2018 Alex Taylor 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a 4 | copy of this software and associated documentation files (the "Software"), 5 | to deal in the Software without restriction, including without limitation the 6 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 7 | sell copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 16 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tumblr-likes 2 | [![Crates.io](https://img.shields.io/crates/v/tumblr-likes.svg)](https://crates.io/crates/tumblr-likes) 3 | 4 | ### A command-line program for downloading liked posts from Tumblr. 5 | 6 | ![Example of exported HTML](https://i.imgur.com/8WAxBit.png) 7 | 8 | ## Installation 9 | 10 | Download the [latest release for your platform](https://github.com/subnomo/tumblr-likes/releases). 11 | 12 | Or, using cargo: 13 | 14 | ``` 15 | $ cargo install tumblr-likes 16 | ``` 17 | 18 | ## Configuration 19 | 20 | In order to download liked posts, you will need an API key. Your blog must be configured to share likes publicly, this can be done by going to your blog's "edit appearance" menu. 21 | 22 | 1. [Register an application with the Tumblr API](https://www.tumblr.com/oauth/apps). The name and other options don't matter. 23 | 2. Click "Expore API" under the application you just created 24 | 3. Click "Allow" 25 | 4. In the upper right, click "Show Keys" 26 | 5. Copy the API key shown 27 | 28 | ## Usage 29 | 30 | On the command line: 31 | 32 | ``` 33 | $ tumblr-likes -a -b 34 | ``` 35 | 36 | **To export posts to html**: 37 | 38 | ``` 39 | $ tumblr-likes -a -b --export likes.html 40 | ``` 41 | 42 | If you don't want to provide the API key every time, you can save it into an environmental variable `$TUMBLR_API_KEY` instead. 43 | 44 | By default, liked posts will be downloaded into a `downloads` folder in the current directory. You can use `-d` to set a custom output directory. 45 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use clap::{crate_version, App, Arg}; 2 | use indicatif::ProgressBar; 3 | use regex::Regex; 4 | use std::env; 5 | use std::error::Error; 6 | use std::fs::{self, File}; 7 | use std::io::{BufReader, Write}; 8 | use std::path::{Path, PathBuf}; 9 | 10 | mod types; 11 | mod util; 12 | 13 | use crate::types::*; 14 | use crate::util::*; 15 | 16 | #[derive(Debug)] 17 | pub struct Arguments { 18 | api_key: String, 19 | blog_name: String, 20 | directory: String, 21 | dump: Option, 22 | restore: Option, 23 | export: Option, 24 | verbose: bool, 25 | } 26 | 27 | fn cli() -> Arguments { 28 | let env_key = env::var("TUMBLR_API_KEY"); 29 | 30 | let matches = App::new("tumblr-likes") 31 | .version(crate_version!()) 32 | .author("Alex Taylor ") 33 | .about("Downloads your liked photos and videos on Tumblr.") 34 | .arg( 35 | Arg::with_name("API_KEY") 36 | .short('a') 37 | .help("Your Tumblr API key") 38 | .takes_value(true) 39 | .required(env_key.is_err()) 40 | .conflicts_with("JSON_RESTORE"), 41 | ) 42 | .arg( 43 | Arg::with_name("BLOG_NAME") 44 | .short('b') 45 | .help("The blog to download likes from") 46 | .takes_value(true) 47 | .required(true) 48 | .conflicts_with("JSON_RESTORE"), 49 | ) 50 | .arg( 51 | Arg::with_name("OUTPUT_DIR") 52 | .short('d') 53 | .long("dir") 54 | .help("The download directory") 55 | .takes_value(true), 56 | ) 57 | .arg( 58 | Arg::with_name("JSON_DUMP") 59 | .long("dump") 60 | .help("Dumps liked posts into the given JSON file") 61 | .takes_value(true) 62 | .conflicts_with("JSON_RESTORE"), 63 | ) 64 | .arg( 65 | Arg::with_name("JSON_RESTORE") 66 | .long("restore") 67 | .help("Restores liked posts from given JSON file") 68 | .takes_value(true), 69 | ) 70 | .arg( 71 | Arg::with_name("HTML_FILE") 72 | .long("export") 73 | .short('e') 74 | .help("Exports liked posts into the given HTML file") 75 | .takes_value(true), 76 | ) 77 | .arg( 78 | Arg::with_name("verbose") 79 | .short('v') 80 | .long("verbose") 81 | .help("Prints extra information, used for debugging"), 82 | ) 83 | .get_matches(); 84 | 85 | Arguments { 86 | api_key: match matches.value_of("API_KEY") { 87 | Some(a) => a.to_string(), 88 | None => { 89 | if !env_key.is_err() { 90 | env_key.unwrap().to_string() 91 | } else { 92 | "".to_string() 93 | } 94 | } 95 | }, 96 | 97 | blog_name: match matches.value_of("BLOG_NAME") { 98 | Some(b) => b.to_string(), 99 | None => "".to_string(), 100 | }, 101 | 102 | directory: match matches.value_of("OUTPUT_DIR") { 103 | Some(d) => d.to_string(), 104 | None => "downloads".to_string(), 105 | }, 106 | 107 | dump: matches.value_of("JSON_DUMP").map(|s| s.to_string()), 108 | restore: matches.value_of("JSON_RESTORE").map(|s| s.to_string()), 109 | export: matches.value_of("HTML_FILE").map(|s| s.to_string()), 110 | verbose: matches.is_present("verbose"), 111 | } 112 | } 113 | 114 | #[tokio::main] 115 | async fn main() -> Result<(), Box> { 116 | let args = cli(); 117 | let client = reqwest::Client::new(); 118 | let bar; 119 | let mut all_posts: Vec = Vec::new(); 120 | let mut files: Vec>> = Vec::new(); 121 | 122 | if !args.restore.is_none() { 123 | if args.verbose { 124 | println!("Restoring dump..."); 125 | } 126 | 127 | let posts = restore_dump(args.restore.clone().unwrap())?; 128 | bar = ProgressBar::new(posts.len() as _); 129 | 130 | // If not exporting, just do a download 131 | if args.export.is_none() { 132 | if args.verbose { 133 | println!("Downloading posts..."); 134 | } 135 | 136 | files = download_posts(posts, &client, &args, &bar).await?; 137 | } else { 138 | all_posts = posts; 139 | } 140 | } else { 141 | let info_url = build_url(&args, true, None); 142 | 143 | if args.verbose { 144 | println!("Info URL: {}", info_url); 145 | } 146 | 147 | let info = client.get(&info_url).send().await?; 148 | 149 | if args.verbose { 150 | println!("{:#?}", info); 151 | } 152 | 153 | if !info.status().is_success() { 154 | println!( 155 | "There was an error fetching your likes. Please make sure \ 156 | you provided the correct API key and blog name." 157 | ); 158 | return Ok(()); 159 | } 160 | 161 | let info: ReturnVal = info.json().await?; 162 | 163 | if args.verbose { 164 | println!("Info: {:#?}", info); 165 | } 166 | 167 | bar = ProgressBar::new(info.response.liked_count as _); 168 | 169 | // Setup directory if not in export mode 170 | if args.export.is_none() { 171 | setup_directory(&args); 172 | } 173 | 174 | // Do rip 175 | let mut before = None; 176 | 177 | if args.verbose { 178 | println!("Downloading likes..."); 179 | } 180 | 181 | loop { 182 | let url = build_url(&args, false, before); 183 | 184 | let mut res: ReturnVal = client.get(&url).send().await?.json().await?; 185 | let links = res.response._links; 186 | 187 | if !args.dump.is_none() || !args.export.is_none() { 188 | // If dumping or exporting, we need to collect every post 189 | all_posts.append(&mut res.response.liked_posts); 190 | } else { 191 | files.append( 192 | &mut download_posts(res.response.liked_posts, &client, &args, &bar).await?, 193 | ); 194 | } 195 | 196 | if let Some(l) = links { 197 | before = if let Some(next) = l.next { 198 | Some(next.query_params.before) 199 | } else { 200 | break; 201 | }; 202 | } else { 203 | break; 204 | } 205 | } 206 | } 207 | 208 | // Dump 209 | if let Some(dump_file) = args.dump { 210 | dump(all_posts, dump_file); 211 | return Ok(()); 212 | } 213 | 214 | // Export 215 | if let Some(export_file) = args.export { 216 | export(&client, all_posts, export_file, &bar, args.verbose).await; 217 | bar.finish(); 218 | return Ok(()); 219 | } 220 | 221 | // Rename files with index 222 | 223 | if args.verbose { 224 | println!("Renaming files...\n"); 225 | } 226 | 227 | rename(files); 228 | bar.finish(); 229 | 230 | Ok(()) 231 | } 232 | 233 | async fn download_posts( 234 | posts: Vec, 235 | client: &reqwest::Client, 236 | args: &Arguments, 237 | bar: &ProgressBar, 238 | ) -> Result>>, Box> { 239 | let mut files: Vec>> = Vec::new(); 240 | 241 | for post in posts { 242 | let mut post_files: Vec> = Vec::new(); 243 | 244 | if post.kind == "photo" { 245 | if let Some(photos) = post.photos { 246 | for photo in photos { 247 | post_files.push(download(client, args, "pics", photo.original_size.url).await?); 248 | } 249 | } 250 | } else if post.kind == "video" { 251 | if let Some(url) = post.video_url { 252 | post_files.push(download(&client, &args, "videos", url).await?); 253 | } 254 | } 255 | 256 | files.push(post_files); 257 | bar.inc(1); 258 | } 259 | 260 | Ok(files) 261 | } 262 | 263 | fn rename(files: Vec>>) { 264 | for (i, post) in files.iter().rev().enumerate() { 265 | for file in post { 266 | if let Some(file) = file { 267 | let filename = &file.file_name().unwrap().to_str().unwrap(); 268 | 269 | let mut new_file = file.clone(); 270 | new_file.set_file_name(format!("{} - {}", i + 1, filename)); 271 | 272 | fs::rename(&file, new_file).unwrap_or_else(|e| { 273 | panic!("Could not rename file! Error: {}", e); 274 | }); 275 | } 276 | } 277 | } 278 | } 279 | 280 | fn dump(posts: Vec, file: String) { 281 | let path = Path::new(&file); 282 | let display = path.display(); 283 | 284 | let mut file = match File::create(&path) { 285 | Ok(f) => f, 286 | Err(e) => panic!("Couldn't create file {}: {}", display, e), 287 | }; 288 | 289 | let json = serde_json::to_string(&posts).unwrap(); 290 | 291 | match file.write_all(json.as_bytes()) { 292 | Ok(_) => println!("Dumped liked post data to {}.", display), 293 | Err(e) => panic!("Couldn't write to {}: {}", display, e), 294 | } 295 | } 296 | 297 | fn restore_dump(file: String) -> Result, Box> { 298 | let path = Path::new(&file); 299 | let file = File::open(path)?; 300 | let reader = BufReader::new(file); 301 | 302 | let res: Vec = serde_json::from_reader(reader)?; 303 | Ok(res) 304 | } 305 | 306 | static HTML_TEMPLATE: &'static str = " 307 | 308 | 309 | 310 | 311 | Tumblr Likes 312 | 313 | 323 | 324 | 325 |
326 | {{cards}} 327 |
328 | 329 | 330 | "; 331 | 332 | static CARD_TEMPLATE: &'static str = "
333 |
334 |
335 | {{title}} 336 |
337 |
338 | 339 |
340 |
341 | {{body}} 342 |
343 | {{tags}} 344 |
345 | {{date}} 346 | {{note_count}} notes 347 |
348 |
349 |
350 | "; 351 | 352 | async fn export( 353 | client: &reqwest::Client, 354 | posts: Vec, 355 | file: String, 356 | bar: &ProgressBar, 357 | verbose: bool, 358 | ) { 359 | // Create export directory 360 | fs::create_dir_all("export").expect("Could not create export directory!"); 361 | 362 | if verbose { 363 | println!("Exporting your liked posts..."); 364 | } 365 | 366 | let mut posts_html = String::new(); 367 | 368 | for post in posts { 369 | let title = format!("{}", post.post_url, post.blog_name); 370 | let mut card = CARD_TEMPLATE.replace("{{title}}", &title); 371 | 372 | if post.tags.len() > 0 { 373 | let tags = format!( 374 | "
{}
", 375 | post.tags.join("") 376 | ); 377 | card = card.replace("{{tags}}", &tags); 378 | } else { 379 | card = card.replace("{{tags}}", ""); 380 | } 381 | 382 | card = card.replace("{{date}}", &post.date); 383 | card = card.replace("{{note_count}}", &post.note_count.to_string()); 384 | 385 | if post.kind == "text" { 386 | if let Some(body) = post.body { 387 | let mut content = body.clone(); 388 | 389 | // Extract URLs from body 390 | let re = Regex::new(r#"src="([^"]+)"#).unwrap(); 391 | let caps = re.captures_iter(&body); 392 | 393 | // Replace all objects with locally stored ones 394 | for cap in caps { 395 | let url = cap.get(1).unwrap().as_str().to_string(); 396 | let split: Vec<&str> = url.split("/").collect(); 397 | let filename = split.last().unwrap(); 398 | 399 | let dl = 400 | download_url(&client, url.clone(), format!("export/{}", filename)).await; 401 | 402 | content = content.replace( 403 | &url, 404 | &inject_content(dl, "Could not fetch object", |path| { 405 | let src = path.to_str().unwrap(); 406 | src.to_string() 407 | }), 408 | ); 409 | } 410 | 411 | card = card.replace("{{body}}", &content); 412 | posts_html = format!("{}{}", posts_html, card); 413 | } 414 | } else if post.kind == "video" { 415 | let mut body = String::new(); 416 | 417 | if let Some(trail) = post.trail { 418 | let mut trail_content = render_trail(trail); 419 | 420 | // Inject video 421 | if let Some(url) = post.video_url { 422 | let split: Vec<&str> = url.split("/").collect(); 423 | let filename = split.last().unwrap(); 424 | 425 | let dl = 426 | download_url(&client, url.clone(), format!("export/{}", filename)).await; 427 | 428 | trail_content = trail_content.replace( 429 | "{{content}}", 430 | &inject_content(dl, "Could not fetch video", |path| { 431 | let src = path.to_str().unwrap(); 432 | let video = format!( 433 | "

", 435 | src 436 | ); 437 | 438 | video 439 | }), 440 | ); 441 | } 442 | 443 | trail_content = trail_content.replace("{{content}}", ""); 444 | body = trail_content; 445 | } 446 | 447 | card = card.replace("{{body}}", &body); 448 | posts_html = format!("{}{}", posts_html, card); 449 | } else if post.kind == "photo" { 450 | let mut body = String::new(); 451 | 452 | if let Some(trail) = post.trail { 453 | let mut trail_content = render_trail(trail); 454 | 455 | // Inject photos 456 | if let Some(photos) = post.photos { 457 | for photo in photos { 458 | let url = photo.original_size.url; 459 | let split: Vec<&str> = url.split("/").collect(); 460 | let filename = split.last().unwrap(); 461 | let dl = download_url(&client, url.clone(), format!("export/{}", filename)) 462 | .await; 463 | 464 | trail_content = trail_content.replace( 465 | "{{content}}", 466 | &inject_content(dl, "Could not fetch photo", |path| { 467 | let src = path.to_str().unwrap(); 468 | let img = format!( 469 | "
{{{{content}}}}", 470 | src 471 | ); 472 | 473 | img 474 | }), 475 | ); 476 | } 477 | } 478 | 479 | trail_content = trail_content.replace("{{content}}", ""); 480 | body = trail_content; 481 | } 482 | 483 | card = card.replace("{{body}}", &body); 484 | posts_html = format!("{}{}", posts_html, card); 485 | } 486 | 487 | bar.inc(1); 488 | } 489 | 490 | // Write to html file 491 | let out = HTML_TEMPLATE.replace("{{cards}}", &posts_html); 492 | 493 | let path = Path::new(&file); 494 | let display = path.display(); 495 | 496 | let mut file = match File::create(&path) { 497 | Ok(f) => f, 498 | Err(e) => panic!("Couldn't create file {}: {}", display, e), 499 | }; 500 | 501 | match file.write_all(out.as_bytes()) { 502 | Ok(_) => { 503 | if verbose { 504 | println!("Exported liked posts to {}.", display) 505 | } 506 | } 507 | Err(e) => panic!("Couldn't write to {}: {}", display, e), 508 | } 509 | } 510 | -------------------------------------------------------------------------------- /src/types.rs: -------------------------------------------------------------------------------- 1 | use serde_derive::{Deserialize, Serialize}; 2 | 3 | #[derive(Debug, Serialize, Deserialize, Clone)] 4 | pub struct BlogItem { 5 | pub name: String, 6 | pub active: bool, 7 | } 8 | 9 | #[derive(Debug, Serialize, Deserialize, Clone)] 10 | pub struct PostItem { 11 | pub id: String, 12 | } 13 | 14 | #[derive(Debug, Serialize, Deserialize, Clone)] 15 | pub struct TrailItem { 16 | pub blog: BlogItem, 17 | pub post: PostItem, 18 | pub content_raw: String, 19 | } 20 | 21 | #[derive(Debug, Serialize, Deserialize, Clone)] 22 | pub struct Photo { 23 | pub url: String, 24 | pub width: i32, 25 | pub height: i32, 26 | } 27 | 28 | #[derive(Debug, Serialize, Deserialize, Clone)] 29 | pub struct Photos { 30 | pub caption: String, 31 | pub original_size: Photo, 32 | } 33 | 34 | #[derive(Debug, Serialize, Deserialize, Clone)] 35 | pub struct Post { 36 | pub blog_name: String, 37 | pub id: u64, 38 | pub post_url: String, 39 | #[serde(rename = "type")] 40 | pub kind: String, 41 | pub timestamp: u64, 42 | pub date: String, 43 | pub format: String, 44 | pub note_count: u64, 45 | pub reblog_key: String, 46 | pub tags: Vec, 47 | pub body: Option, 48 | pub trail: Option>, 49 | pub photos: Option>, 50 | pub video_url: Option, 51 | } 52 | 53 | #[derive(Debug, Serialize, Deserialize)] 54 | pub struct QueryParams { 55 | pub limit: String, 56 | pub before: String, 57 | } 58 | 59 | #[derive(Debug, Serialize, Deserialize)] 60 | pub struct LinksNext { 61 | pub query_params: QueryParams, 62 | } 63 | 64 | #[derive(Debug, Serialize, Deserialize)] 65 | pub struct Links { 66 | pub next: Option, 67 | } 68 | 69 | #[derive(Debug, Serialize, Deserialize)] 70 | pub struct Response { 71 | pub liked_count: i32, 72 | pub liked_posts: Vec, 73 | pub _links: Option, 74 | } 75 | 76 | #[derive(Debug, Serialize, Deserialize)] 77 | pub struct ReturnVal { 78 | pub response: Response, 79 | } 80 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | use std::fs::{self, File}; 2 | use std::path::{Path, PathBuf}; 3 | 4 | use crate::types::TrailItem; 5 | use crate::Arguments; 6 | 7 | pub fn build_url(args: &Arguments, one: bool, before: Option) -> String { 8 | let limit = if one { 1 } else { 20 }; 9 | 10 | let before = match before { 11 | Some(b) => format!("&before={}", b), 12 | _ => "".to_string(), 13 | }; 14 | 15 | format!( 16 | "https://api.tumblr.com/v2/blog/{}/likes?api_key={}&limit={}{}", 17 | args.blog_name, args.api_key, limit, before 18 | ) 19 | } 20 | 21 | pub fn setup_directory(args: &Arguments) { 22 | fs::create_dir_all(format!("{}/pics", args.directory)) 23 | .expect("Could not create download directory!"); 24 | 25 | fs::create_dir_all(format!("{}/videos", args.directory)) 26 | .expect("Could not create download directory!"); 27 | } 28 | 29 | pub fn exists(folder: String, name: String) -> bool { 30 | // Check if file containing name exists 31 | for file in fs::read_dir(folder).unwrap() { 32 | let file = file.unwrap().path(); 33 | let filename = match file.to_str() { 34 | Some(s) => s.to_string(), 35 | _ => continue, 36 | }; 37 | 38 | if filename.contains(&name) { 39 | return true; 40 | } 41 | } 42 | 43 | false 44 | } 45 | 46 | type DownloadResult = Result, reqwest::Error>; 47 | 48 | pub async fn download_url(client: &reqwest::Client, url: String, file: String) -> DownloadResult { 49 | let path = Path::new(&file); 50 | 51 | // Skip existing files 52 | if path.exists() { 53 | return Ok(Some(path.to_path_buf())); 54 | } 55 | 56 | let res = client.get(&url).send().await?; 57 | 58 | if res.status().is_success() { 59 | let mut f = File::create(path).expect("Could not create file!"); 60 | std::io::copy(&mut res.bytes().await?.as_ref(), &mut f).expect("Could not download file!"); 61 | 62 | return Ok(Some(path.to_path_buf())); 63 | } 64 | 65 | Ok(None) 66 | } 67 | 68 | pub async fn download( 69 | client: &reqwest::Client, 70 | args: &Arguments, 71 | folder: &str, 72 | url: String, 73 | ) -> Result, reqwest::Error> { 74 | let split: Vec<&str> = url.split("/").collect(); 75 | let filename = split.last().unwrap(); 76 | let folder = format!("{}/{}", args.directory, folder); 77 | let file = format!("{}/{}", folder, filename); 78 | 79 | // Skip already downloaded files 80 | if exists(folder, filename.to_string()) { 81 | return Ok(None); 82 | } 83 | 84 | download_url(&client, url.clone(), file).await 85 | } 86 | 87 | pub fn render_trail(trail: Vec) -> String { 88 | let mut trail_content = "{{content}}".to_string(); 89 | 90 | for item in trail.iter().rev() { 91 | let blog = &item.blog.name; 92 | let id = &item.post.id; 93 | let content = &item.content_raw; 94 | 95 | let url = format!("https://{}.tumblr.com/post/{}/", blog, id); 96 | let name = format!("

{}:

", url, blog); 97 | 98 | let combined = format!( 99 | "{}
{{{{content}}}}{}
", 100 | name, content 101 | ); 102 | 103 | trail_content = trail_content.replace("{{content}}", &combined); 104 | } 105 | 106 | trail_content 107 | } 108 | 109 | pub fn inject_content String>( 110 | raw: DownloadResult, 111 | error_text: &str, 112 | cb: F, 113 | ) -> String { 114 | return match raw { 115 | Ok(p) => match p { 116 | Some(path) => cb(path), 117 | None => error_text.to_string(), 118 | }, 119 | 120 | _ => error_text.to_string(), 121 | }; 122 | } 123 | --------------------------------------------------------------------------------