Initial working disaster 🔥
This commit is contained in:
commit
02512d0377
|
@ -0,0 +1 @@
|
||||||
|
/target
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,22 @@
|
||||||
|
[package]
|
||||||
|
name = "iso7010_a_day"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
reqwest = { version = "0.11", features = ["blocking", "json", "multipart"]}
|
||||||
|
serde_json = "*"
|
||||||
|
serde = "*"
|
||||||
|
scraper = "*"
|
||||||
|
rand = "*"
|
||||||
|
resvg = "*"
|
||||||
|
tiny-skia = "*"
|
||||||
|
usvg = "*"
|
||||||
|
oauth1 = "*"
|
||||||
|
clap = "*"
|
||||||
|
webbrowser = "*"
|
||||||
|
itertools = "*"
|
||||||
|
url = "*"
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
toml = "*"
|
|
@ -0,0 +1,16 @@
|
||||||
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let s = std::fs::read_to_string("./cargo.lock")?;
|
||||||
|
let lock = s.parse::<toml::Value>().unwrap();
|
||||||
|
let mut reqwest_ver: Option<String> = None;
|
||||||
|
for p in lock.as_table().unwrap()["package"].as_array().unwrap() {
|
||||||
|
if p["name"].as_str().unwrap() == "reqwest" {
|
||||||
|
reqwest_ver = Some(p["version"].as_str().unwrap().into());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println!(
|
||||||
|
"cargo:rustc-env=REQWEST_VER={}",
|
||||||
|
reqwest_ver.ok_or_else(|| "No reqwest ver found")?
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
{ lib
|
||||||
|
, naersk
|
||||||
|
, stdenv
|
||||||
|
, clangStdenv
|
||||||
|
, hostPlatform
|
||||||
|
, targetPlatform
|
||||||
|
, pkg-config
|
||||||
|
, libiconv
|
||||||
|
, rustfmt
|
||||||
|
, cargo
|
||||||
|
, rustc
|
||||||
|
# , llvmPackages # Optional
|
||||||
|
# , protobuf # Optional
|
||||||
|
}:
|
||||||
|
|
||||||
|
let
|
||||||
|
cargoToml = (builtins.fromTOML (builtins.readFile ./Cargo.toml));
|
||||||
|
in
|
||||||
|
|
||||||
|
naersk.lib."${targetPlatform.system}".buildPackage rec {
|
||||||
|
src = ./.;
|
||||||
|
|
||||||
|
buildInputs = [
|
||||||
|
rustfmt
|
||||||
|
pkg-config
|
||||||
|
cargo
|
||||||
|
rustc
|
||||||
|
libiconv
|
||||||
|
];
|
||||||
|
checkInputs = [ cargo rustc ];
|
||||||
|
|
||||||
|
doCheck = true;
|
||||||
|
CARGO_BUILD_INCREMENTAL = "false";
|
||||||
|
RUST_BACKTRACE = "full";
|
||||||
|
copyLibs = true;
|
||||||
|
|
||||||
|
# Optional things you might need:
|
||||||
|
#
|
||||||
|
# If you depend on `libclang`:
|
||||||
|
# LIBCLANG_PATH = "${llvmPackages.libclang}/lib";
|
||||||
|
#
|
||||||
|
# If you depend on protobuf:
|
||||||
|
# PROTOC = "${protobuf}/bin/protoc";
|
||||||
|
# PROTOC_INCLUDE = "${protobuf}/include";
|
||||||
|
|
||||||
|
name = cargoToml.package.name;
|
||||||
|
version = cargoToml.package.version;
|
||||||
|
|
||||||
|
meta = with lib; {
|
||||||
|
description = cargoToml.package.description;
|
||||||
|
homepage = cargoToml.package.homepage;
|
||||||
|
license = with licenses; [ mit ];
|
||||||
|
maintainers = with maintainers; [ ];
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"naersk": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1632266297,
|
||||||
|
"narHash": "sha256-J1yeJk6Gud9ef2pEf6aKQemrfg1pVngYDSh+SAY94xk=",
|
||||||
|
"owner": "nmattia",
|
||||||
|
"repo": "naersk",
|
||||||
|
"rev": "ee7edec50b49ab6d69b06d62f1de554efccb1ccd",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nmattia",
|
||||||
|
"repo": "naersk",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1634919341,
|
||||||
|
"narHash": "sha256-iYTckx+vxrGskDsI7US/3N1CDGYjnFl2Wh6gkLBmAKI=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "1cab3e231b41f38f2d2cbf5617eb7b88e433428a",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixpkgs-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"naersk": "naersk",
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
{
|
||||||
|
description = "My cute Rust crate!";
|
||||||
|
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||||
|
naersk.url = "github:nmattia/naersk";
|
||||||
|
naersk.inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = { self, nixpkgs, naersk }:
|
||||||
|
let
|
||||||
|
cargoToml = (builtins.fromTOML (builtins.readFile ./Cargo.toml));
|
||||||
|
supportedSystems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" ];
|
||||||
|
forAllSystems = f: nixpkgs.lib.genAttrs supportedSystems (system: f system);
|
||||||
|
in
|
||||||
|
{
|
||||||
|
overlay = final: prev: {
|
||||||
|
"${cargoToml.package.name}" = final.callPackage ./. { inherit naersk; };
|
||||||
|
};
|
||||||
|
|
||||||
|
packages = forAllSystems (system:
|
||||||
|
let
|
||||||
|
pkgs = import nixpkgs {
|
||||||
|
inherit system;
|
||||||
|
overlays = [
|
||||||
|
self.overlay
|
||||||
|
];
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
"${cargoToml.package.name}" = pkgs."${cargoToml.package.name}";
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
defaultPackage = forAllSystems (system: (import nixpkgs {
|
||||||
|
inherit system;
|
||||||
|
overlays = [ self.overlay ];
|
||||||
|
})."${cargoToml.package.name}");
|
||||||
|
|
||||||
|
checks = forAllSystems (system:
|
||||||
|
let
|
||||||
|
pkgs = import nixpkgs {
|
||||||
|
inherit system;
|
||||||
|
overlays = [
|
||||||
|
self.overlay
|
||||||
|
];
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
format = pkgs.runCommand "check-format"
|
||||||
|
{
|
||||||
|
buildInputs = with pkgs; [ rustfmt cargo ];
|
||||||
|
} ''
|
||||||
|
${pkgs.rustfmt}/bin/cargo-fmt fmt --manifest-path ${./.}/Cargo.toml -- --check
|
||||||
|
${pkgs.nixpkgs-fmt}/bin/nixpkgs-fmt --check ${./.}
|
||||||
|
touch $out # it worked!
|
||||||
|
'';
|
||||||
|
"${cargoToml.package.name}" = pkgs."${cargoToml.package.name}";
|
||||||
|
});
|
||||||
|
devShell = forAllSystems (system:
|
||||||
|
let
|
||||||
|
pkgs = import nixpkgs {
|
||||||
|
inherit system;
|
||||||
|
overlays = [ self.overlay ];
|
||||||
|
};
|
||||||
|
in
|
||||||
|
pkgs.mkShell {
|
||||||
|
inputsFrom = with pkgs; [
|
||||||
|
pkgs."${cargoToml.package.name}"
|
||||||
|
];
|
||||||
|
buildInputs = with pkgs; [
|
||||||
|
rustfmt
|
||||||
|
nixpkgs-fmt
|
||||||
|
];
|
||||||
|
LIBCLANG_PATH = "${pkgs.llvmPackages.libclang.lib}/lib";
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,337 @@
|
||||||
|
use itertools::Itertools;
|
||||||
|
use rand::seq::SliceRandom;
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::convert::TryInto;
|
||||||
|
use std::io::prelude::*;
|
||||||
|
use std::{collections::HashMap, io::Write};
|
||||||
|
|
||||||
|
const APP_TOKEN_ENV_VAR: &str = "TWITTER_APP_TOKEN";
|
||||||
|
const APP_SECRET_ENV_VAR: &str = "TWITTER_APP_SECRET";
|
||||||
|
const USER_TOKEN_ENV_VAR: &str = "TWITTER_USER_TOKEN";
|
||||||
|
const USER_SECRET_ENV_VAR: &str = "TWITTER_USER_SECRET";
|
||||||
|
const IMG_HEIGHT: u32 = 1000;
|
||||||
|
|
||||||
|
static APP_USER_AGENT: &str = concat!(
|
||||||
|
"bot_",
|
||||||
|
env!("CARGO_PKG_NAME"),
|
||||||
|
"/",
|
||||||
|
env!("CARGO_PKG_VERSION"),
|
||||||
|
" ",
|
||||||
|
"reqwest",
|
||||||
|
"/",
|
||||||
|
env!("REQWEST_VER")
|
||||||
|
);
|
||||||
|
|
||||||
|
static CB_URL: &str = "http://localhost:6969/cb";
|
||||||
|
|
||||||
|
fn render_svg(data: &[u8]) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
|
||||||
|
let opt = usvg::Options::default();
|
||||||
|
let rtree = usvg::Tree::from_data(&data, &opt.to_ref()).expect("couldn't parse");
|
||||||
|
let mut pixmap =
|
||||||
|
tiny_skia::Pixmap::new(IMG_HEIGHT, IMG_HEIGHT).ok_or("Error creating pixmap")?;
|
||||||
|
resvg::render(
|
||||||
|
&rtree,
|
||||||
|
usvg::FitTo::Size(IMG_HEIGHT, IMG_HEIGHT),
|
||||||
|
pixmap.as_mut(),
|
||||||
|
)
|
||||||
|
.ok_or_else(|| "Error rendering svg")?;
|
||||||
|
let mut bigger_pixmap = tiny_skia::Pixmap::new(IMG_HEIGHT / 9 * 16, IMG_HEIGHT)
|
||||||
|
.ok_or("Error creating bigger pixmap")?;
|
||||||
|
bigger_pixmap
|
||||||
|
.draw_pixmap(
|
||||||
|
(bigger_pixmap.width() / 2 - IMG_HEIGHT / 2)
|
||||||
|
.try_into()
|
||||||
|
.unwrap(),
|
||||||
|
0,
|
||||||
|
pixmap.as_ref(),
|
||||||
|
&tiny_skia::PixmapPaint::default(),
|
||||||
|
tiny_skia::Transform::identity(),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.ok_or("Error drawing onto bigger pixmap")?;
|
||||||
|
let png_data = bigger_pixmap.encode_png()?;
|
||||||
|
Ok(png_data)
|
||||||
|
}
|
||||||
|
|
||||||
|
enum PostData<'a> {
|
||||||
|
Empty,
|
||||||
|
Multipart(reqwest::blocking::multipart::Form),
|
||||||
|
Data(&'a [(&'a str, &'a str)]),
|
||||||
|
}
|
||||||
|
|
||||||
|
enum APIAction<'a> {
|
||||||
|
Get,
|
||||||
|
Post(PostData<'a>),
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TwitterEndpoint<'a>(&'a str);
|
||||||
|
|
||||||
|
impl TryInto<reqwest::Url> for TwitterEndpoint<'_> {
|
||||||
|
type Error = url::ParseError;
|
||||||
|
fn try_into(self) -> Result<reqwest::Url, Self::Error> {
|
||||||
|
reqwest::Url::parse(&format!("https://api.twitter.com/{}", self.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make an authed twitter API request
|
||||||
|
fn twitter_api<'a>(
|
||||||
|
url: reqwest::Url,
|
||||||
|
user_token: Option<&oauth1::Token>,
|
||||||
|
action: APIAction,
|
||||||
|
extra_oauth_params: &[(&str, &str)],
|
||||||
|
) -> StdError<reqwest::blocking::Response> {
|
||||||
|
let consumer_token = oauth1::Token::new(
|
||||||
|
std::env::var(APP_TOKEN_ENV_VAR)?,
|
||||||
|
std::env::var(APP_SECRET_ENV_VAR)?,
|
||||||
|
);
|
||||||
|
let mut headers = reqwest::header::HeaderMap::new();
|
||||||
|
let mut oauth_params: HashMap<&str, Cow<str>> = extra_oauth_params
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.map(|(x, y)| (x, y.into()))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// If the request is a key/value form post, we need to include those parameters when
|
||||||
|
// generating the signature.
|
||||||
|
match action {
|
||||||
|
APIAction::Post(PostData::Data(d)) => {
|
||||||
|
oauth_params.extend(d.iter().cloned().map(|(x, y)| (x, y.into())))
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
headers.insert(
|
||||||
|
reqwest::header::AUTHORIZATION,
|
||||||
|
reqwest::header::HeaderValue::from_str(&oauth1::authorize(
|
||||||
|
if matches!(action, APIAction::Post(_)) {
|
||||||
|
"POST"
|
||||||
|
} else {
|
||||||
|
"GET"
|
||||||
|
},
|
||||||
|
url.as_str(),
|
||||||
|
&consumer_token,
|
||||||
|
user_token,
|
||||||
|
Some(oauth_params),
|
||||||
|
))?,
|
||||||
|
);
|
||||||
|
|
||||||
|
let client = reqwest::blocking::Client::builder()
|
||||||
|
.user_agent(APP_USER_AGENT)
|
||||||
|
.default_headers(headers)
|
||||||
|
.build()?;
|
||||||
|
let req = match action {
|
||||||
|
APIAction::Get => client.get(url),
|
||||||
|
APIAction::Post(PostData::Empty) => client.post(url),
|
||||||
|
APIAction::Post(PostData::Data(data)) => client.post(url).form(data),
|
||||||
|
APIAction::Post(PostData::Multipart(form)) => client.post(url).multipart(form),
|
||||||
|
};
|
||||||
|
|
||||||
|
let res = req.send()?;
|
||||||
|
if !res.status().is_success() {
|
||||||
|
return Err(format!(
|
||||||
|
"Got non-200 response: status {}, {}",
|
||||||
|
res.status(),
|
||||||
|
res.text()?
|
||||||
|
)
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
type StdError<T> = Result<T, Box<dyn std::error::Error>>;
|
||||||
|
|
||||||
|
fn main() -> StdError<()> {
|
||||||
|
let matches = clap::App::new(env!("CARGO_PKG_NAME"))
|
||||||
|
.version(env!("CARGO_PKG_VERSION"))
|
||||||
|
.subcommand(clap::SubCommand::with_name("authorize").about("Authorize the twitter application to access a user's account by popping open a web browser and returning the credentials once authorized.")).get_matches();
|
||||||
|
match matches.subcommand() {
|
||||||
|
("authorize", _) => do_authorize(),
|
||||||
|
_ => run_bot(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn do_authorize() -> StdError<()> {
|
||||||
|
println!("Authorizing you lol!");
|
||||||
|
|
||||||
|
// Oauth1 leg 1
|
||||||
|
let res = twitter_api(
|
||||||
|
TwitterEndpoint("oauth/request_token").try_into()?,
|
||||||
|
None,
|
||||||
|
APIAction::Post(PostData::Empty),
|
||||||
|
&[("oauth_callback", CB_URL)],
|
||||||
|
)?
|
||||||
|
.text()?;
|
||||||
|
|
||||||
|
let returned_params: HashMap<&str, &str> = res
|
||||||
|
.split("&")
|
||||||
|
.map(|s| s.split("=").collect_tuple())
|
||||||
|
.collect::<Option<_>>()
|
||||||
|
.ok_or("Unexpected oauth step 1 response")?;
|
||||||
|
|
||||||
|
// Oauth1 leg 2
|
||||||
|
let user_url = reqwest::Url::parse_with_params(
|
||||||
|
"https://api.twitter.com/oauth/authenticate",
|
||||||
|
[("oauth_token", returned_params["oauth_token"])],
|
||||||
|
)?;
|
||||||
|
println!("Plz do the thing in the browser");
|
||||||
|
webbrowser::open(user_url.as_str())?;
|
||||||
|
|
||||||
|
let listener = std::net::TcpListener::bind("127.0.0.1:6969")?;
|
||||||
|
let mut stream = listener.incoming().next().ok_or("Error getting stream")??;
|
||||||
|
let mut buf = [0u8; 4096];
|
||||||
|
stream.read(&mut buf[..])?;
|
||||||
|
|
||||||
|
let target = std::str::from_utf8(
|
||||||
|
buf.split(|c| *c == b' ')
|
||||||
|
.skip(1)
|
||||||
|
.next()
|
||||||
|
.ok_or("No target found")?,
|
||||||
|
)?;
|
||||||
|
let oauth_verifier = reqwest::Url::parse("https://example.net/")?
|
||||||
|
.join(target.into())?
|
||||||
|
.query_pairs()
|
||||||
|
.find_map(|(k, v)| {
|
||||||
|
if k == "oauth_verifier" {
|
||||||
|
Some(v.into_owned())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.ok_or("no oauth_verifier in response")?;
|
||||||
|
stream.write(b"HTTP/1.1 200 OK\r\n\r\nThanks lmao\r\n")?;
|
||||||
|
stream.shutdown(std::net::Shutdown::Read)?;
|
||||||
|
|
||||||
|
// Oauth1 leg 3
|
||||||
|
let res = twitter_api(
|
||||||
|
TwitterEndpoint("oauth/access_token").try_into()?,
|
||||||
|
None,
|
||||||
|
APIAction::Post(PostData::Data(&[("oauth_verifier", &oauth_verifier)])),
|
||||||
|
&[("oauth_token", returned_params["oauth_token"])],
|
||||||
|
)?
|
||||||
|
.text()?;
|
||||||
|
let returned_params: HashMap<&str, &str> = res
|
||||||
|
.split("&")
|
||||||
|
.map(|s| s.split("=").collect_tuple())
|
||||||
|
.collect::<Option<_>>()
|
||||||
|
.ok_or("Unexpected oauth step 3 response")?;
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"Authorized for {}.\nRun with {}={} {}={}",
|
||||||
|
returned_params["screen_name"],
|
||||||
|
USER_TOKEN_ENV_VAR,
|
||||||
|
returned_params["oauth_token"],
|
||||||
|
USER_SECRET_ENV_VAR,
|
||||||
|
returned_params["oauth_token_secret"]
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn upload_image(user_token: &oauth1::Token, img: Cow<'static, [u8]>) -> StdError<u64> {
|
||||||
|
let form = reqwest::blocking::multipart::Form::new()
|
||||||
|
.part("media", reqwest::blocking::multipart::Part::bytes(img));
|
||||||
|
let res: serde_json::Value = twitter_api(
|
||||||
|
"https://upload.twitter.com/1.1/media/upload.json".try_into()?,
|
||||||
|
Some(&user_token),
|
||||||
|
APIAction::Post(PostData::Multipart(form)),
|
||||||
|
&[],
|
||||||
|
)?
|
||||||
|
.json()?;
|
||||||
|
Ok(res["media_id"].as_u64().ok_or("media_id not u64!")?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_bot() -> StdError<()> {
|
||||||
|
let user_token = oauth1::Token::new(
|
||||||
|
std::env::var(USER_TOKEN_ENV_VAR)?,
|
||||||
|
std::env::var(USER_SECRET_ENV_VAR)?,
|
||||||
|
);
|
||||||
|
let args: Vec<String> = std::env::args().collect();
|
||||||
|
if args.len() < 2 {
|
||||||
|
println!("usage: ./thing out.png");
|
||||||
|
}
|
||||||
|
// Parse CSS selectors to scrape elements
|
||||||
|
let gallerybox_sel = scraper::Selector::parse(".mw-body-content li.gallerybox")
|
||||||
|
.map_err(|e| format!("{:?}", e))?;
|
||||||
|
let link_sel = scraper::Selector::parse("a.image").map_err(|e| format!("{:?}", e))?;
|
||||||
|
let title_sel = scraper::Selector::parse(".gallerytext p").map_err(|e| format!("{:?}", e))?;
|
||||||
|
let original_sel = scraper::Selector::parse(".fullMedia a").map_err(|e| format!("{:?}", e))?;
|
||||||
|
|
||||||
|
// Fetch stuff!
|
||||||
|
let client = reqwest::blocking::Client::builder()
|
||||||
|
.user_agent(APP_USER_AGENT)
|
||||||
|
.build()?;
|
||||||
|
println!("Fetching main page");
|
||||||
|
let txt = client
|
||||||
|
.get("https://en.wikipedia.org/wiki/ISO_7010")
|
||||||
|
.send()?
|
||||||
|
.text()?;
|
||||||
|
let page = scraper::Html::parse_document(txt.as_str());
|
||||||
|
let things = page
|
||||||
|
.select(&gallerybox_sel)
|
||||||
|
.map(|a| {
|
||||||
|
let link = a
|
||||||
|
.select(&link_sel)
|
||||||
|
.next()
|
||||||
|
.unwrap()
|
||||||
|
.value()
|
||||||
|
.attr("href")
|
||||||
|
.unwrap();
|
||||||
|
let title = a
|
||||||
|
.select(&title_sel)
|
||||||
|
.next()
|
||||||
|
.unwrap()
|
||||||
|
.text()
|
||||||
|
.collect::<String>()
|
||||||
|
.trim()
|
||||||
|
.to_owned();
|
||||||
|
(title, link)
|
||||||
|
})
|
||||||
|
.collect::<Vec<(String, &str)>>();
|
||||||
|
// Pick a random entry and fetch the original file
|
||||||
|
|
||||||
|
let (title, link) = things
|
||||||
|
.choose(&mut rand::thread_rng())
|
||||||
|
.ok_or_else(|| "got no images m8")?;
|
||||||
|
println!("Fetching image page");
|
||||||
|
let media_page = client
|
||||||
|
.get(format!("https://en.wikipedia.org{}", link))
|
||||||
|
.send()?
|
||||||
|
.text()?;
|
||||||
|
let page = scraper::Html::parse_document(media_page.as_str());
|
||||||
|
let link = page
|
||||||
|
.select(&original_sel)
|
||||||
|
.next()
|
||||||
|
.unwrap()
|
||||||
|
.value()
|
||||||
|
.attr("href")
|
||||||
|
.unwrap();
|
||||||
|
let svg = client.get(format!("https:{}", link)).send()?.bytes()?;
|
||||||
|
let png_data = render_svg(&svg)?;
|
||||||
|
|
||||||
|
let user: serde_json::Value = twitter_api(
|
||||||
|
TwitterEndpoint("1.1/account/verify_credentials.json").try_into()?,
|
||||||
|
Some(&user_token),
|
||||||
|
APIAction::Get,
|
||||||
|
&[],
|
||||||
|
)?
|
||||||
|
.json()?;
|
||||||
|
println!(
|
||||||
|
"Tweeting for user @{}, (id: {})",
|
||||||
|
user["screen_name"], user["id"]
|
||||||
|
);
|
||||||
|
|
||||||
|
println!("Uploading image...");
|
||||||
|
let img_id = upload_image(&user_token, Cow::from(png_data))?;
|
||||||
|
let tweet = title;
|
||||||
|
println!("Sending tweet...");
|
||||||
|
twitter_api(
|
||||||
|
TwitterEndpoint("1.1/statuses/update.json").try_into()?,
|
||||||
|
Some(&user_token),
|
||||||
|
APIAction::Post(PostData::Data(&[
|
||||||
|
("media_ids", &img_id.to_string()),
|
||||||
|
("status", tweet),
|
||||||
|
])),
|
||||||
|
&[],
|
||||||
|
)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
Loading…
Reference in New Issue