Compare commits
No commits in common. "main" and "0.1.0" have entirely different histories.
|
@ -1,5 +1,6 @@
|
||||||
/result
|
# Generated by Cargo
|
||||||
.direnv/
|
|
||||||
coverage/
|
|
||||||
debug/
|
debug/
|
||||||
target/
|
target/
|
||||||
|
|
||||||
|
# Code coverage results
|
||||||
|
coverage/
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
20
Cargo.toml
20
Cargo.toml
|
@ -1,9 +1,9 @@
|
||||||
[package]
|
[package]
|
||||||
name = "steam-rss"
|
name = "steam-rss"
|
||||||
description = "Get RSS feeds for Steam games."
|
description = "Get RSS feeds for Steam games."
|
||||||
repository = "https://github.com/Bauke/steam-rss"
|
repository = "https://git.bauke.xyz/Bauke/steam-rss"
|
||||||
license = "AGPL-3.0-or-later"
|
license = "AGPL-3.0-or-later"
|
||||||
version = "0.2.2"
|
version = "0.1.0"
|
||||||
authors = ["Bauke <me@bauke.xyz>"]
|
authors = ["Bauke <me@bauke.xyz>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
|
@ -11,21 +11,11 @@ edition = "2021"
|
||||||
name = "steam-rss"
|
name = "steam-rss"
|
||||||
path = "source/main.rs"
|
path = "source/main.rs"
|
||||||
|
|
||||||
[lints.clippy]
|
|
||||||
missing_docs_in_private_items = "warn"
|
|
||||||
|
|
||||||
[lints.rust]
|
|
||||||
missing_docs = "warn"
|
|
||||||
unsafe_code = "forbid"
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
color-eyre = "0.6.2"
|
color-eyre = "0.6.2"
|
||||||
opml = "1.1.6"
|
indicatif = "0.17.1"
|
||||||
regex = "1.10.3"
|
ureq = "2.5.0"
|
||||||
serde = "1.0.195"
|
|
||||||
serde_json = "1.0.111"
|
|
||||||
ureq = "2.9.1"
|
|
||||||
|
|
||||||
[dependencies.clap]
|
[dependencies.clap]
|
||||||
features = ["derive"]
|
features = ["derive"]
|
||||||
version = "4.4.18"
|
version = "3.2.22"
|
||||||
|
|
|
@ -1,21 +1,33 @@
|
||||||
# Do a full check of everything.
|
[tasks.fmt]
|
||||||
[tasks.complete-check]
|
command = "cargo"
|
||||||
dependencies = [
|
args = ["fmt", "${@}"]
|
||||||
"format",
|
|
||||||
"spellcheck",
|
[tasks.check]
|
||||||
"check",
|
command = "cargo"
|
||||||
"clippy",
|
args = ["check", "${@}"]
|
||||||
"test",
|
|
||||||
"code-coverage",
|
[tasks.clippy]
|
||||||
"docs",
|
command = "cargo"
|
||||||
"build",
|
args = ["clippy", "${@}"]
|
||||||
"audit-flow",
|
|
||||||
"outdated-flow",
|
[tasks.test]
|
||||||
]
|
command = "cargo"
|
||||||
|
args = ["test", "${@}"]
|
||||||
|
|
||||||
|
[tasks.doc]
|
||||||
|
command = "cargo"
|
||||||
|
args = ["doc", "${@}"]
|
||||||
|
|
||||||
|
[tasks.build]
|
||||||
|
command = "cargo"
|
||||||
|
args = ["build", "${@}"]
|
||||||
|
|
||||||
|
[tasks.complete-check]
|
||||||
|
dependencies = ["fmt", "check", "clippy", "test", "doc", "build"]
|
||||||
|
|
||||||
# Run cargo-tarpaulin and output the test coverage.
|
|
||||||
[tasks.code-coverage]
|
[tasks.code-coverage]
|
||||||
workspace = false
|
workspace = false
|
||||||
|
install_crate = "cargo-tarpaulin"
|
||||||
command = "cargo"
|
command = "cargo"
|
||||||
args = [
|
args = [
|
||||||
"tarpaulin",
|
"tarpaulin",
|
||||||
|
@ -23,10 +35,5 @@ args = [
|
||||||
"--out=html",
|
"--out=html",
|
||||||
"--output-dir=coverage",
|
"--output-dir=coverage",
|
||||||
"--skip-clean",
|
"--skip-clean",
|
||||||
"--target-dir=target/tarpaulin",
|
"--target-dir=target/tarpaulin"
|
||||||
]
|
]
|
||||||
|
|
||||||
# Do a source code spellcheck.
|
|
||||||
[tasks.spellcheck]
|
|
||||||
clear = true
|
|
||||||
command = "typos"
|
|
||||||
|
|
33
README.md
33
README.md
|
@ -1,25 +1,10 @@
|
||||||
# Steam ❤ RSS
|
# Steam RSS
|
||||||
|
|
||||||
> **Get RSS feeds for Steam games.**
|
> **Get RSS feeds for Steam games.**
|
||||||
|
|
||||||
## Features
|
*AGPL-3.0-or-later*
|
||||||
|
|
||||||
* Get RSS feeds from a game's AppID or store page.
|
## `--help`
|
||||||
* Get RSS feeds for all games from a user profile.
|
|
||||||
* Verify potential feeds by checking if they return `text/xml`.
|
|
||||||
* Output feeds as an OPML file for easy importing.
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
### Cargo
|
|
||||||
|
|
||||||
With a working [Rust and Cargo](https://www.rust-lang.org/learn/get-started) installation, you can install `steam-rss` from [Crates.io](https://crates.io/crates/steam-rss).
|
|
||||||
|
|
||||||
```
|
|
||||||
cargo install steam-rss
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
```
|
```
|
||||||
USAGE:
|
USAGE:
|
||||||
|
@ -28,21 +13,9 @@ USAGE:
|
||||||
OPTIONS:
|
OPTIONS:
|
||||||
-a, --appid <APPID> A game's AppID, can be used multiple times
|
-a, --appid <APPID> A game's AppID, can be used multiple times
|
||||||
-h, --help Print help information
|
-h, --help Print help information
|
||||||
--opml Output the feeds as OPML
|
|
||||||
-t, --timeout <TIMEOUT> The time in milliseconds to sleep between HTTP requests [default:
|
-t, --timeout <TIMEOUT> The time in milliseconds to sleep between HTTP requests [default:
|
||||||
250]
|
250]
|
||||||
--url <URL> A game's store URL, can be used multiple times
|
|
||||||
--user <USER> A person's steamcommunity.com ID or full URL, can be used multiple
|
|
||||||
times
|
|
||||||
-v, --verify Verify potential feeds by downloading them and checking if they
|
-v, --verify Verify potential feeds by downloading them and checking if they
|
||||||
return XML
|
return XML
|
||||||
-V, --version Print version information
|
-V, --version Print version information
|
||||||
```
|
```
|
||||||
|
|
||||||
## Feedback
|
|
||||||
|
|
||||||
Found a problem or want to request a new feature? Email [me@bauke.xyz](mailto:me@bauke.xyz) and I'll see what I can do for you.
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
Distributed under the [AGPL-3.0-or-later](https://spdx.org/licenses/AGPL-3.0-or-later.html) license, see [LICENSE](https://github.com/Bauke/steam-rss/blob/main/LICENSE) for more information.
|
|
||||||
|
|
17
default.nix
17
default.nix
|
@ -1,17 +0,0 @@
|
||||||
{ lib, rustPlatform }:
|
|
||||||
|
|
||||||
rustPlatform.buildRustPackage rec {
|
|
||||||
pname = "steam-rss";
|
|
||||||
version = "0.2.2";
|
|
||||||
src = ./.;
|
|
||||||
cargoLock.lockFile = ./Cargo.lock;
|
|
||||||
|
|
||||||
meta = with lib; {
|
|
||||||
description = "Get RSS feeds for Steam games";
|
|
||||||
homepage = "https://github.com/Bauke/steam-rss";
|
|
||||||
changelog = "https://github.com/Bauke/steam-rss/releases/tag/${version}";
|
|
||||||
license = with licenses; [ agpl3Plus ];
|
|
||||||
maintainers = with maintainers; [ Bauke ];
|
|
||||||
mainProgram = "steam-rss";
|
|
||||||
};
|
|
||||||
}
|
|
128
flake.lock
128
flake.lock
|
@ -1,128 +0,0 @@
|
||||||
{
|
|
||||||
"nodes": {
|
|
||||||
"flake-utils": {
|
|
||||||
"inputs": {
|
|
||||||
"systems": "systems"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1705309234,
|
|
||||||
"narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=",
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"flake-utils_2": {
|
|
||||||
"inputs": {
|
|
||||||
"systems": "systems_2"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1681202837,
|
|
||||||
"narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=",
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"rev": "cfacdce06f30d2b68473a46042957675eebb3401",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixpkgs": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1706173671,
|
|
||||||
"narHash": "sha256-lciR7kQUK2FCAYuszyd7zyRRmTaXVeoZsCyK6QFpGdk=",
|
|
||||||
"owner": "NixOS",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"rev": "4fddc9be4eaf195d631333908f2a454b03628ee5",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"id": "nixpkgs",
|
|
||||||
"type": "indirect"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixpkgs_2": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1681358109,
|
|
||||||
"narHash": "sha256-eKyxW4OohHQx9Urxi7TQlFBTDWII+F+x2hklDOQPB50=",
|
|
||||||
"owner": "NixOS",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"rev": "96ba1c52e54e74c3197f4d43026b3f3d92e83ff9",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "NixOS",
|
|
||||||
"ref": "nixpkgs-unstable",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"root": {
|
|
||||||
"inputs": {
|
|
||||||
"flake-utils": "flake-utils",
|
|
||||||
"nixpkgs": "nixpkgs",
|
|
||||||
"rust-overlay": "rust-overlay"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"rust-overlay": {
|
|
||||||
"inputs": {
|
|
||||||
"flake-utils": "flake-utils_2",
|
|
||||||
"nixpkgs": "nixpkgs_2"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1706235145,
|
|
||||||
"narHash": "sha256-3jh5nahTlcsX6QFcMPqxtLn9p9CgT9RSce5GLqjcpi4=",
|
|
||||||
"owner": "oxalica",
|
|
||||||
"repo": "rust-overlay",
|
|
||||||
"rev": "3a57c4e29cb2beb777b2e6ae7309a680585b8b2f",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "oxalica",
|
|
||||||
"repo": "rust-overlay",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"systems": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1681028828,
|
|
||||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"systems_2": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1681028828,
|
|
||||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"root": "root",
|
|
||||||
"version": 7
|
|
||||||
}
|
|
25
flake.nix
25
flake.nix
|
@ -1,25 +0,0 @@
|
||||||
{
|
|
||||||
inputs = {
|
|
||||||
flake-utils.url = "github:numtide/flake-utils";
|
|
||||||
rust-overlay.url = "github:oxalica/rust-overlay";
|
|
||||||
};
|
|
||||||
|
|
||||||
outputs =
|
|
||||||
{
|
|
||||||
self,
|
|
||||||
nixpkgs,
|
|
||||||
flake-utils,
|
|
||||||
rust-overlay,
|
|
||||||
}:
|
|
||||||
flake-utils.lib.eachDefaultSystem (
|
|
||||||
system:
|
|
||||||
let
|
|
||||||
overlays = [ (import rust-overlay) ];
|
|
||||||
pkgs = import nixpkgs { inherit system overlays; };
|
|
||||||
in
|
|
||||||
{
|
|
||||||
devShells.default = import ./shell.nix { inherit pkgs; };
|
|
||||||
packages.default = pkgs.callPackage ./. { };
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
[toolchain]
|
|
||||||
channel = "stable"
|
|
||||||
components = ["cargo", "clippy", "rustfmt", "rust-src"]
|
|
18
shell.nix
18
shell.nix
|
@ -1,18 +0,0 @@
|
||||||
{ pkgs ? import <nixpkgs> { } }:
|
|
||||||
|
|
||||||
with pkgs;
|
|
||||||
|
|
||||||
let
|
|
||||||
rustup-toolchain = rust-bin.fromRustupToolchainFile ./rustup-toolchain.toml;
|
|
||||||
in
|
|
||||||
mkShell rec {
|
|
||||||
packages = [
|
|
||||||
cargo-audit
|
|
||||||
cargo-edit
|
|
||||||
cargo-make
|
|
||||||
cargo-outdated
|
|
||||||
cargo-tarpaulin
|
|
||||||
rustup-toolchain
|
|
||||||
typos
|
|
||||||
];
|
|
||||||
}
|
|
207
source/main.rs
207
source/main.rs
|
@ -1,15 +1,18 @@
|
||||||
//! # Steam RSS
|
//! # Steam RSS
|
||||||
//!
|
//!
|
||||||
//! > **Get RSS feeds for Steam games.**
|
//! > **Get RSS feeds for Steam games.**
|
||||||
|
//!
|
||||||
|
//! *AGPL-3.0-or-later*
|
||||||
|
|
||||||
|
#![forbid(unsafe_code)]
|
||||||
|
#![warn(missing_docs, clippy::missing_docs_in_private_items)]
|
||||||
|
|
||||||
use std::{thread::sleep, time::Duration};
|
use std::{thread::sleep, time::Duration};
|
||||||
|
|
||||||
use {
|
use {
|
||||||
clap::Parser,
|
clap::Parser,
|
||||||
color_eyre::{install, Result},
|
color_eyre::{install, Result},
|
||||||
regex::Regex,
|
indicatif::{ProgressBar, ProgressStyle},
|
||||||
serde::Deserialize,
|
|
||||||
serde_json::Value,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// CLI arguments struct using [`clap`]'s Derive API.
|
/// CLI arguments struct using [`clap`]'s Derive API.
|
||||||
|
@ -20,10 +23,6 @@ pub struct Args {
|
||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
pub appid: Vec<usize>,
|
pub appid: Vec<usize>,
|
||||||
|
|
||||||
/// Output the feeds as OPML.
|
|
||||||
#[clap(long)]
|
|
||||||
pub opml: bool,
|
|
||||||
|
|
||||||
/// The time in milliseconds to sleep between HTTP requests.
|
/// The time in milliseconds to sleep between HTTP requests.
|
||||||
#[clap(short, long, default_value = "250")]
|
#[clap(short, long, default_value = "250")]
|
||||||
pub timeout: u64,
|
pub timeout: u64,
|
||||||
|
@ -31,51 +30,6 @@ pub struct Args {
|
||||||
/// Verify potential feeds by downloading them and checking if they return XML.
|
/// Verify potential feeds by downloading them and checking if they return XML.
|
||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
pub verify: bool,
|
pub verify: bool,
|
||||||
|
|
||||||
/// A game's store URL, can be used multiple times.
|
|
||||||
#[clap(long)]
|
|
||||||
pub url: Vec<String>,
|
|
||||||
|
|
||||||
/// A person's steamcommunity.com ID or full URL, can be used multiple times.
|
|
||||||
#[clap(long)]
|
|
||||||
pub user: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A simple feed struct.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Feed {
|
|
||||||
/// A potential alternate friendly URL, see [`SteamApp::friendly_url`] for an
|
|
||||||
/// explanation.
|
|
||||||
pub friendly_url: Option<String>,
|
|
||||||
|
|
||||||
/// The text to use for the feed in the OPML output.
|
|
||||||
pub text: Option<String>,
|
|
||||||
|
|
||||||
/// The URL of the feed.
|
|
||||||
pub url: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A small representation of a Steam game that is parsed from JSON.
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct SteamApp {
|
|
||||||
/// The AppID of the game.
|
|
||||||
pub appid: usize,
|
|
||||||
|
|
||||||
/// The name of the game.
|
|
||||||
pub name: String,
|
|
||||||
|
|
||||||
/// A friendly URL name of the game, some feeds will use this instead of their
|
|
||||||
/// AppID for their RSS feed.
|
|
||||||
///
|
|
||||||
/// For example, [Portal's feed](https://steamcommunity.com/games/Portal/rss)
|
|
||||||
/// uses `Portal`, instead of
|
|
||||||
/// [its AppID 400](https://steamcommunity.com/games/400/rss).
|
|
||||||
///
|
|
||||||
/// Some games may also have a friendly URL different from their AppID but
|
|
||||||
/// don't use it for their feed. Steam is weird.
|
|
||||||
#[serde(rename = "friendlyURL")]
|
|
||||||
pub friendly_url: Value,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
|
@ -85,155 +39,36 @@ fn main() -> Result<()> {
|
||||||
let timeout = Duration::from_millis(args.timeout);
|
let timeout = Duration::from_millis(args.timeout);
|
||||||
|
|
||||||
let ureq_agent = ureq::AgentBuilder::new()
|
let ureq_agent = ureq::AgentBuilder::new()
|
||||||
.user_agent("Steam Feeds (https://github.com/Bauke/steam-rss)")
|
.user_agent("Steam Feeds (https://git.bauke.xyz/Bauke/steam-rss)")
|
||||||
.build();
|
.build();
|
||||||
let mut potential_feeds = vec![];
|
let mut potential_feeds = vec![];
|
||||||
let mut feeds_to_output = vec![];
|
let mut feeds_to_output = vec![];
|
||||||
|
|
||||||
let store_url_regex =
|
|
||||||
Regex::new(r"(?i)^https?://store.steampowered.com/app/(?P<appid>\d+)")?;
|
|
||||||
let user_json_regex = Regex::new(r"var rgGames = (?P<json>\[.+\]);\s+var")?;
|
|
||||||
let user_id_regex = Regex::new(r"(i?)^\w+$")?;
|
|
||||||
let user_url_regex =
|
|
||||||
Regex::new(r"(?i)https?://steamcommunity.com/id/(?P<userid>\w+)")?;
|
|
||||||
|
|
||||||
for appid in args.appid {
|
for appid in args.appid {
|
||||||
potential_feeds.push(Feed {
|
potential_feeds
|
||||||
friendly_url: None,
|
.push(format!("https://steamcommunity.com/games/{appid}/rss/"));
|
||||||
text: Some(format!("Steam AppID {appid}")),
|
|
||||||
url: appid_to_rss_url(appid),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
for url in args.url {
|
|
||||||
let appid = store_url_regex
|
|
||||||
.captures(&url)
|
|
||||||
.and_then(|captures| captures.name("appid"))
|
|
||||||
.and_then(|appid_match| appid_match.as_str().parse::<usize>().ok());
|
|
||||||
if let Some(appid) = appid {
|
|
||||||
potential_feeds.push(Feed {
|
|
||||||
friendly_url: None,
|
|
||||||
text: Some(format!("Steam AppID {appid}")),
|
|
||||||
url: appid_to_rss_url(appid),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for user in args.user {
|
|
||||||
let user_url = if user_id_regex.is_match(&user) {
|
|
||||||
userid_to_games_url(user)
|
|
||||||
} else if let Some(user) = user_url_regex
|
|
||||||
.captures(&user)
|
|
||||||
.and_then(|captures| captures.name("userid"))
|
|
||||||
{
|
|
||||||
userid_to_games_url(user.as_str())
|
|
||||||
} else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
let body = ureq_agent.get(&user_url).call()?.into_string()?;
|
|
||||||
sleep(timeout);
|
|
||||||
|
|
||||||
let games_json = user_json_regex
|
|
||||||
.captures(&body)
|
|
||||||
.and_then(|captures| captures.name("json"))
|
|
||||||
.map(|json| json.as_str());
|
|
||||||
if let Some(games_json) = games_json {
|
|
||||||
let games = serde_json::from_str::<Vec<SteamApp>>(games_json)?;
|
|
||||||
for game in games {
|
|
||||||
let friendly_url = if game.friendly_url.is_string() {
|
|
||||||
Some(appid_to_rss_url(game.friendly_url.as_str().unwrap()))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
potential_feeds.push(Feed {
|
|
||||||
friendly_url,
|
|
||||||
text: Some(game.name),
|
|
||||||
url: appid_to_rss_url(game.appid),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
eprintln!("Couldn't scan games from: {user_url}");
|
|
||||||
eprintln!(
|
|
||||||
"Make sure \"Game Details\" in Privacy Settings is set to Public."
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if args.verify {
|
if args.verify {
|
||||||
let verify_feed = |url: &str| -> Result<_> {
|
let progress = ProgressBar::new(potential_feeds.len().try_into()?)
|
||||||
let response = ureq_agent.get(url).call()?;
|
.with_style(ProgressStyle::with_template("Verifying {pos}/{len} {bar}")?);
|
||||||
|
|
||||||
|
for potential_feed in potential_feeds {
|
||||||
|
let response = ureq_agent.get(&potential_feed).call()?;
|
||||||
|
if response.content_type() == "text/xml" {
|
||||||
|
feeds_to_output.push(potential_feed);
|
||||||
|
}
|
||||||
|
|
||||||
sleep(timeout);
|
sleep(timeout);
|
||||||
Ok((
|
progress.inc(1);
|
||||||
response.content_type() == "text/xml",
|
|
||||||
response.into_string()?,
|
|
||||||
))
|
|
||||||
};
|
|
||||||
|
|
||||||
for mut potential_feed in potential_feeds {
|
|
||||||
let (mut is_valid_feed, mut body) = verify_feed(&potential_feed.url)?;
|
|
||||||
|
|
||||||
// If the potential URL doesn't return `text/xml`, try the friendly URL
|
|
||||||
// if one exists.
|
|
||||||
if !is_valid_feed && potential_feed.friendly_url.is_some() {
|
|
||||||
let friendly_url = potential_feed.friendly_url.as_deref().unwrap();
|
|
||||||
(is_valid_feed, body) = verify_feed(friendly_url)?;
|
|
||||||
if is_valid_feed {
|
|
||||||
potential_feed.url = friendly_url.to_string();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let verified_feed = if is_valid_feed {
|
|
||||||
let title_start = body.find("<title>").unwrap() + 7;
|
|
||||||
let title_end = body.find("</title>").unwrap();
|
|
||||||
Feed {
|
|
||||||
text: Some(body[title_start..title_end].to_string()),
|
|
||||||
..potential_feed
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
continue;
|
feeds_to_output = potential_feeds;
|
||||||
};
|
|
||||||
|
|
||||||
feeds_to_output.push(verified_feed);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
feeds_to_output.append(&mut potential_feeds);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut opml_document = opml::OPML {
|
|
||||||
head: None,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
if feeds_to_output.is_empty() {
|
|
||||||
eprintln!("No feeds found.");
|
|
||||||
return Ok(());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for feed in feeds_to_output {
|
for feed in feeds_to_output {
|
||||||
if args.opml {
|
println!("{feed}");
|
||||||
opml_document
|
|
||||||
.add_feed(&feed.text.unwrap_or_else(|| feed.url.clone()), &feed.url);
|
|
||||||
} else {
|
|
||||||
println!("{}", feed.url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if args.opml {
|
|
||||||
println!("{}", opml_document.to_string()?);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a Steam RSS URL from a given AppID.
|
|
||||||
fn appid_to_rss_url<D: std::fmt::Display>(appid: D) -> String {
|
|
||||||
format!("https://steamcommunity.com/games/{appid}/rss/")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a user's Steam Games URL from a given User ID.
|
|
||||||
fn userid_to_games_url<D: std::fmt::Display>(userid: D) -> String {
|
|
||||||
format!("https://steamcommunity.com/id/{userid}/games/?tab=all")
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue