Compare commits
20 Commits
ws_stream
...
fix_token_
Author | SHA1 | Date | |
---|---|---|---|
6c2579cda9 | |||
d0c5a1d93a | |||
119b661639 | |||
a8e2430e34 | |||
e8c257f801 | |||
ea3d248766 | |||
5604786146 | |||
9f9ae45f6e | |||
3e459f5415 | |||
95373f8261 | |||
3609564db0 | |||
fcde6ff689 | |||
0f148c58d3 | |||
b1ef598f3c | |||
825e38b25f | |||
f50872a88c | |||
a445759a69 | |||
b578b717d7 | |||
78e51eb11f | |||
5d8529d6bb |
168
Cargo.lock
generated
168
Cargo.lock
generated
@ -25,9 +25,9 @@ checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.8.6"
|
||||
version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a"
|
||||
checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
@ -116,13 +116,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.75"
|
||||
version = "0.1.77"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fdf6721fb0140e4f897002dd086c06f6c27775df19cfe1fccb21181a48fd2c98"
|
||||
checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.43",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -148,9 +148,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.21.5"
|
||||
version = "0.21.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9"
|
||||
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
@ -160,9 +160,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.4.1"
|
||||
version = "2.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
|
||||
checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
@ -196,9 +196,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "1.8.0"
|
||||
version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "542f33a8835a0884b006a0c3df3dadd99c0c3f296ed26c2fdc8028e01ad6230c"
|
||||
checksum = "c48f0051a4b4c5e0b6d365cd04af53aeaa209e3cc15ec2cdb69e73cc87fbd0dc"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"serde",
|
||||
@ -269,18 +269,18 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.4.11"
|
||||
version = "4.4.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfaff671f6b22ca62406885ece523383b9b64022e341e53e009a62ebc47a45f2"
|
||||
checksum = "1e578d6ec4194633722ccf9544794b71b1385c3c027efe0c55db226fc880865c"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.4.11"
|
||||
version = "4.4.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a216b506622bb1d316cd51328dce24e07bdff4a6128a47c7e7fad11878d5adbb"
|
||||
checksum = "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
@ -329,9 +329,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.11"
|
||||
version = "0.2.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0"
|
||||
checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
@ -398,9 +398,9 @@ checksum = "7762d17f1241643615821a8455a0b2c3e803784b058693d990b11f2dce25a0ca"
|
||||
|
||||
[[package]]
|
||||
name = "deranged"
|
||||
version = "0.3.10"
|
||||
version = "0.3.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8eb30d70a07a3b04884d2677f06bec33509dc67ca60d92949e5535352d3191dc"
|
||||
checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
|
||||
dependencies = [
|
||||
"powerfmt",
|
||||
]
|
||||
@ -423,9 +423,9 @@ checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
|
||||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.10.1"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece"
|
||||
checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580"
|
||||
dependencies = [
|
||||
"humantime",
|
||||
"is-terminal",
|
||||
@ -574,9 +574,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.11"
|
||||
version = "0.2.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f"
|
||||
checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
@ -604,9 +604,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.3.22"
|
||||
version = "0.3.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178"
|
||||
checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
@ -642,9 +642,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.3.3"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
|
||||
checksum = "5d3d0e0f38255e7fa3cf31335b3a56f05febd18025f4db5ef7a0cfb4f8da651f"
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
@ -763,13 +763,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "is-terminal"
|
||||
version = "0.4.9"
|
||||
version = "0.4.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
|
||||
checksum = "0bad00257d07be169d870ab665980b06cdb366d792ad690bf2e76876dc503455"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"rustix",
|
||||
"windows-sys 0.48.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -780,9 +780,9 @@ checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.151"
|
||||
version = "0.2.152"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4"
|
||||
checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7"
|
||||
|
||||
[[package]]
|
||||
name = "libflate"
|
||||
@ -810,9 +810,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.4.12"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456"
|
||||
checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
|
||||
|
||||
[[package]]
|
||||
name = "lipsum"
|
||||
@ -842,9 +842,9 @@ checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.6.4"
|
||||
version = "2.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
|
||||
checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
|
||||
|
||||
[[package]]
|
||||
name = "mime"
|
||||
@ -1009,9 +1009,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.71"
|
||||
version = "1.0.76"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75cb1540fadbd5b8fbccc4dddad2734eba435053f725621c070711a14bb5f4b8"
|
||||
checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@ -1024,9 +1024,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.33"
|
||||
version = "1.0.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
|
||||
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
@ -1158,9 +1158,9 @@ checksum = "afab94fb28594581f62d981211a9a4d53cc8130bbcbbb89a0440d9b8e81a7746"
|
||||
|
||||
[[package]]
|
||||
name = "rust-embed"
|
||||
version = "8.1.0"
|
||||
version = "8.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "810294a8a4a0853d4118e3b94bb079905f2107c7fe979d8f0faae98765eb6378"
|
||||
checksum = "a82c0bbc10308ed323529fd3c1dce8badda635aa319a5ff0e6466f33b8101e3f"
|
||||
dependencies = [
|
||||
"rust-embed-impl",
|
||||
"rust-embed-utils",
|
||||
@ -1169,22 +1169,22 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rust-embed-impl"
|
||||
version = "8.1.0"
|
||||
version = "8.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfc144a1273124a67b8c1d7cd19f5695d1878b31569c0512f6086f0f4676604e"
|
||||
checksum = "6227c01b1783cdfee1bcf844eb44594cd16ec71c35305bf1c9fb5aade2735e16"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rust-embed-utils",
|
||||
"syn 2.0.43",
|
||||
"syn 2.0.48",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-embed-utils"
|
||||
version = "8.1.0"
|
||||
version = "8.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "816ccd4875431253d6bb54b804bcff4369cbde9bae33defde25fdf6c2ef91d40"
|
||||
checksum = "8cb0a25bfbb2d4b4402179c2cf030387d9990857ce08a32592c6238db9fa8665"
|
||||
dependencies = [
|
||||
"globset",
|
||||
"sha2",
|
||||
@ -1199,11 +1199,11 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.28"
|
||||
version = "0.38.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316"
|
||||
checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca"
|
||||
dependencies = [
|
||||
"bitflags 2.4.1",
|
||||
"bitflags 2.4.2",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
@ -1282,11 +1282,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "schannel"
|
||||
version = "0.1.22"
|
||||
version = "0.1.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88"
|
||||
checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534"
|
||||
dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1352,29 +1352,29 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.193"
|
||||
version = "1.0.195"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89"
|
||||
checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.193"
|
||||
version = "1.0.195"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
|
||||
checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.43",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.108"
|
||||
version = "1.0.111"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b"
|
||||
checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
@ -1392,9 +1392,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_yaml"
|
||||
version = "0.9.29"
|
||||
version = "0.9.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a15e0ef66bf939a7c890a0bf6d5a733c70202225f9888a89ed5c62298b019129"
|
||||
checksum = "b1bf28c79a99f70ee1f1d83d10c875d2e70618417fda01ad1785e027579d9d38"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"itoa",
|
||||
@ -1434,9 +1434,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.11.2"
|
||||
version = "1.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970"
|
||||
checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7"
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
@ -1473,9 +1473,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.43"
|
||||
version = "2.0.48"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee659fb5f3d355364e1f3e5bc10fb82068efbf824a1e9d1c9504244a6469ad53"
|
||||
checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -1484,44 +1484,44 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.8.1"
|
||||
version = "3.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5"
|
||||
checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"fastrand 2.0.1",
|
||||
"redox_syscall",
|
||||
"rustix",
|
||||
"windows-sys 0.48.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.4.0"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449"
|
||||
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.52"
|
||||
version = "1.0.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83a48fd946b02c0a526b2e9481c8e2a17755e47039164a86c4070446e3a4614d"
|
||||
checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.52"
|
||||
version = "1.0.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7fbe9b594d6568a6a1443250a7e67d80b74e1e96f6d1715e1e21cc1888291d3"
|
||||
checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.43",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1597,7 +1597,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.43",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1706,9 +1706,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.14"
|
||||
version = "0.3.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f2528f27a9eb2b21e69c95319b30bd0efd85d09c379741b0f78ea1d86be2416"
|
||||
checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
@ -1750,9 +1750,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.6.1"
|
||||
version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e395fcf16a7a3d8127ec99782007af141946b4795001f876d54fb0d55978560"
|
||||
checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
@ -1968,9 +1968,9 @@ checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.5.30"
|
||||
version = "0.5.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b5c3db89721d50d0e2a673f5043fc4722f76dcc352d7b1ab8b8288bed4ed2c5"
|
||||
checksum = "b7cf47b659b318dccbd69cc4797a39ae128f533dce7902a1096044d1967b9c16"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
@ -1992,5 +1992,5 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.43",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
46
README.md
46
README.md
@ -1,15 +1,12 @@
|
||||
# Redlib
|
||||
|
||||
> An alternative private front-end to Reddit
|
||||
|
||||
# ⚠️ Why do I get TOO MANY REQUESTS errors? ⚠️
|
||||
#### As of July 12th, 2023, Redlib is currently not operational as Reddit's API changes, that were designed to kill third-party apps and content scrapers who don't pay [large fees](https://www.theverge.com/2023/5/31/23743993/reddit-apollo-client-api-cost), went into effect. [Read the full announcement here.](https://github.com/libreddit/libreddit/issues/840)
|
||||
> An alternative private front-end to Reddit, with its origins in [Libreddit](https://github.com/libreddit/libreddit).
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
**10-second pitch:** Redlib is a private front-end like [Invidious](https://github.com/iv-org/invidious) but for Reddit. Browse the coldest takes of [r/unpopularopinion](https://libreddit.spike.codes/r/unpopularopinion) without being [tracked](#reddit).
|
||||
**10-second pitch:** Redlib is a private front-end like [Invidious](https://github.com/iv-org/invidious) but for Reddit. Browse the coldest takes of [r/unpopularopinion](https://redlib.matthew.science/r/unpopularopinion) without being [tracked](#reddit).
|
||||
|
||||
- 🚀 Fast: written in Rust for blazing-fast speeds and memory safety
|
||||
- ☁️ Light: no JavaScript, no ads, no tracking, no bloat
|
||||
@ -18,25 +15,13 @@
|
||||
|
||||
---
|
||||
|
||||
I appreciate any donations! Your support allows me to continue developing Redlib.
|
||||
|
||||
<a href="https://www.buymeacoffee.com/spikecodes" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" style="height: 40px" ></a>
|
||||
<a href="https://liberapay.com/spike/donate"><img alt="Donate using Liberapay" src="https://liberapay.com/assets/widgets/donate.svg" style="height: 40px"></a>
|
||||
|
||||
|
||||
**Bitcoin:** `bc1qwyxjnafpu3gypcpgs025cw9wa7ryudtecmwa6y`
|
||||
|
||||
**Monero:** `45FJrEuFPtG2o7QZz2Nps77TbHD4sPqxViwbdyV9A6ktfHiWs47UngG5zXPcLoDXAc8taeuBgeNjfeprwgeXYXhN3C9tVSR`
|
||||
|
||||
---
|
||||
|
||||
# Instances
|
||||
|
||||
🔗 **Want to automatically redirect Reddit links to Redlib? Use [LibRedirect](https://github.com/libredirect/libredirect) or [Privacy Redirect](https://github.com/SimonBrazell/privacy-redirect)!**
|
||||
|
||||
[Follow this link](https://github.com/redlib-org/redlib-instances/blob/main/instances.md) for an up-to-date table of instances in Markdown format. This list is also available as [a machine-readable JSON](https://github.com/redlib-org/redlib-instances/blob/main/instances.json).
|
||||
|
||||
Both files are part of the [libreddit-instances](https://github.com/redlib-org/redlib-instances) repository. To contribute your [self-hosted instance](#deployment) to the list, see the [libreddit-instances README](https://github.com/redlib-org/redlib-instances/blob/main/README.md).
|
||||
Both files are part of the [redlib-instances](https://github.com/redlib-org/redlib-instances) repository. To contribute your [self-hosted instance](#deployment) to the list, see the [redlib-instances README](https://github.com/redlib-org/redlib-instances/blob/main/README.md).
|
||||
|
||||
---
|
||||
|
||||
@ -54,7 +39,7 @@ Find Redlib on 💬 [Matrix](https://matrix.to/#/#redlib:matrix.org), 🐋 [Quay
|
||||
## Info
|
||||
Redlib hopes to provide an easier way to browse Reddit, without the ads, trackers, and bloat. Redlib was inspired by other alternative front-ends to popular services such as [Invidious](https://github.com/iv-org/invidious) for YouTube, [Nitter](https://github.com/zedeus/nitter) for Twitter, and [Bibliogram](https://sr.ht/~cadence/bibliogram/) for Instagram.
|
||||
|
||||
Redlib currently implements most of Reddit's (signed-out) functionalities but still lacks [a few features](https://github.com/libreddit/libreddit/issues).
|
||||
Redlib currently implements most of Reddit's (signed-out) functionalities but still lacks [a few features](https://github.com/redlib-org/redlib/issues).
|
||||
|
||||
## How does it compare to Teddit?
|
||||
|
||||
@ -72,14 +57,14 @@ This section outlines how Redlib compares to Reddit.
|
||||
|
||||
## Speed
|
||||
|
||||
Lasted tested Nov 11, 2022.
|
||||
Lasted tested Jan 12, 2024.
|
||||
|
||||
Results from Google PageSpeed Insights ([Redlib Report](https://pagespeed.web.dev/report?url=https%3A%2F%2Flibreddit.spike.codes%2F), [Reddit Report](https://pagespeed.web.dev/report?url=https://www.reddit.com)).
|
||||
Results from Google PageSpeed Insights ([Redlib Report](https://pagespeed.web.dev/report?url=https%3A%2F%2Fredlib.matthew.science%2F), [Reddit Report](https://pagespeed.web.dev/report?url=https://www.reddit.com)).
|
||||
|
||||
| | Redlib | Reddit |
|
||||
|------------------------|-------------|-----------|
|
||||
| Requests | 60 | 83 |
|
||||
| Speed Index | 2.0s | 10.4s |
|
||||
| Speed Index | 0.6s | 1.9s |
|
||||
| Performance Score | *100%* | *64%* |
|
||||
| Time to Interactive | **2.8s** | **12.4s** |
|
||||
|
||||
## Privacy
|
||||
@ -121,11 +106,11 @@ For transparency, I hope to describe all the ways Redlib handles user privacy.
|
||||
|
||||
* **Logging:** In production (when running the binary, hosting with docker, or using the official instances), Redlib logs nothing. When debugging (running from source without `--release`), Redlib logs post IDs fetched to aid with troubleshooting.
|
||||
|
||||
* **Cookies:** Redlib uses optional cookies to store any configured settings in [the settings menu](https://libreddit.spike.codes/settings). These are not cross-site cookies and the cookies hold no personal data.
|
||||
* **Cookies:** Redlib uses optional cookies to store any configured settings in [the settings menu](https://redlib.matthew.science/settings). These are not cross-site cookies and the cookies hold no personal data.
|
||||
|
||||
#### Official instance (libreddit.spike.codes)
|
||||
#### Official instance (redlib.matthew.science)
|
||||
|
||||
The official instance is hosted at https://libreddit.spike.codes.
|
||||
The official instance is hosted at https://redlib.matthew.science.
|
||||
|
||||
* **Server:** The official instance runs a production binary, and thus logs nothing.
|
||||
|
||||
@ -137,13 +122,13 @@ The official instance is hosted at https://libreddit.spike.codes.
|
||||
|
||||
# Installation
|
||||
|
||||
## 1) Cargo
|
||||
<!-- ## 1) Cargo
|
||||
|
||||
Make sure Rust stable is installed along with `cargo`, Rust's package manager.
|
||||
|
||||
```
|
||||
cargo install libreddit
|
||||
```
|
||||
``` -->
|
||||
|
||||
## 2) Docker
|
||||
|
||||
@ -163,7 +148,7 @@ To deploy on `arm64` platforms, simply replace `quay.io/redlib/redlib` in the co
|
||||
|
||||
To deploy on `armv7` platforms, simply replace `quay.io/redlib/redlib` in the commands above with `quay.io/redlib/redlib:latest-armv7`.
|
||||
|
||||
## 3) AUR
|
||||
<!-- ## 3) AUR
|
||||
|
||||
For ArchLinux users, Redlib is available from the AUR as [`libreddit-git`](https://aur.archlinux.org/packages/libreddit-git).
|
||||
|
||||
@ -183,7 +168,7 @@ Or, if you prefer to build from source
|
||||
```
|
||||
cd /usr/pkgsrc/libreddit
|
||||
make install
|
||||
```
|
||||
``` -->
|
||||
|
||||
## 5) GitHub Releases
|
||||
|
||||
@ -196,7 +181,6 @@ If you're on Linux and none of these methods work for you, you can grab a Linux
|
||||
|
||||
<a href="https://repl.it/github/redlib-org/redlib"><img src="https://repl.it/badge/github/redlib-org/redlib" alt="Run on Repl.it" height="32" /></a>
|
||||
[](https://heroku.com/deploy?template=https://github.com/redlib-org/redlib)
|
||||
[](https://glitch.com/edit/#!/remix/libreddit)
|
||||
|
||||
---
|
||||
|
||||
|
27
docker-compose.dev.yml
Normal file
27
docker-compose.dev.yml
Normal file
@ -0,0 +1,27 @@
|
||||
# docker-compose -f docker-compose.dev.yml up -d
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
web:
|
||||
build: .
|
||||
restart: always
|
||||
container_name: "redlib"
|
||||
ports:
|
||||
- 8080:8080
|
||||
user: nobody
|
||||
read_only: true
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
cap_drop:
|
||||
- ALL
|
||||
networks:
|
||||
- redlib
|
||||
security_opt:
|
||||
- seccomp="seccomp-redlib.json"
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--spider", "-q", "--tries=1", "http://localhost:8080/settings"]
|
||||
interval: 5m
|
||||
timeout: 3s
|
||||
|
||||
networks:
|
||||
redlib:
|
@ -2,7 +2,9 @@ version: "3.8"
|
||||
|
||||
services:
|
||||
web:
|
||||
build: .
|
||||
image: quay.io/redlib/redlib
|
||||
# quay.io/redlib/redlib:latest-arm # uncomment if you use arm64
|
||||
# quay.io/redlib/redlib:latest-armv7 # uncomment if you use armv7
|
||||
restart: always
|
||||
container_name: "redlib"
|
||||
ports:
|
||||
|
18
scripts/update_hls_js.sh
Executable file
18
scripts/update_hls_js.sh
Executable file
@ -0,0 +1,18 @@
|
||||
#!/bin/bash
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
LATEST_TAG=$(curl -s https://api.github.com/repos/video-dev/hls.js/releases/latest | jq -r '.tag_name')
|
||||
|
||||
if [[ -z "$LATEST_TAG" || "$LATEST_TAG" == "null" ]]; then
|
||||
echo "Failed to fetch the latest release tag from GitHub."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
LICENSE="// @license http://www.apache.org/licenses/LICENSE-2.0 Apache-2.0
|
||||
// @source https://github.com/video-dev/hls.js/tree/$LATEST_TAG"
|
||||
|
||||
echo "$LICENSE" > ../static/hls.min.js
|
||||
|
||||
curl -s https://cdn.jsdelivr.net/npm/hls.js@${LATEST_TAG}/dist/hls.min.js >> ../static/hls.min.js
|
||||
|
||||
echo "Update complete. The latest hls.js (${LATEST_TAG}) has been saved to static/hls.min.js."
|
@ -5,6 +5,7 @@ use hyper::client::HttpConnector;
|
||||
use hyper::{body, body::Buf, client, header, Body, Client, Method, Request, Response, Uri};
|
||||
use hyper_rustls::HttpsConnector;
|
||||
use libflate::gzip;
|
||||
use log::error;
|
||||
use once_cell::sync::Lazy;
|
||||
use percent_encoding::{percent_encode, CONTROLS};
|
||||
use serde_json::Value;
|
||||
@ -13,7 +14,7 @@ use std::{io, result::Result};
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use crate::dbg_msg;
|
||||
use crate::oauth::{token_daemon, Oauth};
|
||||
use crate::oauth::{force_refresh_token, token_daemon, Oauth};
|
||||
use crate::server::RequestExt;
|
||||
use crate::utils::format_url;
|
||||
|
||||
@ -56,7 +57,9 @@ pub async fn canonical_path(path: String) -> Result<Option<String>, String> {
|
||||
// If Reddit responds with a 301, then the path is redirected.
|
||||
301 => match res.headers().get(header::LOCATION) {
|
||||
Some(val) => {
|
||||
let original = val.to_str().unwrap();
|
||||
let Ok(original) = val.to_str() else {
|
||||
return Err("Unable to decode Location header.".to_string());
|
||||
};
|
||||
// We need to strip the .json suffix from the original path.
|
||||
// In addition, we want to remove share parameters.
|
||||
// Cut it off here instead of letting it propagate all the way
|
||||
@ -88,12 +91,12 @@ pub async fn canonical_path(path: String) -> Result<Option<String>, String> {
|
||||
}
|
||||
|
||||
pub async fn proxy(req: Request<Body>, format: &str) -> Result<Response<Body>, String> {
|
||||
let mut url = format!("{}?{}", format, req.uri().query().unwrap_or_default());
|
||||
let mut url = format!("{format}?{}", req.uri().query().unwrap_or_default());
|
||||
|
||||
// For each parameter in request
|
||||
for (name, value) in req.params().iter() {
|
||||
for (name, value) in &req.params() {
|
||||
// Fill the parameter value in the url
|
||||
url = url.replace(&format!("{{{}}}", name), value);
|
||||
url = url.replace(&format!("{{{name}}}"), value);
|
||||
}
|
||||
|
||||
stream(&url, &req).await
|
||||
@ -101,12 +104,12 @@ pub async fn proxy(req: Request<Body>, format: &str) -> Result<Response<Body>, S
|
||||
|
||||
async fn stream(url: &str, req: &Request<Body>) -> Result<Response<Body>, String> {
|
||||
// First parameter is target URL (mandatory).
|
||||
let uri = url.parse::<Uri>().map_err(|_| "Couldn't parse URL".to_string())?;
|
||||
let parsed_uri = url.parse::<Uri>().map_err(|_| "Couldn't parse URL".to_string())?;
|
||||
|
||||
// Build the hyper client from the HTTPS connector.
|
||||
let client: client::Client<_, hyper::Body> = CLIENT.clone();
|
||||
let client: Client<_, Body> = CLIENT.clone();
|
||||
|
||||
let mut builder = Request::get(uri);
|
||||
let mut builder = Request::get(parsed_uri);
|
||||
|
||||
// Copy useful headers from original request
|
||||
for &key in &["Range", "If-Modified-Since", "Cache-Control"] {
|
||||
@ -152,15 +155,15 @@ fn reddit_head(path: String, quarantine: bool) -> Boxed<Result<Response<Body>, S
|
||||
request(&Method::HEAD, path, false, quarantine)
|
||||
}
|
||||
|
||||
/// Makes a request to Reddit. If `redirect` is `true`, request_with_redirect
|
||||
/// Makes a request to Reddit. If `redirect` is `true`, `request_with_redirect`
|
||||
/// will recurse on the URL that Reddit provides in the Location HTTP header
|
||||
/// in its response.
|
||||
fn request(method: &'static Method, path: String, redirect: bool, quarantine: bool) -> Boxed<Result<Response<Body>, String>> {
|
||||
// Build Reddit URL from path.
|
||||
let url = format!("{}{}", REDDIT_URL_BASE, path);
|
||||
let url = format!("{REDDIT_URL_BASE}{path}");
|
||||
|
||||
// Construct the hyper client from the HTTPS connector.
|
||||
let client: client::Client<_, hyper::Body> = CLIENT.clone();
|
||||
let client: Client<_, Body> = CLIENT.clone();
|
||||
|
||||
let (token, vendor_id, device_id, user_agent, loid) = {
|
||||
let client = block_on(OAUTH_CLIENT.read());
|
||||
@ -182,7 +185,7 @@ fn request(method: &'static Method, path: String, redirect: bool, quarantine: bo
|
||||
.header("X-Reddit-Device-Id", device_id)
|
||||
.header("x-reddit-loid", loid)
|
||||
.header("Host", "oauth.reddit.com")
|
||||
.header("Authorization", &format!("Bearer {}", token))
|
||||
.header("Authorization", &format!("Bearer {token}"))
|
||||
.header("Accept-Encoding", if method == Method::GET { "gzip" } else { "identity" })
|
||||
.header("Accept-Language", "en-US,en;q=0.5")
|
||||
.header("Connection", "keep-alive")
|
||||
@ -225,7 +228,7 @@ fn request(method: &'static Method, path: String, redirect: bool, quarantine: bo
|
||||
//
|
||||
// 2. Percent-encode the path.
|
||||
let new_path = percent_encode(val.as_bytes(), CONTROLS).to_string().trim_start_matches(REDDIT_URL_BASE).to_string();
|
||||
format!("{}{}raw_json=1", new_path, if new_path.contains('?') { "&" } else { "?" })
|
||||
format!("{new_path}{}raw_json=1", if new_path.contains('?') { "&" } else { "?" })
|
||||
})
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
@ -300,7 +303,7 @@ pub async fn json(path: String, quarantine: bool) -> Result<Value, String> {
|
||||
// Closure to quickly build errors
|
||||
let err = |msg: &str, e: String| -> Result<Value, String> {
|
||||
// eprintln!("{} - {}: {}", url, msg, e);
|
||||
Err(format!("{}: {}", msg, e))
|
||||
Err(format!("{msg}: {e}"))
|
||||
};
|
||||
|
||||
// Fetch the url...
|
||||
@ -322,7 +325,7 @@ pub async fn json(path: String, quarantine: bool) -> Result<Value, String> {
|
||||
.as_str()
|
||||
.unwrap_or_else(|| {
|
||||
json["message"].as_str().unwrap_or_else(|| {
|
||||
eprintln!("{}{} - Error parsing reddit error", REDDIT_URL_BASE, path);
|
||||
eprintln!("{REDDIT_URL_BASE}{path} - Error parsing reddit error");
|
||||
"Error parsing reddit error"
|
||||
})
|
||||
})
|
||||
@ -333,6 +336,8 @@ pub async fn json(path: String, quarantine: bool) -> Result<Value, String> {
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Got a bad response from reddit {e} - forcing a token refresh. Status code: {status}");
|
||||
let _ = force_refresh_token().await;
|
||||
if status.is_server_error() {
|
||||
Err("Reddit is having issues, check if there's an outage".to_string())
|
||||
} else {
|
||||
|
@ -17,7 +17,7 @@ pub const DEFAULT_PUSHSHIFT_FRONTEND: &str = "www.unddit.com";
|
||||
/// config file. `Config::Default()` contains None for each setting.
|
||||
/// When adding more config settings, add it to `Config::load`,
|
||||
/// `get_setting_from_config`, both below, as well as
|
||||
/// instance_info::InstanceInfo.to_string(), README.md and app.json.
|
||||
/// `instance_info::InstanceInfo.to_string`(), README.md and app.json.
|
||||
#[derive(Default, Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct Config {
|
||||
#[serde(rename = "REDLIB_SFW_ONLY")]
|
||||
@ -103,7 +103,7 @@ impl Config {
|
||||
new_file.ok().and_then(|new_file| toml::from_str::<Self>(&new_file).ok())
|
||||
};
|
||||
|
||||
let config = load_config("redlib.toml").or(load_config("libreddit.toml")).unwrap_or_default();
|
||||
let config = load_config("redlib.toml").or_else(|| load_config("libreddit.toml")).unwrap_or_default();
|
||||
|
||||
// This function defines the order of preference - first check for
|
||||
// environment variables with "REDLIB", then check the legacy LIBREDDIT
|
||||
@ -112,7 +112,7 @@ impl Config {
|
||||
// Return the first non-`None` value
|
||||
// If all are `None`, return `None`
|
||||
let legacy_key = key.replace("REDLIB_", "LIBREDDIT_");
|
||||
var(key).ok().or(var(legacy_key).ok()).or(get_setting_from_config(key, &config))
|
||||
var(key).ok().or_else(|| var(legacy_key).ok()).or_else(|| get_setting_from_config(key, &config))
|
||||
};
|
||||
Self {
|
||||
sfw_only: parse("REDLIB_SFW_ONLY"),
|
||||
|
@ -12,14 +12,14 @@ use std::borrow::ToOwned;
|
||||
use std::collections::HashSet;
|
||||
use std::vec::Vec;
|
||||
|
||||
/// DuplicatesParams contains the parameters in the URL.
|
||||
/// `DuplicatesParams` contains the parameters in the URL.
|
||||
struct DuplicatesParams {
|
||||
before: String,
|
||||
after: String,
|
||||
sort: String,
|
||||
}
|
||||
|
||||
/// DuplicatesTemplate defines an Askama template for rendering duplicate
|
||||
/// `DuplicatesTemplate` defines an Askama template for rendering duplicate
|
||||
/// posts.
|
||||
#[derive(Template)]
|
||||
#[template(path = "duplicates.html")]
|
||||
@ -59,7 +59,7 @@ pub async fn item(req: Request<Body>) -> Result<Response<Body>, String> {
|
||||
|
||||
// Log the request in debugging mode
|
||||
#[cfg(debug_assertions)]
|
||||
dbg!(req.param("id").unwrap_or_default());
|
||||
req.param("id").unwrap_or_default();
|
||||
|
||||
// Send the GET, and await JSON.
|
||||
match json(path, quarantined).await {
|
||||
@ -189,7 +189,7 @@ pub async fn item(req: Request<Body>) -> Result<Response<Body>, String> {
|
||||
Err(msg) => {
|
||||
// Abort entirely if we couldn't get the previous
|
||||
// batch.
|
||||
return error(req, msg).await;
|
||||
return error(req, &msg).await;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -197,7 +197,7 @@ pub async fn item(req: Request<Body>) -> Result<Response<Body>, String> {
|
||||
}
|
||||
}
|
||||
|
||||
template(DuplicatesTemplate {
|
||||
Ok(template(&DuplicatesTemplate {
|
||||
params: DuplicatesParams { before, after, sort },
|
||||
post,
|
||||
duplicates,
|
||||
@ -205,28 +205,28 @@ pub async fn item(req: Request<Body>) -> Result<Response<Body>, String> {
|
||||
url: req_url,
|
||||
num_posts_filtered,
|
||||
all_posts_filtered,
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
// Process error.
|
||||
Err(msg) => {
|
||||
if msg == "quarantined" || msg == "gated" {
|
||||
let sub = req.param("sub").unwrap_or_default();
|
||||
quarantine(req, sub, msg)
|
||||
Ok(quarantine(&req, sub, &msg))
|
||||
} else {
|
||||
error(req, msg).await
|
||||
error(req, &msg).await
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DUPLICATES
|
||||
async fn parse_duplicates(json: &serde_json::Value, filters: &HashSet<String>) -> (Vec<Post>, u64, bool) {
|
||||
async fn parse_duplicates(json: &Value, filters: &HashSet<String>) -> (Vec<Post>, u64, bool) {
|
||||
let post_duplicates: &Vec<Value> = &json["data"]["children"].as_array().map_or(Vec::new(), ToOwned::to_owned);
|
||||
let mut duplicates: Vec<Post> = Vec::new();
|
||||
|
||||
// Process each post and place them in the Vec<Post>.
|
||||
for val in post_duplicates.iter() {
|
||||
for val in post_duplicates {
|
||||
let post: Post = parse_post(val).await;
|
||||
duplicates.push(post);
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ pub async fn instance_info(req: Request<Body>) -> Result<Response<Body>, String>
|
||||
"yaml" | "yml" => info_yaml(),
|
||||
"txt" => info_txt(),
|
||||
"json" => info_json(),
|
||||
"html" | "" => info_html(req),
|
||||
"html" | "" => info_html(&req),
|
||||
_ => {
|
||||
let error = ErrorTemplate {
|
||||
msg: "Error: Invalid info extension".into(),
|
||||
@ -68,13 +68,13 @@ fn info_txt() -> Result<Response<Body>, Error> {
|
||||
Response::builder()
|
||||
.status(200)
|
||||
.header("content-type", "text/plain")
|
||||
.body(Body::from(INSTANCE_INFO.to_string(StringType::Raw)))
|
||||
.body(Body::from(INSTANCE_INFO.to_string(&StringType::Raw)))
|
||||
}
|
||||
fn info_html(req: Request<Body>) -> Result<Response<Body>, Error> {
|
||||
fn info_html(req: &Request<Body>) -> Result<Response<Body>, Error> {
|
||||
let message = MessageTemplate {
|
||||
title: String::from("Instance information"),
|
||||
body: INSTANCE_INFO.to_string(StringType::Html),
|
||||
prefs: Preferences::new(&req),
|
||||
body: INSTANCE_INFO.to_string(&StringType::Html),
|
||||
prefs: Preferences::new(req),
|
||||
url: req.uri().to_string(),
|
||||
}
|
||||
.render()
|
||||
@ -109,7 +109,7 @@ impl InstanceInfo {
|
||||
}
|
||||
fn to_table(&self) -> String {
|
||||
let mut container = Container::default();
|
||||
let convert = |o: &Option<String>| -> String { o.clone().unwrap_or("<span class=\"unset\"><i>Unset</i></span>".to_owned()) };
|
||||
let convert = |o: &Option<String>| -> String { o.clone().unwrap_or_else(|| "<span class=\"unset\"><i>Unset</i></span>".to_owned()) };
|
||||
if let Some(banner) = &self.config.banner {
|
||||
container.add_header(3, "Instance banner");
|
||||
container.add_raw("<br />");
|
||||
@ -151,7 +151,7 @@ impl InstanceInfo {
|
||||
);
|
||||
container.to_html_string().replace("<th>", "<th colspan=\"2\">")
|
||||
}
|
||||
fn to_string(&self, string_type: StringType) -> String {
|
||||
fn to_string(&self, string_type: &StringType) -> String {
|
||||
match string_type {
|
||||
StringType::Raw => {
|
||||
format!(
|
||||
|
80
src/main.rs
80
src/main.rs
@ -1,6 +1,42 @@
|
||||
// Global specifiers
|
||||
#![forbid(unsafe_code)]
|
||||
#![allow(clippy::cmp_owned)]
|
||||
#![deny(
|
||||
anonymous_parameters,
|
||||
clippy::all,
|
||||
illegal_floating_point_literal_pattern,
|
||||
late_bound_lifetime_arguments,
|
||||
path_statements,
|
||||
patterns_in_fns_without_body,
|
||||
rust_2018_idioms,
|
||||
trivial_numeric_casts,
|
||||
unused_extern_crates
|
||||
)]
|
||||
#![warn(
|
||||
clippy::dbg_macro,
|
||||
clippy::decimal_literal_representation,
|
||||
clippy::get_unwrap,
|
||||
clippy::nursery,
|
||||
clippy::pedantic,
|
||||
clippy::todo,
|
||||
clippy::unimplemented,
|
||||
clippy::use_debug,
|
||||
clippy::all,
|
||||
unused_qualifications,
|
||||
variant_size_differences
|
||||
)]
|
||||
#![allow(
|
||||
clippy::cmp_owned,
|
||||
clippy::unused_async,
|
||||
clippy::option_if_let_else,
|
||||
clippy::items_after_statements,
|
||||
clippy::cast_possible_truncation,
|
||||
clippy::cast_possible_wrap,
|
||||
clippy::cast_precision_loss,
|
||||
clippy::struct_field_names,
|
||||
clippy::struct_excessive_bools,
|
||||
clippy::useless_let_if_seq,
|
||||
clippy::collection_is_never_read
|
||||
)]
|
||||
|
||||
// Reference local files
|
||||
mod config;
|
||||
@ -193,7 +229,7 @@ async fn main() {
|
||||
};
|
||||
|
||||
if let Some(expire_time) = hsts {
|
||||
if let Ok(val) = HeaderValue::from_str(&format!("max-age={}", expire_time)) {
|
||||
if let Ok(val) = HeaderValue::from_str(&format!("max-age={expire_time}")) {
|
||||
app.default_headers.insert("Strict-Transport-Security", val);
|
||||
}
|
||||
}
|
||||
@ -249,11 +285,11 @@ async fn main() {
|
||||
// Browse user profile
|
||||
app
|
||||
.at("/u/:name")
|
||||
.get(|r| async move { Ok(redirect(format!("/user/{}", r.param("name").unwrap_or_default()))) }.boxed());
|
||||
.get(|r| async move { Ok(redirect(&format!("/user/{}", r.param("name").unwrap_or_default()))) }.boxed());
|
||||
app.at("/u/:name/comments/:id/:title").get(|r| post::item(r).boxed());
|
||||
app.at("/u/:name/comments/:id/:title/:comment_id").get(|r| post::item(r).boxed());
|
||||
|
||||
app.at("/user/[deleted]").get(|req| error(req, "User has deleted their account".to_string()).boxed());
|
||||
app.at("/user/[deleted]").get(|req| error(req, "User has deleted their account").boxed());
|
||||
app.at("/user/:name").get(|r| user::profile(r).boxed());
|
||||
app.at("/user/:name/:listing").get(|r| user::profile(r).boxed());
|
||||
app.at("/user/:name/comments/:id").get(|r| post::item(r).boxed());
|
||||
@ -273,7 +309,7 @@ async fn main() {
|
||||
|
||||
app
|
||||
.at("/r/u_:name")
|
||||
.get(|r| async move { Ok(redirect(format!("/user/{}", r.param("name").unwrap_or_default()))) }.boxed());
|
||||
.get(|r| async move { Ok(redirect(&format!("/user/{}", r.param("name").unwrap_or_default()))) }.boxed());
|
||||
|
||||
app.at("/r/:sub/subscribe").post(|r| subreddit::subscriptions_filters(r).boxed());
|
||||
app.at("/r/:sub/unsubscribe").post(|r| subreddit::subscriptions_filters(r).boxed());
|
||||
@ -298,10 +334,10 @@ async fn main() {
|
||||
|
||||
app
|
||||
.at("/r/:sub/w")
|
||||
.get(|r| async move { Ok(redirect(format!("/r/{}/wiki", r.param("sub").unwrap_or_default()))) }.boxed());
|
||||
.get(|r| async move { Ok(redirect(&format!("/r/{}/wiki", r.param("sub").unwrap_or_default()))) }.boxed());
|
||||
app
|
||||
.at("/r/:sub/w/*page")
|
||||
.get(|r| async move { Ok(redirect(format!("/r/{}/wiki/{}", r.param("sub").unwrap_or_default(), r.param("wiki").unwrap_or_default()))) }.boxed());
|
||||
.get(|r| async move { Ok(redirect(&format!("/r/{}/wiki/{}", r.param("sub").unwrap_or_default(), r.param("wiki").unwrap_or_default()))) }.boxed());
|
||||
app.at("/r/:sub/wiki").get(|r| subreddit::wiki(r).boxed());
|
||||
app.at("/r/:sub/wiki/*page").get(|r| subreddit::wiki(r).boxed());
|
||||
|
||||
@ -313,10 +349,10 @@ async fn main() {
|
||||
app.at("/").get(|r| subreddit::community(r).boxed());
|
||||
|
||||
// View Reddit wiki
|
||||
app.at("/w").get(|_| async { Ok(redirect("/wiki".to_string())) }.boxed());
|
||||
app.at("/w").get(|_| async { Ok(redirect("/wiki")) }.boxed());
|
||||
app
|
||||
.at("/w/*page")
|
||||
.get(|r| async move { Ok(redirect(format!("/wiki/{}", r.param("page").unwrap_or_default()))) }.boxed());
|
||||
.get(|r| async move { Ok(redirect(&format!("/wiki/{}", r.param("page").unwrap_or_default()))) }.boxed());
|
||||
app.at("/wiki").get(|r| subreddit::wiki(r).boxed());
|
||||
app.at("/wiki/*page").get(|r| subreddit::wiki(r).boxed());
|
||||
|
||||
@ -324,7 +360,7 @@ async fn main() {
|
||||
app.at("/search").get(|r| search::find(r).boxed());
|
||||
|
||||
// Handle about pages
|
||||
app.at("/about").get(|req| error(req, "About pages aren't added yet".to_string()).boxed());
|
||||
app.at("/about").get(|req| error(req, "About pages aren't added yet").boxed());
|
||||
|
||||
// Instance info page
|
||||
app.at("/info").get(|r| instance_info::instance_info(r).boxed());
|
||||
@ -337,14 +373,14 @@ async fn main() {
|
||||
let sub = req.param("sub").unwrap_or_default();
|
||||
match req.param("id").as_deref() {
|
||||
// Share link
|
||||
Some(id) if (8..12).contains(&id.len()) => match canonical_path(format!("/r/{}/s/{}", sub, id)).await {
|
||||
Ok(Some(path)) => Ok(redirect(path)),
|
||||
Some(id) if (8..12).contains(&id.len()) => match canonical_path(format!("/r/{sub}/s/{id}")).await {
|
||||
Ok(Some(path)) => Ok(redirect(&path)),
|
||||
Ok(None) => error(req, "Post ID is invalid. It may point to a post on a community that has been banned.").await,
|
||||
Err(e) => error(req, e).await,
|
||||
Err(e) => error(req, &e).await,
|
||||
},
|
||||
|
||||
// Error message for unknown pages
|
||||
_ => error(req, "Nothing here".to_string()).await,
|
||||
_ => error(req, "Nothing here").await,
|
||||
}
|
||||
})
|
||||
});
|
||||
@ -356,29 +392,29 @@ async fn main() {
|
||||
Some("best" | "hot" | "new" | "top" | "rising" | "controversial") => subreddit::community(req).await,
|
||||
|
||||
// Short link for post
|
||||
Some(id) if (5..8).contains(&id.len()) => match canonical_path(format!("/{}", id)).await {
|
||||
Some(id) if (5..8).contains(&id.len()) => match canonical_path(format!("/{id}")).await {
|
||||
Ok(path_opt) => match path_opt {
|
||||
Some(path) => Ok(redirect(path)),
|
||||
Some(path) => Ok(redirect(&path)),
|
||||
None => error(req, "Post ID is invalid. It may point to a post on a community that has been banned.").await,
|
||||
},
|
||||
Err(e) => error(req, e).await,
|
||||
Err(e) => error(req, &e).await,
|
||||
},
|
||||
|
||||
// Error message for unknown pages
|
||||
_ => error(req, "Nothing here".to_string()).await,
|
||||
_ => error(req, "Nothing here").await,
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
// Default service in case no routes match
|
||||
app.at("/*").get(|req| error(req, "Nothing here".to_string()).boxed());
|
||||
app.at("/*").get(|req| error(req, "Nothing here").boxed());
|
||||
|
||||
println!("Running Redlib v{} on {}!", env!("CARGO_PKG_VERSION"), listener);
|
||||
println!("Running Redlib v{} on {listener}!", env!("CARGO_PKG_VERSION"));
|
||||
|
||||
let server = app.listen(listener);
|
||||
let server = app.listen(&listener);
|
||||
|
||||
// Run this server for... forever!
|
||||
if let Err(e) = server.await {
|
||||
eprintln!("Server error: {}", e);
|
||||
eprintln!("Server error: {e}");
|
||||
}
|
||||
}
|
||||
|
11
src/oauth.rs
11
src/oauth.rs
@ -46,11 +46,11 @@ impl Oauth {
|
||||
}
|
||||
async fn login(&mut self) -> Option<()> {
|
||||
// Construct URL for OAuth token
|
||||
let url = format!("{}/api/access_token", AUTH_ENDPOINT);
|
||||
let url = format!("{AUTH_ENDPOINT}/api/access_token");
|
||||
let mut builder = Request::builder().method(Method::POST).uri(&url);
|
||||
|
||||
// Add headers from spoofed client
|
||||
for (key, value) in self.initial_headers.iter() {
|
||||
for (key, value) in &self.initial_headers {
|
||||
builder = builder.header(key, value);
|
||||
}
|
||||
// Set up HTTP Basic Auth - basically just the const OAuth ID's with no password,
|
||||
@ -70,7 +70,7 @@ impl Oauth {
|
||||
let request = builder.body(body).unwrap();
|
||||
|
||||
// Send request
|
||||
let client: client::Client<_, hyper::Body> = CLIENT.clone();
|
||||
let client: client::Client<_, Body> = CLIENT.clone();
|
||||
let resp = client.request(request).await.ok()?;
|
||||
|
||||
// Parse headers - loid header _should_ be saved sent on subsequent token refreshes.
|
||||
@ -129,6 +129,11 @@ pub async fn token_daemon() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn force_refresh_token() {
|
||||
OAUTH_CLIENT.write().await.refresh().await;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
struct Device {
|
||||
oauth_id: String,
|
||||
|
@ -2,79 +2,8 @@
|
||||
// Rerun scripts/update_oauth_resources.sh to update this file
|
||||
// Please do not edit manually
|
||||
// Filled in with real app versions
|
||||
pub static _IOS_APP_VERSION_LIST: &[&str; 67] = &[
|
||||
"Version 2020.0.0/Build 306960",
|
||||
"Version 2020.10.0/Build 307041",
|
||||
"Version 2020.10.1/Build 307047",
|
||||
"Version 2020.1.0/Build 306966",
|
||||
"Version 2020.11.0/Build 307049",
|
||||
"Version 2020.11.1/Build 307063",
|
||||
"Version 2020.12.0/Build 307070",
|
||||
"Version 2020.13.0/Build 307072",
|
||||
"Version 2020.13.1/Build 307075",
|
||||
"Version 2020.14.0/Build 307077",
|
||||
"Version 2020.14.1/Build 307080",
|
||||
"Version 2020.15.0/Build 307084",
|
||||
"Version 2020.16.0/Build 307090",
|
||||
"Version 2020.17.0/Build 307093",
|
||||
"Version 2020.19.0/Build 307137",
|
||||
"Version 2020.20.0/Build 307156",
|
||||
"Version 2020.20.1/Build 307159",
|
||||
"Version 2020.2.0/Build 306969",
|
||||
"Version 2020.21.0/Build 307162",
|
||||
"Version 2020.21.1/Build 307165",
|
||||
"Version 2020.22.0/Build 307177",
|
||||
"Version 2020.22.1/Build 307181",
|
||||
"Version 2020.23.0/Build 307183",
|
||||
"Version 2020.24.0/Build 307189",
|
||||
"Version 2020.25.0/Build 307198",
|
||||
"Version 2020.26.0/Build 307205",
|
||||
"Version 2020.26.1/Build 307213",
|
||||
"Version 2020.27.0/Build 307229",
|
||||
"Version 2020.28.0/Build 307233",
|
||||
"Version 2020.29.0/Build 307235",
|
||||
"Version 2020.30.0/Build 307238",
|
||||
"Version 2020.3.0/Build 306971",
|
||||
"Version 2020.31.0/Build 307240",
|
||||
"Version 2020.31.1/Build 307246",
|
||||
"Version 2020.32.0/Build 307250",
|
||||
"Version 2020.33.0/Build 307252",
|
||||
"Version 2020.34.0/Build 307260",
|
||||
"Version 2020.35.0/Build 307262",
|
||||
"Version 2020.36.0/Build 307265",
|
||||
"Version 2020.37.0/Build 307272",
|
||||
"Version 2020.38.0/Build 307286",
|
||||
"Version 2020.39.0/Build 307306",
|
||||
"Version 2020.4.0/Build 306978",
|
||||
"Version 2020.5.0/Build 306993",
|
||||
"Version 2020.5.1/Build 307005",
|
||||
"Version 2020.6.0/Build 307007",
|
||||
"Version 2020.7.0/Build 307012",
|
||||
"Version 2020.8.0/Build 307014",
|
||||
"Version 2020.8.1/Build 307017",
|
||||
"Version 2020.9.0/Build 307035",
|
||||
"Version 2020.9.1/Build 307039",
|
||||
"Version 2023.18.0/Build 310494",
|
||||
"Version 2023.19.0/Build 310507",
|
||||
"Version 2023.20.0/Build 310535",
|
||||
"Version 2023.21.0/Build 310560",
|
||||
"Version 2023.22.0/Build 613580",
|
||||
"Version 2023.23.0/Build 310613",
|
||||
"Version 2023.23.1/Build 613639",
|
||||
"Version 2023.24.0/Build 613663",
|
||||
"Version 2023.25.0/Build 613739",
|
||||
"Version 2023.26.0/Build 613749",
|
||||
"Version 2023.27.0/Build 613771",
|
||||
"Version 2023.28.0/Build 613803",
|
||||
"Version 2023.28.1/Build 613809",
|
||||
"Version 2023.29.0/Build 613825",
|
||||
"Version 2023.30.0/Build 613849",
|
||||
"Version 2023.31.0/Build 613864",
|
||||
];
|
||||
pub static _IOS_APP_VERSION_LIST: &[&str; 1] = &[""];
|
||||
pub static ANDROID_APP_VERSION_LIST: &[&str; 150] = &[
|
||||
"Version 2023.25.1/Build 1018737",
|
||||
"Version 2023.26.0/Build 1019073",
|
||||
"Version 2023.27.0/Build 1031923",
|
||||
"Version 2023.28.0/Build 1046887",
|
||||
"Version 2023.29.0/Build 1059855",
|
||||
"Version 2023.30.0/Build 1078734",
|
||||
@ -102,9 +31,9 @@ pub static ANDROID_APP_VERSION_LIST: &[&str; 150] = &[
|
||||
"Version 2023.49.1/Build 1322281",
|
||||
"Version 2023.50.0/Build 1332338",
|
||||
"Version 2023.50.1/Build 1345844",
|
||||
"Version 2023.02.0/Build 717912",
|
||||
"Version 2023.03.0/Build 729220",
|
||||
"Version 2023.04.0/Build 744681",
|
||||
"Version 2024.02.0/Build 1368985",
|
||||
"Version 2024.03.0/Build 1379408",
|
||||
"Version 2024.04.0/Build 1391236",
|
||||
"Version 2023.05.0/Build 755453",
|
||||
"Version 2023.06.0/Build 775017",
|
||||
"Version 2023.07.0/Build 788827",
|
||||
@ -132,9 +61,9 @@ pub static ANDROID_APP_VERSION_LIST: &[&str; 150] = &[
|
||||
"Version 2023.23.0/Build 983896",
|
||||
"Version 2023.24.0/Build 998541",
|
||||
"Version 2023.25.0/Build 1014750",
|
||||
"Version 2022.24.0/Build 510950",
|
||||
"Version 2022.24.1/Build 513462",
|
||||
"Version 2022.25.0/Build 515072",
|
||||
"Version 2023.25.1/Build 1018737",
|
||||
"Version 2023.26.0/Build 1019073",
|
||||
"Version 2023.27.0/Build 1031923",
|
||||
"Version 2022.25.1/Build 516394",
|
||||
"Version 2022.25.2/Build 519915",
|
||||
"Version 2022.26.0/Build 521193",
|
||||
@ -162,9 +91,9 @@ pub static ANDROID_APP_VERSION_LIST: &[&str; 150] = &[
|
||||
"Version 2022.44.0/Build 664348",
|
||||
"Version 2022.45.0/Build 677985",
|
||||
"Version 2023.01.0/Build 709875",
|
||||
"Version 2021.45.0/Build 387663",
|
||||
"Version 2021.46.0/Build 392043",
|
||||
"Version 2021.47.0/Build 394342",
|
||||
"Version 2023.02.0/Build 717912",
|
||||
"Version 2023.03.0/Build 729220",
|
||||
"Version 2023.04.0/Build 744681",
|
||||
"Version 2022.10.0/Build 429896",
|
||||
"Version 2022.1.0/Build 402829",
|
||||
"Version 2022.11.0/Build 433004",
|
||||
@ -183,6 +112,9 @@ pub static ANDROID_APP_VERSION_LIST: &[&str; 150] = &[
|
||||
"Version 2022.22.0/Build 498700",
|
||||
"Version 2022.23.0/Build 502374",
|
||||
"Version 2022.23.1/Build 506606",
|
||||
"Version 2022.24.0/Build 510950",
|
||||
"Version 2022.24.1/Build 513462",
|
||||
"Version 2022.25.0/Build 515072",
|
||||
"Version 2022.3.0/Build 408637",
|
||||
"Version 2022.4.0/Build 411368",
|
||||
"Version 2022.5.0/Build 414731",
|
||||
@ -192,9 +124,6 @@ pub static ANDROID_APP_VERSION_LIST: &[&str; 150] = &[
|
||||
"Version 2022.7.0/Build 420849",
|
||||
"Version 2022.8.0/Build 423906",
|
||||
"Version 2022.9.0/Build 426592",
|
||||
"Version 2021.17.0/Build 323213",
|
||||
"Version 2021.18.0/Build 324849",
|
||||
"Version 2021.19.0/Build 325762",
|
||||
"Version 2021.20.0/Build 326964",
|
||||
"Version 2021.21.0/Build 327703",
|
||||
"Version 2021.21.1/Build 328461",
|
||||
@ -222,14 +151,8 @@ pub static ANDROID_APP_VERSION_LIST: &[&str; 150] = &[
|
||||
"Version 2021.42.0/Build 378193",
|
||||
"Version 2021.43.0/Build 382019",
|
||||
"Version 2021.44.0/Build 385129",
|
||||
"Version 2021.45.0/Build 387663",
|
||||
"Version 2021.46.0/Build 392043",
|
||||
"Version 2021.47.0/Build 394342",
|
||||
];
|
||||
pub static _IOS_OS_VERSION_LIST: &[&str; 8] = &[
|
||||
"Version 17.0.1 (Build 21A340)",
|
||||
"Version 17.0.2 (Build 21A350)",
|
||||
"Version 17.0.3 (Build 21A360)",
|
||||
"Version 17.1 (Build 21B74)",
|
||||
"Version 17.1.1 (Build 21B91)",
|
||||
"Version 17.1.2 (Build 21B101)",
|
||||
"Version 17.2 (Build 21C62)",
|
||||
"Version 17.2.1 (Build 21C66)",
|
||||
];
|
||||
pub static _IOS_OS_VERSION_LIST: &[&str; 1] = &[""];
|
||||
|
24
src/post.rs
24
src/post.rs
@ -27,7 +27,7 @@ struct PostTemplate {
|
||||
comment_query: String,
|
||||
}
|
||||
|
||||
static COMMENT_SEARCH_CAPTURE: Lazy<Regex> = Lazy::new(|| Regex::new(r#"\?q=(.*)&type=comment"#).unwrap());
|
||||
static COMMENT_SEARCH_CAPTURE: Lazy<Regex> = Lazy::new(|| Regex::new(r"\?q=(.*)&type=comment").unwrap());
|
||||
|
||||
pub async fn item(req: Request<Body>) -> Result<Response<Body>, String> {
|
||||
// Build Reddit API path
|
||||
@ -52,7 +52,7 @@ pub async fn item(req: Request<Body>) -> Result<Response<Body>, String> {
|
||||
|
||||
// Log the post ID being fetched in debug mode
|
||||
#[cfg(debug_assertions)]
|
||||
dbg!(req.param("id").unwrap_or_default());
|
||||
req.param("id").unwrap_or_default();
|
||||
|
||||
let single_thread = req.param("comment_id").is_some();
|
||||
let highlighted_comment = &req.param("comment_id").unwrap_or_default();
|
||||
@ -83,7 +83,7 @@ pub async fn item(req: Request<Body>) -> Result<Response<Body>, String> {
|
||||
};
|
||||
|
||||
// Use the Post and Comment structs to generate a website to show users
|
||||
template(PostTemplate {
|
||||
Ok(template(&PostTemplate {
|
||||
comments,
|
||||
post,
|
||||
url_without_query: url.clone().trim_end_matches(&format!("?q={query}&type=comment")).to_string(),
|
||||
@ -92,15 +92,15 @@ pub async fn item(req: Request<Body>) -> Result<Response<Body>, String> {
|
||||
single_thread,
|
||||
url: req_url,
|
||||
comment_query: query,
|
||||
})
|
||||
}))
|
||||
}
|
||||
// If the Reddit API returns an error, exit and send error page to user
|
||||
Err(msg) => {
|
||||
if msg == "quarantined" || msg == "gated" {
|
||||
let sub = req.param("sub").unwrap_or_default();
|
||||
quarantine(req, sub, msg)
|
||||
Ok(quarantine(&req, sub, &msg))
|
||||
} else {
|
||||
error(req, msg).await
|
||||
error(req, &msg).await
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -139,19 +139,19 @@ fn query_comments(
|
||||
let comments = json["data"]["children"].as_array().map_or(Vec::new(), std::borrow::ToOwned::to_owned);
|
||||
let mut results = Vec::new();
|
||||
|
||||
comments.into_iter().for_each(|comment| {
|
||||
for comment in comments {
|
||||
let data = &comment["data"];
|
||||
|
||||
// If this comment contains replies, handle those too
|
||||
if data["replies"].is_object() {
|
||||
results.append(&mut query_comments(&data["replies"], post_link, post_author, highlighted_comment, filters, query, req))
|
||||
results.append(&mut query_comments(&data["replies"], post_link, post_author, highlighted_comment, filters, query, req));
|
||||
}
|
||||
|
||||
let c = build_comment(&comment, data, Vec::new(), post_link, post_author, highlighted_comment, filters, req);
|
||||
if c.body.to_lowercase().contains(&query.to_lowercase()) {
|
||||
results.push(c);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
results
|
||||
}
|
||||
@ -170,10 +170,8 @@ fn build_comment(
|
||||
|
||||
let body = if (val(comment, "author") == "[deleted]" && val(comment, "body") == "[removed]") || val(comment, "body") == "[ Removed by Reddit ]" {
|
||||
format!(
|
||||
"<div class=\"md\"><p>[removed] — <a href=\"https://{}{}{}\">view removed comment</a></p></div>",
|
||||
get_setting("REDLIB_PUSHSHIFT_FRONTEND").unwrap_or(String::from(crate::config::DEFAULT_PUSHSHIFT_FRONTEND)),
|
||||
post_link,
|
||||
id
|
||||
"<div class=\"md\"><p>[removed] — <a href=\"https://{}{post_link}{id}\">view removed comment</a></p></div>",
|
||||
get_setting("REDLIB_PUSHSHIFT_FRONTEND").unwrap_or_else(|| String::from(crate::config::DEFAULT_PUSHSHIFT_FRONTEND)),
|
||||
)
|
||||
} else {
|
||||
rewrite_urls(&val(comment, "body_html"))
|
||||
|
@ -65,11 +65,11 @@ pub async fn find(req: Request<Body>) -> Result<Response<Body>, String> {
|
||||
query = REDDIT_URL_MATCH.replace(&query, "").to_string();
|
||||
|
||||
if query.is_empty() {
|
||||
return Ok(redirect("/".to_string()));
|
||||
return Ok(redirect("/"));
|
||||
}
|
||||
|
||||
if query.starts_with("r/") {
|
||||
return Ok(redirect(format!("/{}", query)));
|
||||
return Ok(redirect(&format!("/{query}")));
|
||||
}
|
||||
|
||||
let sub = req.param("sub").unwrap_or_default();
|
||||
@ -97,7 +97,7 @@ pub async fn find(req: Request<Body>) -> Result<Response<Body>, String> {
|
||||
|
||||
// If all requested subs are filtered, we don't need to fetch posts.
|
||||
if sub.split('+').all(|s| filters.contains(s)) {
|
||||
template(SearchTemplate {
|
||||
Ok(template(&SearchTemplate {
|
||||
posts: Vec::new(),
|
||||
subreddits,
|
||||
sub,
|
||||
@ -106,7 +106,7 @@ pub async fn find(req: Request<Body>) -> Result<Response<Body>, String> {
|
||||
sort,
|
||||
t: param(&path, "t").unwrap_or_default(),
|
||||
before: param(&path, "after").unwrap_or_default(),
|
||||
after: "".to_string(),
|
||||
after: String::new(),
|
||||
restrict_sr: param(&path, "restrict_sr").unwrap_or_default(),
|
||||
typed,
|
||||
},
|
||||
@ -116,14 +116,14 @@ pub async fn find(req: Request<Body>) -> Result<Response<Body>, String> {
|
||||
all_posts_filtered: false,
|
||||
all_posts_hidden_nsfw: false,
|
||||
no_posts: false,
|
||||
})
|
||||
}))
|
||||
} else {
|
||||
match Post::fetch(&path, quarantined).await {
|
||||
Ok((mut posts, after)) => {
|
||||
let (_, all_posts_filtered) = filter_posts(&mut posts, &filters);
|
||||
let no_posts = posts.is_empty();
|
||||
let all_posts_hidden_nsfw = !no_posts && (posts.iter().all(|p| p.flags.nsfw) && setting(&req, "show_nsfw") != "on");
|
||||
template(SearchTemplate {
|
||||
Ok(template(&SearchTemplate {
|
||||
posts,
|
||||
subreddits,
|
||||
sub,
|
||||
@ -142,14 +142,14 @@ pub async fn find(req: Request<Body>) -> Result<Response<Body>, String> {
|
||||
all_posts_filtered,
|
||||
all_posts_hidden_nsfw,
|
||||
no_posts,
|
||||
})
|
||||
}))
|
||||
}
|
||||
Err(msg) => {
|
||||
if msg == "quarantined" || msg == "gated" {
|
||||
let sub = req.param("sub").unwrap_or_default();
|
||||
quarantine(req, sub, msg)
|
||||
Ok(quarantine(&req, sub, &msg))
|
||||
} else {
|
||||
error(req, msg).await
|
||||
error(req, &msg).await
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -158,7 +158,7 @@ pub async fn find(req: Request<Body>) -> Result<Response<Body>, String> {
|
||||
|
||||
async fn search_subreddits(q: &str, typed: &str) -> Vec<Subreddit> {
|
||||
let limit = if typed == "sr_user" { "50" } else { "3" };
|
||||
let subreddit_search_path = format!("/subreddits/search.json?q={}&limit={}", q.replace(' ', "+"), limit);
|
||||
let subreddit_search_path = format!("/subreddits/search.json?q={}&limit={limit}", q.replace(' ', "+"));
|
||||
|
||||
// Send a request to the url
|
||||
json(subreddit_search_path, false).await.unwrap_or_default()["data"]["children"]
|
||||
|
@ -70,7 +70,7 @@ impl ToString for CompressionType {
|
||||
match self {
|
||||
Self::Gzip => "gzip".to_string(),
|
||||
Self::Brotli => "br".to_string(),
|
||||
_ => String::new(),
|
||||
Self::Passthrough => String::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -104,13 +104,13 @@ pub trait RequestExt {
|
||||
fn params(&self) -> Params;
|
||||
fn param(&self, name: &str) -> Option<String>;
|
||||
fn set_params(&mut self, params: Params) -> Option<Params>;
|
||||
fn cookies(&self) -> Vec<Cookie>;
|
||||
fn cookie(&self, name: &str) -> Option<Cookie>;
|
||||
fn cookies(&self) -> Vec<Cookie<'_>>;
|
||||
fn cookie(&self, name: &str) -> Option<Cookie<'_>>;
|
||||
}
|
||||
|
||||
pub trait ResponseExt {
|
||||
fn cookies(&self) -> Vec<Cookie>;
|
||||
fn insert_cookie(&mut self, cookie: Cookie);
|
||||
fn cookies(&self) -> Vec<Cookie<'_>>;
|
||||
fn insert_cookie(&mut self, cookie: Cookie<'_>);
|
||||
fn remove_cookie(&mut self, name: String);
|
||||
}
|
||||
|
||||
@ -131,7 +131,7 @@ impl RequestExt for Request<Body> {
|
||||
self.extensions_mut().insert(params)
|
||||
}
|
||||
|
||||
fn cookies(&self) -> Vec<Cookie> {
|
||||
fn cookies(&self) -> Vec<Cookie<'_>> {
|
||||
self.headers().get("Cookie").map_or(Vec::new(), |header| {
|
||||
header
|
||||
.to_str()
|
||||
@ -142,13 +142,13 @@ impl RequestExt for Request<Body> {
|
||||
})
|
||||
}
|
||||
|
||||
fn cookie(&self, name: &str) -> Option<Cookie> {
|
||||
fn cookie(&self, name: &str) -> Option<Cookie<'_>> {
|
||||
self.cookies().into_iter().find(|c| c.name() == name)
|
||||
}
|
||||
}
|
||||
|
||||
impl ResponseExt for Response<Body> {
|
||||
fn cookies(&self) -> Vec<Cookie> {
|
||||
fn cookies(&self) -> Vec<Cookie<'_>> {
|
||||
self.headers().get("Cookie").map_or(Vec::new(), |header| {
|
||||
header
|
||||
.to_str()
|
||||
@ -159,7 +159,7 @@ impl ResponseExt for Response<Body> {
|
||||
})
|
||||
}
|
||||
|
||||
fn insert_cookie(&mut self, cookie: Cookie) {
|
||||
fn insert_cookie(&mut self, cookie: Cookie<'_>) {
|
||||
if let Ok(val) = header::HeaderValue::from_str(&cookie.to_string()) {
|
||||
self.headers_mut().append("Set-Cookie", val);
|
||||
}
|
||||
@ -176,19 +176,19 @@ impl ResponseExt for Response<Body> {
|
||||
}
|
||||
|
||||
impl Route<'_> {
|
||||
fn method(&mut self, method: Method, dest: fn(Request<Body>) -> BoxResponse) -> &mut Self {
|
||||
fn method(&mut self, method: &Method, dest: fn(Request<Body>) -> BoxResponse) -> &mut Self {
|
||||
self.router.add(&format!("/{}{}", method.as_str(), self.path), dest);
|
||||
self
|
||||
}
|
||||
|
||||
/// Add an endpoint for `GET` requests
|
||||
pub fn get(&mut self, dest: fn(Request<Body>) -> BoxResponse) -> &mut Self {
|
||||
self.method(Method::GET, dest)
|
||||
self.method(&Method::GET, dest)
|
||||
}
|
||||
|
||||
/// Add an endpoint for `POST` requests
|
||||
pub fn post(&mut self, dest: fn(Request<Body>) -> BoxResponse) -> &mut Self {
|
||||
self.method(Method::POST, dest)
|
||||
self.method(&Method::POST, dest)
|
||||
}
|
||||
}
|
||||
|
||||
@ -200,14 +200,14 @@ impl Server {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn at(&mut self, path: &str) -> Route {
|
||||
pub fn at(&mut self, path: &str) -> Route<'_> {
|
||||
Route {
|
||||
path: path.to_owned(),
|
||||
router: &mut self.router,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn listen(self, addr: String) -> Boxed<Result<(), hyper::Error>> {
|
||||
pub fn listen(self, addr: &str) -> Boxed<Result<(), hyper::Error>> {
|
||||
let make_svc = make_service_fn(move |_conn| {
|
||||
// For correct borrowing, these values need to be borrowed
|
||||
let router = self.router.clone();
|
||||
@ -260,7 +260,7 @@ impl Server {
|
||||
});
|
||||
|
||||
// Build SocketAddr from provided address
|
||||
let address = &addr.parse().unwrap_or_else(|_| panic!("Cannot parse {} as address (example format: 0.0.0.0:8080)", addr));
|
||||
let address = &addr.parse().unwrap_or_else(|_| panic!("Cannot parse {addr} as address (example format: 0.0.0.0:8080)"));
|
||||
|
||||
// Bind server to address specified above. Gracefully shut down if CTRL+C is pressed
|
||||
let server = HyperServer::bind(address).serve(make_svc).with_graceful_shutdown(async {
|
||||
@ -376,7 +376,7 @@ fn determine_compressor(accept_encoding: String) -> Option<CompressionType> {
|
||||
|
||||
// The compressor and q-value (if the latter is defined)
|
||||
// will be delimited by semicolons.
|
||||
let mut spl: Split<char> = val.split(';');
|
||||
let mut spl: Split<'_, char> = val.split(';');
|
||||
|
||||
// Get the compressor. For example, in
|
||||
// gzip;q=0.8
|
||||
@ -438,10 +438,10 @@ fn determine_compressor(accept_encoding: String) -> Option<CompressionType> {
|
||||
};
|
||||
}
|
||||
|
||||
if cur_candidate.q != f64::NEG_INFINITY {
|
||||
Some(cur_candidate.alg)
|
||||
} else {
|
||||
if cur_candidate.q == f64::NEG_INFINITY {
|
||||
None
|
||||
} else {
|
||||
Some(cur_candidate.alg)
|
||||
}
|
||||
}
|
||||
|
||||
@ -453,16 +453,16 @@ fn determine_compressor(accept_encoding: String) -> Option<CompressionType> {
|
||||
/// conditions are met:
|
||||
///
|
||||
/// 1. the HTTP client requests a compression encoding in the Content-Encoding
|
||||
/// header (hence the need for the req_headers);
|
||||
/// header (hence the need for the `req_headers`);
|
||||
///
|
||||
/// 2. the content encoding corresponds to a compression algorithm we support;
|
||||
///
|
||||
/// 3. the Media type in the Content-Type response header is text with any
|
||||
/// subtype (e.g. text/plain) or application/json.
|
||||
///
|
||||
/// compress_response returns Ok on successful compression, or if not all three
|
||||
/// `compress_response` returns Ok on successful compression, or if not all three
|
||||
/// conditions above are met. It returns Err if there was a problem decoding
|
||||
/// any header in either req_headers or res, but res will remain intact.
|
||||
/// any header in either `req_headers` or res, but res will remain intact.
|
||||
///
|
||||
/// This function logs errors to stderr, but only in debug mode. No information
|
||||
/// is logged in release builds.
|
||||
@ -601,7 +601,7 @@ fn compress_body(compressor: CompressionType, body_bytes: Vec<u8>) -> Result<Vec
|
||||
|
||||
// This arm is for any requested compressor for which we don't yet
|
||||
// have an implementation.
|
||||
_ => {
|
||||
CompressionType::Passthrough => {
|
||||
let msg = "unsupported compressor".to_string();
|
||||
return Err(msg);
|
||||
}
|
||||
@ -677,7 +677,7 @@ mod tests {
|
||||
|
||||
// Perform the compression.
|
||||
if let Err(e) = block_on(compress_response(&req_headers, &mut res)) {
|
||||
panic!("compress_response(&req_headers, &mut res) => Err(\"{}\")", e);
|
||||
panic!("compress_response(&req_headers, &mut res) => Err(\"{e}\")");
|
||||
};
|
||||
|
||||
// If the content was compressed, we expect the Content-Encoding
|
||||
@ -699,7 +699,7 @@ mod tests {
|
||||
// the Response is the same as what with which we start.
|
||||
let body_vec = match block_on(body::to_bytes(res.body_mut())) {
|
||||
Ok(b) => b.to_vec(),
|
||||
Err(e) => panic!("{}", e),
|
||||
Err(e) => panic!("{e}"),
|
||||
};
|
||||
|
||||
if expected_encoding == CompressionType::Passthrough {
|
||||
@ -715,7 +715,7 @@ mod tests {
|
||||
let mut decoder: Box<dyn io::Read> = match expected_encoding {
|
||||
CompressionType::Gzip => match gzip::Decoder::new(&mut body_cursor) {
|
||||
Ok(dgz) => Box::new(dgz),
|
||||
Err(e) => panic!("{}", e),
|
||||
Err(e) => panic!("{e}"),
|
||||
},
|
||||
|
||||
CompressionType::Brotli => Box::new(BrotliDecompressor::new(body_cursor, expected_lorem_ipsum.len())),
|
||||
@ -725,7 +725,7 @@ mod tests {
|
||||
|
||||
let mut decompressed = Vec::<u8>::new();
|
||||
if let Err(e) = io::copy(&mut decoder, &mut decompressed) {
|
||||
panic!("{}", e);
|
||||
panic!("{e}");
|
||||
};
|
||||
|
||||
assert!(decompressed.eq(&expected_lorem_ipsum));
|
||||
|
@ -42,10 +42,10 @@ const PREFS: [&str; 15] = [
|
||||
// Retrieve cookies from request "Cookie" header
|
||||
pub async fn get(req: Request<Body>) -> Result<Response<Body>, String> {
|
||||
let url = req.uri().to_string();
|
||||
template(SettingsTemplate {
|
||||
Ok(template(&SettingsTemplate {
|
||||
prefs: Preferences::new(&req),
|
||||
url,
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
// Set cookies using response "Set-Cookie" header
|
||||
@ -54,7 +54,7 @@ pub async fn set(req: Request<Body>) -> Result<Response<Body>, String> {
|
||||
let (parts, mut body) = req.into_parts();
|
||||
|
||||
// Grab existing cookies
|
||||
let _cookies: Vec<Cookie> = parts
|
||||
let _cookies: Vec<Cookie<'_>> = parts
|
||||
.headers
|
||||
.get_all("Cookie")
|
||||
.iter()
|
||||
@ -73,7 +73,7 @@ pub async fn set(req: Request<Body>) -> Result<Response<Body>, String> {
|
||||
|
||||
let form = url::form_urlencoded::parse(&body_bytes).collect::<HashMap<_, _>>();
|
||||
|
||||
let mut response = redirect("/settings".to_string());
|
||||
let mut response = redirect("/settings");
|
||||
|
||||
for &name in &PREFS {
|
||||
match form.get(name) {
|
||||
@ -96,7 +96,7 @@ fn set_cookies_method(req: Request<Body>, remove_cookies: bool) -> Response<Body
|
||||
let (parts, _) = req.into_parts();
|
||||
|
||||
// Grab existing cookies
|
||||
let _cookies: Vec<Cookie> = parts
|
||||
let _cookies: Vec<Cookie<'_>> = parts
|
||||
.headers
|
||||
.get_all("Cookie")
|
||||
.iter()
|
||||
@ -112,7 +112,7 @@ fn set_cookies_method(req: Request<Body>, remove_cookies: bool) -> Response<Body
|
||||
None => "/".to_string(),
|
||||
};
|
||||
|
||||
let mut response = redirect(path);
|
||||
let mut response = redirect(&path);
|
||||
|
||||
for name in [PREFS.to_vec(), vec!["subscriptions", "filters"]].concat() {
|
||||
match form.get(name) {
|
||||
|
@ -76,7 +76,7 @@ pub async fn community(req: Request<Body>) -> Result<Response<Body>, String> {
|
||||
}
|
||||
|
||||
if req.param("sub").is_some() && sub_name.starts_with("u_") {
|
||||
return Ok(redirect(["/user/", &sub_name[2..]].concat()));
|
||||
return Ok(redirect(&["/user/", &sub_name[2..]].concat()));
|
||||
}
|
||||
|
||||
// Request subreddit metadata
|
||||
@ -117,11 +117,11 @@ pub async fn community(req: Request<Body>) -> Result<Response<Body>, String> {
|
||||
|
||||
// If all requested subs are filtered, we don't need to fetch posts.
|
||||
if sub_name.split('+').all(|s| filters.contains(s)) {
|
||||
template(SubredditTemplate {
|
||||
Ok(template(&SubredditTemplate {
|
||||
sub,
|
||||
posts: Vec::new(),
|
||||
sort: (sort, param(&path, "t").unwrap_or_default()),
|
||||
ends: (param(&path, "after").unwrap_or_default(), "".to_string()),
|
||||
ends: (param(&path, "after").unwrap_or_default(), String::new()),
|
||||
prefs: Preferences::new(&req),
|
||||
url,
|
||||
redirect_url,
|
||||
@ -129,14 +129,14 @@ pub async fn community(req: Request<Body>) -> Result<Response<Body>, String> {
|
||||
all_posts_filtered: false,
|
||||
all_posts_hidden_nsfw: false,
|
||||
no_posts: false,
|
||||
})
|
||||
}))
|
||||
} else {
|
||||
match Post::fetch(&path, quarantined).await {
|
||||
Ok((mut posts, after)) => {
|
||||
let (_, all_posts_filtered) = filter_posts(&mut posts, &filters);
|
||||
let no_posts = posts.is_empty();
|
||||
let all_posts_hidden_nsfw = !no_posts && (posts.iter().all(|p| p.flags.nsfw) && setting(&req, "show_nsfw") != "on");
|
||||
template(SubredditTemplate {
|
||||
Ok(template(&SubredditTemplate {
|
||||
sub,
|
||||
posts,
|
||||
sort: (sort, param(&path, "t").unwrap_or_default()),
|
||||
@ -148,40 +148,38 @@ pub async fn community(req: Request<Body>) -> Result<Response<Body>, String> {
|
||||
all_posts_filtered,
|
||||
all_posts_hidden_nsfw,
|
||||
no_posts,
|
||||
})
|
||||
}))
|
||||
}
|
||||
Err(msg) => match msg.as_str() {
|
||||
"quarantined" | "gated" => quarantine(req, sub_name, msg),
|
||||
"private" => error(req, format!("r/{} is a private community", sub_name)).await,
|
||||
"banned" => error(req, format!("r/{} has been banned from Reddit", sub_name)).await,
|
||||
_ => error(req, msg).await,
|
||||
"quarantined" | "gated" => Ok(quarantine(&req, sub_name, &msg)),
|
||||
"private" => error(req, &format!("r/{sub_name} is a private community")).await,
|
||||
"banned" => error(req, &format!("r/{sub_name} has been banned from Reddit")).await,
|
||||
_ => error(req, &msg).await,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn quarantine(req: Request<Body>, sub: String, restriction: String) -> Result<Response<Body>, String> {
|
||||
pub fn quarantine(req: &Request<Body>, sub: String, restriction: &str) -> Response<Body> {
|
||||
let wall = WallTemplate {
|
||||
title: format!("r/{} is {}", sub, restriction),
|
||||
title: format!("r/{sub} is {restriction}"),
|
||||
msg: "Please click the button below to continue to this subreddit.".to_string(),
|
||||
url: req.uri().to_string(),
|
||||
sub,
|
||||
prefs: Preferences::new(&req),
|
||||
prefs: Preferences::new(req),
|
||||
};
|
||||
|
||||
Ok(
|
||||
Response::builder()
|
||||
.status(403)
|
||||
.header("content-type", "text/html")
|
||||
.body(wall.render().unwrap_or_default().into())
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
Response::builder()
|
||||
.status(403)
|
||||
.header("content-type", "text/html")
|
||||
.body(wall.render().unwrap_or_default().into())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub async fn add_quarantine_exception(req: Request<Body>) -> Result<Response<Body>, String> {
|
||||
let subreddit = req.param("sub").ok_or("Invalid URL")?;
|
||||
let redir = param(&format!("?{}", req.uri().query().unwrap_or_default()), "redir").ok_or("Invalid URL")?;
|
||||
let mut response = redirect(redir);
|
||||
let mut response = redirect(&redir);
|
||||
response.insert_cookie(
|
||||
Cookie::build((&format!("allow_quaran_{}", subreddit.to_lowercase()), "true"))
|
||||
.path("/")
|
||||
@ -206,9 +204,8 @@ pub async fn subscriptions_filters(req: Request<Body>) -> Result<Response<Body>,
|
||||
if sub == "random" || sub == "randnsfw" {
|
||||
if action.contains(&"filter".to_string()) || action.contains(&"unfilter".to_string()) {
|
||||
return Err("Can't filter random subreddit!".to_string());
|
||||
} else {
|
||||
return Err("Can't subscribe to random subreddit!".to_string());
|
||||
}
|
||||
return Err("Can't subscribe to random subreddit!".to_string());
|
||||
}
|
||||
|
||||
let query = req.uri().query().unwrap_or_default().to_string();
|
||||
@ -219,7 +216,7 @@ pub async fn subscriptions_filters(req: Request<Body>) -> Result<Response<Body>,
|
||||
|
||||
// Retrieve list of posts for these subreddits to extract display names
|
||||
|
||||
let posts = json(format!("/r/{}/hot.json?raw_json=1", sub), true).await;
|
||||
let posts = json(format!("/r/{sub}/hot.json?raw_json=1"), true).await;
|
||||
let display_lookup: Vec<(String, &str)> = match &posts {
|
||||
Ok(posts) => posts["data"]["children"]
|
||||
.as_array()
|
||||
@ -247,7 +244,7 @@ pub async fn subscriptions_filters(req: Request<Body>) -> Result<Response<Body>,
|
||||
display
|
||||
} else {
|
||||
// This subreddit display name isn't known, retrieve it
|
||||
let path: String = format!("/r/{}/about.json?raw_json=1", part);
|
||||
let path: String = format!("/r/{part}/about.json?raw_json=1");
|
||||
display = json(path, true).await;
|
||||
match &display {
|
||||
Ok(display) => display["data"]["display_name"].as_str(),
|
||||
@ -282,13 +279,13 @@ pub async fn subscriptions_filters(req: Request<Body>) -> Result<Response<Body>,
|
||||
|
||||
// Redirect back to subreddit
|
||||
// check for redirect parameter if unsubscribing/unfiltering from outside sidebar
|
||||
let path = if let Some(redirect_path) = param(&format!("?{}", query), "redirect") {
|
||||
format!("/{}", redirect_path)
|
||||
let path = if let Some(redirect_path) = param(&format!("?{query}"), "redirect") {
|
||||
format!("/{redirect_path}")
|
||||
} else {
|
||||
format!("/r/{}", sub)
|
||||
format!("/r/{sub}")
|
||||
};
|
||||
|
||||
let mut response = redirect(path);
|
||||
let mut response = redirect(&path);
|
||||
|
||||
// Delete cookie if empty, else set
|
||||
if sub_list.is_empty() {
|
||||
@ -326,22 +323,22 @@ pub async fn wiki(req: Request<Body>) -> Result<Response<Body>, String> {
|
||||
}
|
||||
|
||||
let page = req.param("page").unwrap_or_else(|| "index".to_string());
|
||||
let path: String = format!("/r/{}/wiki/{}.json?raw_json=1", sub, page);
|
||||
let path: String = format!("/r/{sub}/wiki/{page}.json?raw_json=1");
|
||||
let url = req.uri().to_string();
|
||||
|
||||
match json(path, quarantined).await {
|
||||
Ok(response) => template(WikiTemplate {
|
||||
Ok(response) => Ok(template(&WikiTemplate {
|
||||
sub,
|
||||
wiki: rewrite_urls(response["data"]["content_html"].as_str().unwrap_or("<h3>Wiki not found</h3>")),
|
||||
page,
|
||||
prefs: Preferences::new(&req),
|
||||
url,
|
||||
}),
|
||||
})),
|
||||
Err(msg) => {
|
||||
if msg == "quarantined" || msg == "gated" {
|
||||
quarantine(req, sub, msg)
|
||||
Ok(quarantine(&req, sub, &msg))
|
||||
} else {
|
||||
error(req, msg).await
|
||||
error(req, &msg).await
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -357,13 +354,13 @@ pub async fn sidebar(req: Request<Body>) -> Result<Response<Body>, String> {
|
||||
}
|
||||
|
||||
// Build the Reddit JSON API url
|
||||
let path: String = format!("/r/{}/about.json?raw_json=1", sub);
|
||||
let path: String = format!("/r/{sub}/about.json?raw_json=1");
|
||||
let url = req.uri().to_string();
|
||||
|
||||
// Send a request to the url
|
||||
match json(path, quarantined).await {
|
||||
// If success, receive JSON in response
|
||||
Ok(response) => template(WikiTemplate {
|
||||
Ok(response) => Ok(template(&WikiTemplate {
|
||||
wiki: rewrite_urls(&val(&response, "description_html")),
|
||||
// wiki: format!(
|
||||
// "{}<hr><h1>Moderators</h1><br><ul>{}</ul>",
|
||||
@ -374,12 +371,12 @@ pub async fn sidebar(req: Request<Body>) -> Result<Response<Body>, String> {
|
||||
page: "Sidebar".to_string(),
|
||||
prefs: Preferences::new(&req),
|
||||
url,
|
||||
}),
|
||||
})),
|
||||
Err(msg) => {
|
||||
if msg == "quarantined" || msg == "gated" {
|
||||
quarantine(req, sub, msg)
|
||||
Ok(quarantine(&req, sub, &msg))
|
||||
} else {
|
||||
error(req, msg).await
|
||||
error(req, &msg).await
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -422,7 +419,7 @@ pub async fn sidebar(req: Request<Body>) -> Result<Response<Body>, String> {
|
||||
// SUBREDDIT
|
||||
async fn subreddit(sub: &str, quarantined: bool) -> Result<Subreddit, String> {
|
||||
// Build the Reddit JSON API url
|
||||
let path: String = format!("/r/{}/about.json?raw_json=1", sub);
|
||||
let path: String = format!("/r/{sub}/about.json?raw_json=1");
|
||||
|
||||
// Send a request to the url
|
||||
let res = json(path, quarantined).await?;
|
||||
|
17
src/user.rs
17
src/user.rs
@ -35,9 +35,8 @@ pub async fn profile(req: Request<Body>) -> Result<Response<Body>, String> {
|
||||
|
||||
// Build the Reddit JSON API path
|
||||
let path = format!(
|
||||
"/user/{}/{}.json?{}&raw_json=1",
|
||||
"/user/{}/{listing}.json?{}&raw_json=1",
|
||||
req.param("name").unwrap_or_else(|| "reddit".to_string()),
|
||||
listing,
|
||||
req.uri().query().unwrap_or_default(),
|
||||
);
|
||||
let url = String::from(req.uri().path_and_query().map_or("", |val| val.as_str()));
|
||||
@ -60,11 +59,11 @@ pub async fn profile(req: Request<Body>) -> Result<Response<Body>, String> {
|
||||
|
||||
let filters = get_filters(&req);
|
||||
if filters.contains(&["u_", &username].concat()) {
|
||||
template(UserTemplate {
|
||||
Ok(template(&UserTemplate {
|
||||
user,
|
||||
posts: Vec::new(),
|
||||
sort: (sort, param(&path, "t").unwrap_or_default()),
|
||||
ends: (param(&path, "after").unwrap_or_default(), "".to_string()),
|
||||
ends: (param(&path, "after").unwrap_or_default(), String::new()),
|
||||
listing,
|
||||
prefs: Preferences::new(&req),
|
||||
url,
|
||||
@ -73,7 +72,7 @@ pub async fn profile(req: Request<Body>) -> Result<Response<Body>, String> {
|
||||
all_posts_filtered: false,
|
||||
all_posts_hidden_nsfw: false,
|
||||
no_posts: false,
|
||||
})
|
||||
}))
|
||||
} else {
|
||||
// Request user posts/comments from Reddit
|
||||
match Post::fetch(&path, false).await {
|
||||
@ -81,7 +80,7 @@ pub async fn profile(req: Request<Body>) -> Result<Response<Body>, String> {
|
||||
let (_, all_posts_filtered) = filter_posts(&mut posts, &filters);
|
||||
let no_posts = posts.is_empty();
|
||||
let all_posts_hidden_nsfw = !no_posts && (posts.iter().all(|p| p.flags.nsfw) && setting(&req, "show_nsfw") != "on");
|
||||
template(UserTemplate {
|
||||
Ok(template(&UserTemplate {
|
||||
user,
|
||||
posts,
|
||||
sort: (sort, param(&path, "t").unwrap_or_default()),
|
||||
@ -94,10 +93,10 @@ pub async fn profile(req: Request<Body>) -> Result<Response<Body>, String> {
|
||||
all_posts_filtered,
|
||||
all_posts_hidden_nsfw,
|
||||
no_posts,
|
||||
})
|
||||
}))
|
||||
}
|
||||
// If there is an error show error page
|
||||
Err(msg) => error(req, msg).await,
|
||||
Err(msg) => error(req, &msg).await,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -105,7 +104,7 @@ pub async fn profile(req: Request<Body>) -> Result<Response<Body>, String> {
|
||||
// USER
|
||||
async fn user(name: &str) -> Result<User, String> {
|
||||
// Build the Reddit JSON API path
|
||||
let path: String = format!("/user/{}/about.json?raw_json=1", name);
|
||||
let path: String = format!("/user/{name}/about.json?raw_json=1");
|
||||
|
||||
// Send a request to the url
|
||||
json(path, false).await.map(|res| {
|
||||
|
68
src/utils.rs
68
src/utils.rs
@ -6,6 +6,7 @@ use crate::{client::json, server::RequestExt};
|
||||
use askama::Template;
|
||||
use cookie::Cookie;
|
||||
use hyper::{Body, Request, Response};
|
||||
use log::error;
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use rust_embed::RustEmbed;
|
||||
@ -115,8 +116,8 @@ impl Poll {
|
||||
|
||||
Some(Self {
|
||||
poll_options,
|
||||
total_vote_count,
|
||||
voting_end_timestamp,
|
||||
total_vote_count,
|
||||
})
|
||||
}
|
||||
|
||||
@ -326,9 +327,8 @@ impl Post {
|
||||
};
|
||||
|
||||
// Fetch the list of posts from the JSON response
|
||||
let post_list = match res["data"]["children"].as_array() {
|
||||
Some(list) => list,
|
||||
None => return Err("No posts found".to_string()),
|
||||
let Some(post_list) = res["data"]["children"].as_array() else {
|
||||
return Err("No posts found".to_string());
|
||||
};
|
||||
|
||||
let mut posts: Vec<Self> = Vec::new();
|
||||
@ -383,7 +383,7 @@ impl Post {
|
||||
alt_url: String::new(),
|
||||
width: data["thumbnail_width"].as_i64().unwrap_or_default(),
|
||||
height: data["thumbnail_height"].as_i64().unwrap_or_default(),
|
||||
poster: "".to_string(),
|
||||
poster: String::new(),
|
||||
},
|
||||
media,
|
||||
domain: val(post, "domain"),
|
||||
@ -456,7 +456,7 @@ pub struct Award {
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Award {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{} {} {}", self.name, self.icon_url, self.description)
|
||||
}
|
||||
}
|
||||
@ -472,8 +472,8 @@ impl std::ops::Deref for Awards {
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Awards {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
self.iter().fold(Ok(()), |result, award| result.and_then(|_| writeln!(f, "{}", award)))
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.iter().fold(Ok(()), |result, award| result.and_then(|()| writeln!(f, "{award}")))
|
||||
}
|
||||
}
|
||||
|
||||
@ -602,7 +602,7 @@ impl Preferences {
|
||||
let mut themes = vec!["system".to_string()];
|
||||
for file in ThemeAssets::iter() {
|
||||
let chunks: Vec<&str> = file.as_ref().split(".css").collect();
|
||||
themes.push(chunks[0].to_owned())
|
||||
themes.push(chunks[0].to_owned());
|
||||
}
|
||||
Self {
|
||||
available_themes: themes,
|
||||
@ -656,7 +656,7 @@ pub fn filter_posts(posts: &mut Vec<Post>, filters: &HashSet<String>) -> (u64, b
|
||||
}
|
||||
|
||||
/// Creates a [`Post`] from a provided JSON.
|
||||
pub async fn parse_post(post: &serde_json::Value) -> Post {
|
||||
pub async fn parse_post(post: &Value) -> Post {
|
||||
// Grab UTC time as unix timestamp
|
||||
let (rel_time, created) = time(post["data"]["created_utc"].as_f64().unwrap_or_default());
|
||||
// Parse post score and upvote ratio
|
||||
@ -674,9 +674,8 @@ pub async fn parse_post(post: &serde_json::Value) -> Post {
|
||||
|
||||
let body = if val(post, "removed_by_category") == "moderator" {
|
||||
format!(
|
||||
"<div class=\"md\"><p>[removed] — <a href=\"https://{}{}\">view removed post</a></p></div>",
|
||||
get_setting("REDLIB_PUSHSHIFT_FRONTEND").unwrap_or(String::from(crate::config::DEFAULT_PUSHSHIFT_FRONTEND)),
|
||||
permalink
|
||||
"<div class=\"md\"><p>[removed] — <a href=\"https://{}{permalink}\">view removed post</a></p></div>",
|
||||
get_setting("REDLIB_PUSHSHIFT_FRONTEND").unwrap_or_else(|| String::from(crate::config::DEFAULT_PUSHSHIFT_FRONTEND)),
|
||||
)
|
||||
} else {
|
||||
rewrite_urls(&val(post, "selftext_html"))
|
||||
@ -752,7 +751,7 @@ pub async fn parse_post(post: &serde_json::Value) -> Post {
|
||||
// Grab a query parameter from a url
|
||||
pub fn param(path: &str, value: &str) -> Option<String> {
|
||||
Some(
|
||||
Url::parse(format!("https://libredd.it/{}", path).as_str())
|
||||
Url::parse(format!("https://libredd.it/{path}").as_str())
|
||||
.ok()?
|
||||
.query_pairs()
|
||||
.into_owned()
|
||||
@ -769,7 +768,7 @@ pub fn setting(req: &Request<Body>, name: &str) -> String {
|
||||
.cookie(name)
|
||||
.unwrap_or_else(|| {
|
||||
// If there is no cookie for this setting, try receiving a default from the config
|
||||
if let Some(default) = crate::config::get_setting(&format!("REDLIB_DEFAULT_{}", name.to_uppercase())) {
|
||||
if let Some(default) = get_setting(&format!("REDLIB_DEFAULT_{}", name.to_uppercase())) {
|
||||
Cookie::new(name, default)
|
||||
} else {
|
||||
Cookie::from(name)
|
||||
@ -782,21 +781,21 @@ pub fn setting(req: &Request<Body>, name: &str) -> String {
|
||||
// Retrieve the value of a setting by name or the default value
|
||||
pub fn setting_or_default(req: &Request<Body>, name: &str, default: String) -> String {
|
||||
let value = setting(req, name);
|
||||
if !value.is_empty() {
|
||||
value
|
||||
} else {
|
||||
if value.is_empty() {
|
||||
default
|
||||
} else {
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
// Detect and redirect in the event of a random subreddit
|
||||
pub async fn catch_random(sub: &str, additional: &str) -> Result<Response<Body>, String> {
|
||||
if sub == "random" || sub == "randnsfw" {
|
||||
let new_sub = json(format!("/r/{}/about.json?raw_json=1", sub), false).await?["data"]["display_name"]
|
||||
let new_sub = json(format!("/r/{sub}/about.json?raw_json=1"), false).await?["data"]["display_name"]
|
||||
.as_str()
|
||||
.unwrap_or_default()
|
||||
.to_string();
|
||||
Ok(redirect(format!("/r/{}{}", new_sub, additional)))
|
||||
Ok(redirect(&format!("/r/{new_sub}{additional}")))
|
||||
} else {
|
||||
Err("No redirect needed".to_string())
|
||||
}
|
||||
@ -962,27 +961,26 @@ pub fn val(j: &Value, k: &str) -> String {
|
||||
// NETWORKING
|
||||
//
|
||||
|
||||
pub fn template(t: impl Template) -> Result<Response<Body>, String> {
|
||||
Ok(
|
||||
Response::builder()
|
||||
.status(200)
|
||||
.header("content-type", "text/html")
|
||||
.body(t.render().unwrap_or_default().into())
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
pub fn template(t: &impl Template) -> Response<Body> {
|
||||
Response::builder()
|
||||
.status(200)
|
||||
.header("content-type", "text/html")
|
||||
.body(t.render().unwrap_or_default().into())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn redirect(path: String) -> Response<Body> {
|
||||
pub fn redirect(path: &str) -> Response<Body> {
|
||||
Response::builder()
|
||||
.status(302)
|
||||
.header("content-type", "text/html")
|
||||
.header("Location", &path)
|
||||
.body(format!("Redirecting to <a href=\"{0}\">{0}</a>...", path).into())
|
||||
.header("Location", path)
|
||||
.body(format!("Redirecting to <a href=\"{path}\">{path}</a>...").into())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Renders a generic error landing page.
|
||||
pub async fn error(req: Request<Body>, msg: impl ToString) -> Result<Response<Body>, String> {
|
||||
pub async fn error(req: Request<Body>, msg: &str) -> Result<Response<Body>, String> {
|
||||
error!("Error page rendered: {msg}");
|
||||
let url = req.uri().to_string();
|
||||
let body = ErrorTemplate {
|
||||
msg: msg.to_string(),
|
||||
@ -1003,7 +1001,7 @@ pub async fn error(req: Request<Body>, msg: impl ToString) -> Result<Response<Bo
|
||||
/// subreddits or posts or userpages for users Reddit has deemed NSFW will
|
||||
/// be denied.
|
||||
pub fn sfw_only() -> bool {
|
||||
match crate::config::get_setting("REDLIB_SFW_ONLY") {
|
||||
match get_setting("REDLIB_SFW_ONLY") {
|
||||
Some(val) => val == "on",
|
||||
None => false,
|
||||
}
|
||||
@ -1027,7 +1025,7 @@ pub async fn nsfw_landing(req: Request<Body>, req_url: String) -> Result<Respons
|
||||
|
||||
// Determine from the request URL if the resource is a subreddit, a user
|
||||
// page, or a post.
|
||||
let res: String = if !req.param("name").unwrap_or_default().is_empty() {
|
||||
let resource: String = if !req.param("name").unwrap_or_default().is_empty() {
|
||||
res_type = ResourceType::User;
|
||||
req.param("name").unwrap_or_default()
|
||||
} else if !req.param("id").unwrap_or_default().is_empty() {
|
||||
@ -1039,7 +1037,7 @@ pub async fn nsfw_landing(req: Request<Body>, req_url: String) -> Result<Respons
|
||||
};
|
||||
|
||||
let body = NSFWLandingTemplate {
|
||||
res,
|
||||
res: resource,
|
||||
res_type,
|
||||
prefs: Preferences::new(&req),
|
||||
url: req_url,
|
||||
|
5
static/hls.min.js
vendored
5
static/hls.min.js
vendored
File diff suppressed because one or more lines are too long
@ -114,14 +114,18 @@ pre, form, fieldset, table, th, td, select, input {
|
||||
|
||||
body {
|
||||
background: var(--background);
|
||||
padding-bottom: var(--footer-height);
|
||||
font-size: 15px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
body.card {
|
||||
min-height: calc(100vh - 30px);
|
||||
}
|
||||
|
||||
body.fixed_navbar {
|
||||
min-height: calc(100vh - 90px);
|
||||
padding-top: 60px;
|
||||
padding-bottom: var(--footer-height);
|
||||
min-height: calc(100vh - 60px);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
nav {
|
||||
|
Reference in New Issue
Block a user