* Add RSS feeds * feat(rss): feature-ify rss * feat(rss): config-ify rss * fix(rss): update info page * feat(rss): conditionally add RSS feeds to user and sub pages * feat(rss): implement URLs for RSS
This commit is contained in:
parent
9bdb5c8966
commit
374238abc3
234
Cargo.lock
generated
234
Cargo.lock
generated
@ -98,7 +98,7 @@ dependencies = [
|
||||
"mime_guess",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.68",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -124,7 +124,20 @@ checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.68",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atom_syndication"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2f34613907f31c9dbef0240156db3c9263f34842b6e1a8999d2304ea62c8a30"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"derive_builder 0.20.0",
|
||||
"diligent-date-parser",
|
||||
"never",
|
||||
"quick-xml 0.31.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -236,10 +249,10 @@ version = "0.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "771aa57f3b17da6c8bcacb187bb9ec9bc81c8160e72342e67c329e0e1651a669"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"darling 0.20.9",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.68",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -260,6 +273,15 @@ version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.7"
|
||||
@ -348,14 +370,38 @@ dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.14.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850"
|
||||
dependencies = [
|
||||
"darling_core 0.14.4",
|
||||
"darling_macro 0.14.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.20.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83b2eb4d90d12bdda5ed17de686c2acb4c57914f8f921b8da7e112b5a36f3fe1"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"darling_macro",
|
||||
"darling_core 0.20.9",
|
||||
"darling_macro 0.20.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_core"
|
||||
version = "0.14.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"ident_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim 0.10.0",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -368,8 +414,19 @@ dependencies = [
|
||||
"ident_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim",
|
||||
"syn",
|
||||
"strsim 0.11.1",
|
||||
"syn 2.0.68",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.14.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e"
|
||||
dependencies = [
|
||||
"darling_core 0.14.4",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -378,9 +435,9 @@ version = "0.20.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"darling_core 0.20.9",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.68",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -398,6 +455,68 @@ dependencies = [
|
||||
"powerfmt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_builder"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8d67778784b508018359cbc8696edb3db78160bab2c2a28ba7f56ef6932997f8"
|
||||
dependencies = [
|
||||
"derive_builder_macro 0.12.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_builder"
|
||||
version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0350b5cb0331628a5916d6c5c0b72e97393b8b6b03b47a9284f4e7f5a405ffd7"
|
||||
dependencies = [
|
||||
"derive_builder_macro 0.20.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_builder_core"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c11bdc11a0c47bc7d37d582b5285da6849c96681023680b906673c5707af7b0f"
|
||||
dependencies = [
|
||||
"darling 0.14.4",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_builder_core"
|
||||
version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d48cda787f839151732d396ac69e3473923d54312c070ee21e9effcaa8ca0b1d"
|
||||
dependencies = [
|
||||
"darling 0.20.9",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.68",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_builder_macro"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebcda35c7a396850a55ffeac740804b40ffec779b98fffbb1738f4033f0ee79e"
|
||||
dependencies = [
|
||||
"derive_builder_core 0.12.0",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_builder_macro"
|
||||
version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "206868b8242f27cecce124c19fd88157fbd0dd334df2587f36417bafbc85097b"
|
||||
dependencies = [
|
||||
"derive_builder_core 0.20.0",
|
||||
"syn 2.0.68",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
@ -408,12 +527,30 @@ dependencies = [
|
||||
"crypto-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "diligent-date-parser"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6cf7fe294274a222363f84bcb63cdea762979a0443b4cf1f4f8fd17c86b1182"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dotenvy"
|
||||
version = "0.15.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
|
||||
|
||||
[[package]]
|
||||
name = "encoding_rs"
|
||||
version = "0.8.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.10.2"
|
||||
@ -862,6 +999,12 @@ dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "never"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c96aba5aa877601bb3f6dd6a63a969e1f82e60646e81e71b14496995e9853c91"
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.3"
|
||||
@ -878,6 +1021,15 @@ version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "1.16.0"
|
||||
@ -1002,6 +1154,26 @@ version = "1.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.30.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956"
|
||||
dependencies = [
|
||||
"encoding_rs",
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.31.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33"
|
||||
dependencies = [
|
||||
"encoding_rs",
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.36"
|
||||
@ -1061,6 +1233,7 @@ dependencies = [
|
||||
"pretty_env_logger",
|
||||
"regex",
|
||||
"route-recognizer",
|
||||
"rss",
|
||||
"rust-embed",
|
||||
"sealed_test",
|
||||
"serde",
|
||||
@ -1138,6 +1311,18 @@ version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "afab94fb28594581f62d981211a9a4d53cc8130bbcbbb89a0440d9b8e81a7746"
|
||||
|
||||
[[package]]
|
||||
name = "rss"
|
||||
version = "2.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7b2c77eb4450d7d5f98df52c381cd6c4e19b75dad9209a9530b85a44510219a"
|
||||
dependencies = [
|
||||
"atom_syndication",
|
||||
"derive_builder 0.12.0",
|
||||
"never",
|
||||
"quick-xml 0.30.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-embed"
|
||||
version = "8.4.0"
|
||||
@ -1158,7 +1343,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rust-embed-utils",
|
||||
"syn",
|
||||
"syn 2.0.68",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
@ -1307,7 +1492,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77253fb2d4451418d07025826028bcb96ee42d3e58859689a70ce62908009db6"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.68",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1350,7 +1535,7 @@ checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.68",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1437,6 +1622,12 @@ version = "0.9.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
@ -1449,6 +1640,17 @@ version = "2.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.109"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.68"
|
||||
@ -1498,7 +1700,7 @@ checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.68",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1576,7 +1778,7 @@ checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.68",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1950,7 +2152,7 @@ checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.68",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -42,8 +42,10 @@ fastrand = "2.0.1"
|
||||
log = "0.4.20"
|
||||
pretty_env_logger = "0.5.0"
|
||||
dotenvy = "0.15.7"
|
||||
rss = "2.0.7"
|
||||
arc-swap = "1.7.1"
|
||||
|
||||
|
||||
[dev-dependencies]
|
||||
lipsum = "0.9.0"
|
||||
sealed_test = "1.0.0"
|
||||
|
@ -381,7 +381,8 @@ Assign a default value for each instance-specific setting by passing environment
|
||||
| `ROBOTS_DISABLE_INDEXING` | `["on", "off"]` | `off` | Disables indexing of the instance by search engines. |
|
||||
| `PUSHSHIFT_FRONTEND` | String | `undelete.pullpush.io` | Allows the server to set the Pushshift frontend to be used with "removed" links. |
|
||||
| `PORT` | Integer 0-65535 | `8080` | The **internal** port Redlib listens on. |
|
||||
|
||||
| `ENABLE_RSS` | `["on", "off"]` | `off` | Enables RSS feed generation. |
|
||||
| `FULL_URL` | String | (empty) | Allows for proper URLs (for now, only needed by RSS)
|
||||
## Default user settings
|
||||
|
||||
Assign a default value for each user-modifiable setting by passing environment variables to Redlib in the format `REDLIB_DEFAULT_{Y}`. Replace `{Y}` with the setting name (see list below) in capital letters.
|
||||
|
10
app.json
10
app.json
@ -56,8 +56,8 @@
|
||||
"REDLIB_BANNER": {
|
||||
"required": false
|
||||
},
|
||||
"REDLIB_ROBOTS_DISABLE_INDEXING": {
|
||||
"required": false
|
||||
"REDLIB_ROBOTS_DISABLE_INDEXING": {
|
||||
"required": false
|
||||
},
|
||||
"REDLIB_DEFAULT_SUBSCRIPTIONS": {
|
||||
"required": false
|
||||
@ -70,6 +70,12 @@
|
||||
},
|
||||
"REDLIB_PUSHSHIFT_FRONTEND": {
|
||||
"required": false
|
||||
},
|
||||
"REDLIB_ENABLE_RSS": {
|
||||
"required": false
|
||||
},
|
||||
"REDLIB_FULL_URL": {
|
||||
"required": false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -103,6 +103,12 @@ pub struct Config {
|
||||
#[serde(rename = "REDLIB_PUSHSHIFT_FRONTEND")]
|
||||
#[serde(alias = "LIBREDDIT_PUSHSHIFT_FRONTEND")]
|
||||
pub(crate) pushshift: Option<String>,
|
||||
|
||||
#[serde(rename = "REDLIB_ENABLE_RSS")]
|
||||
pub(crate) enable_rss: Option<String>,
|
||||
|
||||
#[serde(rename = "REDLIB_FULL_URL")]
|
||||
pub(crate) full_url: Option<String>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
@ -148,6 +154,8 @@ impl Config {
|
||||
banner: parse("REDLIB_BANNER"),
|
||||
robots_disable_indexing: parse("REDLIB_ROBOTS_DISABLE_INDEXING"),
|
||||
pushshift: parse("REDLIB_PUSHSHIFT_FRONTEND"),
|
||||
enable_rss: parse("REDLIB_ENABLE_RSS"),
|
||||
full_url: parse("REDLIB_FULL_URL"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -175,6 +183,8 @@ fn get_setting_from_config(name: &str, config: &Config) -> Option<String> {
|
||||
"REDLIB_BANNER" => config.banner.clone(),
|
||||
"REDLIB_ROBOTS_DISABLE_INDEXING" => config.robots_disable_indexing.clone(),
|
||||
"REDLIB_PUSHSHIFT_FRONTEND" => config.pushshift.clone(),
|
||||
"REDLIB_ENABLE_RSS" => config.enable_rss.clone(),
|
||||
"REDLIB_FULL_URL" => config.full_url.clone(),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
@ -126,6 +126,8 @@ impl InstanceInfo {
|
||||
["Compile mode", &self.compile_mode],
|
||||
["SFW only", &convert(&self.config.sfw_only)],
|
||||
["Pushshift frontend", &convert(&self.config.pushshift)],
|
||||
["RSS enabled", &convert(&self.config.enable_rss)],
|
||||
["Full URL", &convert(&self.config.full_url)],
|
||||
//TODO: fallback to crate::config::DEFAULT_PUSHSHIFT_FRONTEND
|
||||
])
|
||||
.with_header_row(["Settings"]),
|
||||
@ -165,6 +167,8 @@ impl InstanceInfo {
|
||||
Compile mode: {}\n
|
||||
SFW only: {:?}\n
|
||||
Pushshift frontend: {:?}\n
|
||||
RSS enabled: {:?}\n
|
||||
Full URL: {:?}\n
|
||||
Config:\n
|
||||
Banner: {:?}\n
|
||||
Hide awards: {:?}\n
|
||||
@ -189,6 +193,8 @@ impl InstanceInfo {
|
||||
self.deploy_unix_ts,
|
||||
self.compile_mode,
|
||||
self.config.sfw_only,
|
||||
self.config.enable_rss,
|
||||
self.config.full_url,
|
||||
self.config.pushshift,
|
||||
self.config.banner,
|
||||
self.config.default_hide_awards,
|
||||
|
@ -254,6 +254,7 @@ async fn main() {
|
||||
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").boxed());
|
||||
app.at("/user/:name.rss").get(|r| user::rss(r).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());
|
||||
@ -265,6 +266,9 @@ async fn main() {
|
||||
app.at("/settings/restore").get(|r| settings::restore(r).boxed());
|
||||
app.at("/settings/update").get(|r| settings::update(r).boxed());
|
||||
|
||||
// RSS Subscriptions
|
||||
app.at("/r/:sub.rss").get(|r| subreddit::rss(r).boxed());
|
||||
|
||||
// Subreddit services
|
||||
app
|
||||
.at("/r/:sub")
|
||||
|
@ -1,3 +1,4 @@
|
||||
use crate::{config, utils};
|
||||
// CRATES
|
||||
use crate::utils::{
|
||||
catch_random, error, filter_posts, format_num, format_url, get_filters, nsfw_landing, param, redirect, rewrite_urls, setting, template, val, Post, Preferences, Subreddit,
|
||||
@ -459,6 +460,56 @@ async fn subreddit(sub: &str, quarantined: bool) -> Result<Subreddit, String> {
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn rss(req: Request<Body>) -> Result<Response<Body>, String> {
|
||||
if config::get_setting("REDLIB_ENABLE_RSS").is_none() {
|
||||
return Ok(error(req, "RSS is disabled on this instance.").await.unwrap_or_default());
|
||||
}
|
||||
|
||||
use hyper::header::CONTENT_TYPE;
|
||||
use rss::{ChannelBuilder, Item};
|
||||
|
||||
// Get subreddit
|
||||
let sub = req.param("sub").unwrap_or_default();
|
||||
let post_sort = req.cookie("post_sort").map_or_else(|| "hot".to_string(), |c| c.value().to_string());
|
||||
let sort = req.param("sort").unwrap_or_else(|| req.param("id").unwrap_or(post_sort));
|
||||
|
||||
// Get path
|
||||
let path = format!("/r/{sub}/{sort}.json?{}", req.uri().query().unwrap_or_default());
|
||||
|
||||
// Get subreddit data
|
||||
let subreddit = subreddit(&sub, false).await?;
|
||||
|
||||
// Get posts
|
||||
let (posts, _) = Post::fetch(&path, false).await?;
|
||||
|
||||
// Build the RSS feed
|
||||
let channel = ChannelBuilder::default()
|
||||
.title(&subreddit.title)
|
||||
.description(&subreddit.description)
|
||||
.items(
|
||||
posts
|
||||
.into_iter()
|
||||
.map(|post| Item {
|
||||
title: Some(post.title.to_string()),
|
||||
link: Some(utils::get_post_url(&post)),
|
||||
author: Some(post.author.name),
|
||||
content: Some(rewrite_urls(&post.body)),
|
||||
..Default::default()
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.build();
|
||||
|
||||
// Serialize the feed to RSS
|
||||
let body = channel.to_string().into_bytes();
|
||||
|
||||
// Create the HTTP response
|
||||
let mut res = Response::new(Body::from(body));
|
||||
res.headers_mut().insert(CONTENT_TYPE, hyper::header::HeaderValue::from_static("application/rss+xml"));
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_fetching_subreddit() {
|
||||
let subreddit = subreddit("rust", false).await;
|
||||
|
51
src/user.rs
51
src/user.rs
@ -2,6 +2,7 @@
|
||||
use crate::client::json;
|
||||
use crate::server::RequestExt;
|
||||
use crate::utils::{error, filter_posts, format_url, get_filters, nsfw_landing, param, setting, template, Post, Preferences, User};
|
||||
use crate::{config, utils};
|
||||
use askama::Template;
|
||||
use hyper::{Body, Request, Response};
|
||||
use time::{macros::format_description, OffsetDateTime};
|
||||
@ -129,6 +130,56 @@ async fn user(name: &str) -> Result<User, String> {
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn rss(req: Request<Body>) -> Result<Response<Body>, String> {
|
||||
if config::get_setting("REDLIB_ENABLE_RSS").is_none() {
|
||||
return Ok(error(req, "RSS is disabled on this instance.").await.unwrap_or_default());
|
||||
}
|
||||
use crate::utils::rewrite_urls;
|
||||
use hyper::header::CONTENT_TYPE;
|
||||
use rss::{ChannelBuilder, Item};
|
||||
|
||||
// Get user
|
||||
let user_str = req.param("name").unwrap_or_default();
|
||||
|
||||
let listing = req.param("listing").unwrap_or_else(|| "overview".to_string());
|
||||
|
||||
// Get path
|
||||
let path = format!("/user/{user_str}/{listing}.json?{}&raw_json=1", req.uri().query().unwrap_or_default(),);
|
||||
|
||||
// Get user
|
||||
let user_obj = user(&user_str).await.unwrap_or_default();
|
||||
|
||||
// Get posts
|
||||
let (posts, _) = Post::fetch(&path, false).await?;
|
||||
|
||||
// Build the RSS feed
|
||||
let channel = ChannelBuilder::default()
|
||||
.title(user_str)
|
||||
.description(user_obj.description)
|
||||
.items(
|
||||
posts
|
||||
.into_iter()
|
||||
.map(|post| Item {
|
||||
title: Some(post.title.to_string()),
|
||||
link: Some(utils::get_post_url(&post)),
|
||||
author: Some(post.author.name),
|
||||
content: Some(rewrite_urls(&post.body)),
|
||||
..Default::default()
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.build();
|
||||
|
||||
// Serialize the feed to RSS
|
||||
let body = channel.to_string().into_bytes();
|
||||
|
||||
// Create the HTTP response
|
||||
let mut res = Response::new(Body::from(body));
|
||||
res.headers_mut().insert(CONTENT_TYPE, hyper::header::HeaderValue::from_static("application/rss+xml"));
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_fetching_user() {
|
||||
let user = user("spez").await;
|
||||
|
30
src/utils.rs
30
src/utils.rs
@ -1,5 +1,5 @@
|
||||
#![allow(dead_code)]
|
||||
use crate::config::get_setting;
|
||||
use crate::config::{self, get_setting};
|
||||
//
|
||||
// CRATES
|
||||
//
|
||||
@ -15,6 +15,7 @@ use serde_json::Value;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::env;
|
||||
use std::str::FromStr;
|
||||
use std::string::ToString;
|
||||
use time::{macros::format_description, Duration, OffsetDateTime};
|
||||
use url::Url;
|
||||
|
||||
@ -327,6 +328,7 @@ pub struct Post {
|
||||
pub gallery: Vec<GalleryMedia>,
|
||||
pub awards: Awards,
|
||||
pub nsfw: bool,
|
||||
pub out_url: Option<String>,
|
||||
pub ws_url: String,
|
||||
}
|
||||
|
||||
@ -435,6 +437,7 @@ impl Post {
|
||||
awards,
|
||||
nsfw: post["data"]["over_18"].as_bool().unwrap_or_default(),
|
||||
ws_url: val(post, "websocket_url"),
|
||||
out_url: post["data"]["url_overridden_by_dest"].as_str().map(|a| a.to_string()),
|
||||
});
|
||||
}
|
||||
Ok((posts, res["data"]["after"].as_str().unwrap_or_default().to_string()))
|
||||
@ -770,6 +773,7 @@ pub async fn parse_post(post: &Value) -> Post {
|
||||
awards,
|
||||
nsfw: post["data"]["over_18"].as_bool().unwrap_or_default(),
|
||||
ws_url: val(post, "websocket_url"),
|
||||
out_url: post["data"]["url_overridden_by_dest"].as_str().map(|a| a.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
@ -1082,6 +1086,16 @@ pub fn sfw_only() -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if the config/env variable REDLIB_ENABLE_RSS is set to "on".
|
||||
/// If this variable is set as such, the instance will enable RSS feeds.
|
||||
/// Otherwise, the instance will not provide RSS feeds.
|
||||
pub fn enable_rss() -> bool {
|
||||
match get_setting("REDLIB_ENABLE_RSS") {
|
||||
Some(val) => val == "on",
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
// Determines if a request shoud redirect to a nsfw landing gate.
|
||||
pub fn should_be_nsfw_gated(req: &Request<Body>, req_url: &str) -> bool {
|
||||
let sfw_instance = sfw_only();
|
||||
@ -1137,6 +1151,20 @@ pub fn url_path_basename(path: &str) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the URL of a post, as needed by RSS feeds
|
||||
pub fn get_post_url(post: &Post) -> String {
|
||||
if let Some(out_url) = &post.out_url {
|
||||
// Handle cross post
|
||||
if out_url.starts_with("/r/") {
|
||||
format!("{}{}", config::get_setting("REDLIB_FULL_URL").unwrap_or_default(), out_url)
|
||||
} else {
|
||||
out_url.to_string()
|
||||
}
|
||||
} else {
|
||||
format!("{}{}", config::get_setting("REDLIB_FULL_URL").unwrap_or_default(), post.permalink)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{format_num, format_url, rewrite_urls};
|
||||
|
2242
static/style.css
2242
static/style.css
File diff suppressed because it is too large
Load Diff
@ -130,7 +130,13 @@
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% if crate::utils::enable_rss() %}
|
||||
<div id="sub_rss">
|
||||
<a href="/r/{{ sub.name }}.rss" title="RSS feed for r/{{ sub.name }}">
|
||||
<button class="subscribe">RSS feed</button >
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</details>
|
||||
<details class="panel" id="sidebar">
|
||||
|
@ -1,140 +1,178 @@
|
||||
{% extends "base.html" %}
|
||||
{% import "utils.html" as utils %}
|
||||
{% extends "base.html" %} {% import "utils.html" as utils %} {% block search %}
|
||||
{% call utils::search("".to_owned(), "") %} {% endblock %} {% block title %}{{
|
||||
user.name.replace("u/", "") }} (u/{{ user.name }}) - Redlib{% endblock %} {%
|
||||
block subscriptions %} {% call utils::sub_list("") %} {% endblock %} {% block
|
||||
body %}
|
||||
<main>
|
||||
{% if !is_filtered %}
|
||||
<div id="column_one">
|
||||
<form id="sort">
|
||||
<div id="listing_options">
|
||||
{% call utils::sort(["/user/", user.name.as_str()].concat(),
|
||||
["overview", "comments", "submitted"], listing) %}
|
||||
</div>
|
||||
<select id="sort_select" name="sort">
|
||||
{% call utils::options(sort.0, ["hot", "new", "top",
|
||||
"controversial"], "") %}</select
|
||||
>{% if sort.0 == "top" || sort.0 == "controversial" %}<select
|
||||
id="timeframe"
|
||||
name="t"
|
||||
>
|
||||
{% call utils::options(sort.1, ["hour", "day", "week", "month",
|
||||
"year", "all"], "all") %}</select
|
||||
>{% endif %}<button id="sort_submit" class="submit">
|
||||
<svg
|
||||
width="15"
|
||||
viewBox="0 0 110 100"
|
||||
fill="none"
|
||||
stroke-width="10"
|
||||
stroke-linecap="round"
|
||||
>
|
||||
<path d="M20 50 H100" />
|
||||
<path d="M75 15 L100 50 L75 85" />
|
||||
→
|
||||
</svg>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
{% block search %}
|
||||
{% call utils::search("".to_owned(), "") %}
|
||||
{% endblock %}
|
||||
|
||||
{% block title %}{{ user.name.replace("u/", "") }} (u/{{ user.name }}) - Redlib{% endblock %}
|
||||
|
||||
{% block subscriptions %}
|
||||
{% call utils::sub_list("") %}
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<main>
|
||||
{% if !is_filtered %}
|
||||
<div id="column_one">
|
||||
<form id="sort">
|
||||
<div id="listing_options">
|
||||
{% call utils::sort(["/user/", user.name.as_str()].concat(), ["overview", "comments", "submitted"], listing) %}
|
||||
</div>
|
||||
<select id="sort_select" name="sort">
|
||||
{% call utils::options(sort.0, ["hot", "new", "top", "controversial"], "") %}
|
||||
</select>{% if sort.0 == "top" || sort.0 == "controversial" %}<select id="timeframe" name="t">
|
||||
{% call utils::options(sort.1, ["hour", "day", "week", "month", "year", "all"], "all") %}
|
||||
</select>{% endif %}<button id="sort_submit" class="submit">
|
||||
<svg width="15" viewBox="0 0 110 100" fill="none" stroke-width="10" stroke-linecap="round">
|
||||
<path d="M20 50 H100" />
|
||||
<path d="M75 15 L100 50 L75 85" />
|
||||
→
|
||||
</svg>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
{% if all_posts_hidden_nsfw %}
|
||||
<center>All posts are hidden because they are NSFW. Enable "Show NSFW posts" in settings to view.</center>
|
||||
{% endif %}
|
||||
|
||||
{% if no_posts %}
|
||||
<center>No posts were found.</center>
|
||||
{% endif %}
|
||||
|
||||
{% if all_posts_filtered %}
|
||||
<center>(All content on this page has been filtered)</center>
|
||||
{% else %}
|
||||
<div id="posts">
|
||||
{% for post in posts %}
|
||||
|
||||
{% if post.flags.nsfw && prefs.show_nsfw != "on" %}
|
||||
{% else if !post.title.is_empty() %}
|
||||
{% call utils::post_in_list(post) %}
|
||||
{% else %}
|
||||
<div class="comment user-comment">
|
||||
<div class="comment_left">
|
||||
<p class="comment_score" title="{{ post.score.1 }}">
|
||||
{% if prefs.hide_score != "on" %}
|
||||
{{ post.score.0 }}
|
||||
{% else %}
|
||||
•
|
||||
{% endif %}
|
||||
{% if all_posts_hidden_nsfw %}
|
||||
<center>
|
||||
All posts are hidden because they are NSFW. Enable "Show NSFW posts"
|
||||
in settings to view.
|
||||
</center>
|
||||
{% endif %} {% if no_posts %}
|
||||
<center>No posts were found.</center>
|
||||
{% endif %} {% if all_posts_filtered %}
|
||||
<center>(All content on this page has been filtered)</center>
|
||||
{% else %}
|
||||
<div id="posts">
|
||||
{% for post in posts %} {% if post.flags.nsfw && prefs.show_nsfw !=
|
||||
"on" %} {% else if !post.title.is_empty() %} {% call
|
||||
utils::post_in_list(post) %} {% else %}
|
||||
<div class="comment user-comment">
|
||||
<div class="comment_left">
|
||||
<p class="comment_score" title="{{ post.score.1 }}">
|
||||
{% if prefs.hide_score != "on" %} {{ post.score.0 }} {%
|
||||
else %} • {% endif %}
|
||||
</p>
|
||||
<div class="line"></div>
|
||||
</div>
|
||||
<details class="comment_right" open>
|
||||
<summary class="comment_data">
|
||||
<a class="comment_link" href="{{ post.permalink }}" title="{{ post.link_title }}">{{ post.link_title }}</a>
|
||||
<div class="user_comment_data_divider">
|
||||
<span class="created-in"> in </span>
|
||||
<a class="comment_subreddit" href="/r/{{ post.community }}">r/{{ post.community }}</a>
|
||||
<span class="dot">•</span>
|
||||
<span class="created" title="{{ post.created }}"> {{ post.rel_time }}</span>
|
||||
</div>
|
||||
</summary>
|
||||
<p class="comment_body">{{ post.body|safe }}</p>
|
||||
</details>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if prefs.use_hls == "on" %}
|
||||
<script src="/hls.min.js"></script>
|
||||
<script src="/playHLSVideo.js"></script>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="line"></div>
|
||||
</div>
|
||||
<details class="comment_right" open>
|
||||
<summary class="comment_data">
|
||||
<a
|
||||
class="comment_link"
|
||||
href="{{ post.permalink }}"
|
||||
title="{{ post.link_title }}"
|
||||
>{{ post.link_title }}</a
|
||||
>
|
||||
<div class="user_comment_data_divider">
|
||||
<span class="created-in"> in </span>
|
||||
<a
|
||||
class="comment_subreddit"
|
||||
href="/r/{{ post.community }}"
|
||||
>r/{{ post.community }}</a
|
||||
>
|
||||
<span class="dot">•</span>
|
||||
<span class="created" title="{{ post.created }}"
|
||||
> {{ post.rel_time }}</span
|
||||
>
|
||||
</div>
|
||||
</summary>
|
||||
<p class="comment_body">{{ post.body|safe }}</p>
|
||||
</details>
|
||||
</div>
|
||||
{% endif %} {% endfor %} {% if prefs.use_hls == "on" %}
|
||||
<script src="/hls.min.js"></script>
|
||||
<script src="/playHLSVideo.js"></script>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<footer>
|
||||
{% if ends.0 != "" %}
|
||||
<a href="?sort={{ sort.0 }}&t={{ sort.1 }}&before={{ ends.0 }}" accesskey="P">PREV</a>
|
||||
{% endif %}
|
||||
|
||||
{% if ends.1 != "" %}
|
||||
<a href="?sort={{ sort.0 }}&t={{ sort.1 }}&after={{ ends.1 }}" accesskey="N">NEXT</a>
|
||||
{% endif %}
|
||||
</footer>
|
||||
</div>
|
||||
{% endif %}
|
||||
<aside>
|
||||
{% if is_filtered %}
|
||||
<center>(Content from u/{{ user.name }} has been filtered)</center>
|
||||
{% endif %}
|
||||
<div class="panel" id="user">
|
||||
<img loading="lazy" id="user_icon" src="{{ user.icon }}" alt="User icon">
|
||||
<h1 id="user_title">{{ user.title }}</h1>
|
||||
<p id="user_name">u/{{ user.name }}</p>
|
||||
<div id="user_description">{{ user.description }}</div>
|
||||
<div id="user_details">
|
||||
<label>Karma</label>
|
||||
<label>Created</label>
|
||||
<div>{{ user.karma }}</div>
|
||||
<div>{{ user.created }}</div>
|
||||
</div>
|
||||
<div id="user_actions">
|
||||
{% let name = ["u_", user.name.as_str()].join("") %}
|
||||
<div id="user_subscription">
|
||||
{% if prefs.subscriptions.contains(name) %}
|
||||
<form action="/r/{{ name }}/unsubscribe?redirect={{ redirect_url }}" method="POST">
|
||||
<button class="unsubscribe">Unfollow</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<form action="/r/{{ name }}/subscribe?redirect={{ redirect_url }}" method="POST">
|
||||
<button class="subscribe">Follow</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div id="user_filter">
|
||||
{% if prefs.filters.contains(name) %}
|
||||
<form action="/r/{{ name }}/unfilter?redirect={{ redirect_url }}" method="POST">
|
||||
<button class="unfilter">Unfilter</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<form action="/r/{{ name }}/filter?redirect={{ redirect_url }}" method="POST">
|
||||
<button class="filter">Filter</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
</main>
|
||||
<footer>
|
||||
{% if ends.0 != "" %}
|
||||
<a
|
||||
href="?sort={{ sort.0 }}&t={{ sort.1 }}&before={{ ends.0 }}"
|
||||
accesskey="P"
|
||||
>PREV</a
|
||||
>
|
||||
{% endif %} {% if ends.1 != "" %}
|
||||
<a
|
||||
href="?sort={{ sort.0 }}&t={{ sort.1 }}&after={{ ends.1 }}"
|
||||
accesskey="N"
|
||||
>NEXT</a
|
||||
>
|
||||
{% endif %}
|
||||
</footer>
|
||||
</div>
|
||||
{% endif %}
|
||||
<aside>
|
||||
{% if is_filtered %}
|
||||
<center>(Content from u/{{ user.name }} has been filtered)</center>
|
||||
{% endif %}
|
||||
<div class="panel" id="user">
|
||||
<img
|
||||
loading="lazy"
|
||||
id="user_icon"
|
||||
src="{{ user.icon }}"
|
||||
alt="User icon"
|
||||
/>
|
||||
<h1 id="user_title">{{ user.title }}</h1>
|
||||
<p id="user_name">u/{{ user.name }}</p>
|
||||
<div id="user_description">{{ user.description }}</div>
|
||||
<div id="user_details">
|
||||
<label>Karma</label>
|
||||
<label>Created</label>
|
||||
<div>{{ user.karma }}</div>
|
||||
<div>{{ user.created }}</div>
|
||||
</div>
|
||||
<div id="user_actions">
|
||||
{% let name = ["u_", user.name.as_str()].join("") %}
|
||||
<div id="user_subscription">
|
||||
{% if prefs.subscriptions.contains(name) %}
|
||||
<form
|
||||
action="/r/{{ name }}/unsubscribe?redirect={{ redirect_url }}"
|
||||
method="POST"
|
||||
>
|
||||
<button class="unsubscribe">Unfollow</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<form
|
||||
action="/r/{{ name }}/subscribe?redirect={{ redirect_url }}"
|
||||
method="POST"
|
||||
>
|
||||
<button class="subscribe">Follow</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div id="user_filter">
|
||||
{% if prefs.filters.contains(name) %}
|
||||
<form
|
||||
action="/r/{{ name }}/unfilter?redirect={{ redirect_url }}"
|
||||
method="POST"
|
||||
>
|
||||
<button class="unfilter">Unfilter</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<form
|
||||
action="/r/{{ name }}/filter?redirect={{ redirect_url }}"
|
||||
method="POST"
|
||||
>
|
||||
<button class="filter">Filter</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if crate::utils::enable_rss() %}
|
||||
<div id="user_rss">
|
||||
<a
|
||||
href="/u/{{ user.name }}.rss"
|
||||
title="RSS feed for u/{{ user.name }}"
|
||||
>
|
||||
<button class="subscribe">RSS feed</button>
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
</main>
|
||||
{% endblock %}
|
||||
|
Loading…
Reference in New Issue
Block a user