Skip to content

Instantly share code, notes, and snippets.

@qryxip
Last active August 24, 2020 17:21
Show Gist options
  • Save qryxip/e53b5d3d817ed7af81fda2c96d36fe36 to your computer and use it in GitHub Desktop.
Save qryxip/e53b5d3d817ed7af81fda2c96d36fe36 to your computer and use it in GitHub Desktop.
//! ```cargo
//! [package]
//! name = "invesigate-dropbox"
//! version = "0.0.0"
//! authors = ["Ryo Yamashita <qryxip@gmail.com>"]
//! edition = "2018"
//! publish = false
//!
//! [dependencies]
//! anyhow = "1.0.32"
//! atty = "0.2.14"
//! maplit = "1.0.2"
//! reqwest = { version = "0.10.7", features = ["blocking", "json"] }
//! serde = { version = "1.0.115", features = ["derive"] }
//! serde_json = "1.0.57"
//! structopt = "0.3.16"
//! termcolor = "1.1.0"
//! ```
use anyhow::{anyhow, bail, Context as _};
use maplit::btreeset;
use reqwest::Method;
use serde::Deserialize;
use serde_json::json;
use std::{
collections::BTreeSet,
convert, env, fmt,
io::{self, Write as _},
time::Duration,
};
use structopt::StructOpt;
use termcolor::{BufferedStandardStream, Color, ColorChoice, ColorSpec, WriteColor as _};
#[derive(StructOpt)]
struct Opt {}
fn main() -> anyhow::Result<()> {
Opt::from_args();
let mut shell = Shell::new();
let client = client()?;
let access_token = access_token()?;
let dir_names = list_folder(&mut shell, &client, &access_token, "")?;
let mut different = btreeset!();
let mut arc_abcd = btreeset!();
let mut arc_cdef = btreeset!();
let mut arc_abcdef = btreeset!();
for dir_name in dir_names {
let contest_ids = match &*dir_name {
"ARC060" => vec!["arc060".to_owned(), "abc044".to_owned()],
"ARC061" => vec!["arc061".to_owned(), "abc045".to_owned()],
"ARC062" => vec!["arc062".to_owned(), "abc046".to_owned()],
"ARC063" => vec!["arc063".to_owned(), "abc047".to_owned()],
"ARC064" => vec!["arc064".to_owned(), "abc048".to_owned()],
"ARC065" => vec!["arc065".to_owned(), "abc049".to_owned()],
"ARC066" => vec!["arc066".to_owned(), "abc050".to_owned()],
"ARC067" => vec!["arc067".to_owned(), "abc052".to_owned()],
"ARC068" => vec!["arc068".to_owned(), "abc053".to_owned()],
"ARC069" => vec!["arc069".to_owned(), "abc055".to_owned()],
"ARC070" => vec!["arc070".to_owned(), "abc056".to_owned()],
"ARC071" => vec!["arc071".to_owned(), "abc058".to_owned()],
"ARC072" => vec!["arc072".to_owned(), "abc059".to_owned()],
"ARC073" => vec!["arc073".to_owned(), "abc060".to_owned()],
"ARC074" => vec!["arc074".to_owned(), "abc062".to_owned()],
"ARC075" => vec!["arc075".to_owned(), "abc063".to_owned()],
"ARC076" => vec!["arc076".to_owned(), "abc065".to_owned()],
"ARC077" => vec!["arc077".to_owned(), "abc066".to_owned()],
"ARC078" => vec!["arc078".to_owned(), "abc067".to_owned()],
"ARC079" => vec!["arc079".to_owned(), "abc068".to_owned()],
"ARC080" => vec!["arc080".to_owned(), "abc069".to_owned()],
"ARC081" => vec!["arc081".to_owned(), "abc071".to_owned()],
"ARC082" => vec!["arc082".to_owned(), "abc072".to_owned()],
"ARC083" => vec!["arc083".to_owned(), "abc074".to_owned()],
"ARC084" => vec!["arc084".to_owned(), "abc077".to_owned()],
"ARC085" => vec!["arc085".to_owned(), "abc078".to_owned()],
"ARC086" => vec!["arc086".to_owned(), "abc081".to_owned()],
"ARC087" => vec!["arc087".to_owned(), "abc082".to_owned()],
"ARC088" => vec!["arc088".to_owned(), "abc083".to_owned()],
"ARC089" => vec!["arc089".to_owned(), "abc086".to_owned()],
"ARC090" => vec!["arc090".to_owned(), "abc087".to_owned()],
"ARC091" => vec!["arc091".to_owned(), "abc090".to_owned()],
"ARC092" => vec!["arc092".to_owned(), "abc091".to_owned()],
"ARC093" => vec!["arc093".to_owned(), "abc092".to_owned()],
"ARC094" => vec!["arc094".to_owned(), "abc093".to_owned()],
"ARC095" => vec!["arc095".to_owned(), "abc094".to_owned()],
"ARC096" => vec!["arc096".to_owned(), "abc095".to_owned()],
"ARC097" => vec!["arc097".to_owned(), "abc097".to_owned()],
"ARC098" => vec!["arc098".to_owned(), "abc098".to_owned()],
"ARC099" => vec!["arc099".to_owned(), "abc101".to_owned()],
"ARC100" => vec!["arc100".to_owned(), "abc102".to_owned()],
"ARC101" => vec!["arc101".to_owned(), "abc107".to_owned()],
"ARC102" => vec!["arc102".to_owned(), "abc108".to_owned()],
"ARC103" => vec!["arc103".to_owned(), "abc111".to_owned()],
"ARC058_ABC042" => vec!["arc058".to_owned(), "abc042".to_owned()],
"ARC059_ABC043" => vec!["arc059".to_owned(), "abc043".to_owned()],
"2019ddccqual" => vec!["ddcc2019-qual".to_owned()],
"2019exa" => vec!["exawizards2019".to_owned()],
"2019nikkei_qual" => vec!["nikkei2019-qual".to_owned()],
"2019yahoo_qual" => vec!["yahoo-procon2019-qual".to_owned()],
"2020_dwango_qual" => vec!["dwacon6th-prelims".to_owned()],
"2020_hitachi" => vec!["hitachi2020".to_owned()],
"2020_panasonic" => vec!["panasonic2020".to_owned()],
"CodeFestival2016EliminationTournament" => vec![
"cf16-tournament-round1-open".to_owned(),
"cf16-tournament-round2-open".to_owned(),
"cf16-tournament-round3-open".to_owned(),
],
"CodeFestival2016Exhibition" => vec!["cf16-exhibition-open".to_owned()],
"CodeFestival2016Final" => vec!["cf16-final-open".to_owned()],
"CodeFestival2016GrandFinal" => vec!["cf16-exhibition-final-open".to_owned()],
"CodeFestival2016QualA" => vec!["code-festival-2016-quala".to_owned()],
"CodeFestival2016QualB" => vec!["code-festival-2016-qualb".to_owned()],
"CodeFestival2016QualC" => vec!["code-festival-2016-qualc".to_owned()],
"CodeFestival2016Relay" => vec!["cf16-relay-open".to_owned()],
"CodeFestival2017Exhibition" => vec!["cf17-exhibition-open".to_owned()],
"CodeFestival2017Final" => vec!["cf17-final-open".to_owned()],
"CodeFestival2017QualA" => vec!["code-festival-2017-quala".to_owned()],
"CodeFestival2017QualB" => vec!["code-festival-2017-qualb".to_owned()],
"CodeFestival2017QualC" => vec!["code-festival-2017-qualc".to_owned()],
"CodeFestival2017Relay" => vec!["cf17-relay-open".to_owned()],
"CodeFestival2017Tournament" => vec![
"cf17-tournament-round1-open".to_owned(),
"cf17-tournament-round2-open".to_owned(),
"cf17-tournament-round3-open".to_owned(),
],
"MUJIN2017" => vec!["mujin-pc-2017".to_owned()],
"diverta2_2019" => vec!["diverta2019-2".to_owned()],
"diverta_2019" => vec!["diverta2019".to_owned()],
"jsc2019" => vec!["jsc2019-qual".to_owned()],
"msolutions2019" => vec!["m-solutions2019".to_owned()],
"nikkeiqual_2019" => vec!["nikkei2019-qual".to_owned()],
"tenka1_2017" => vec!["tenka1-2017".to_owned()],
"tenka1_2018" => vec!["tenka1-2018".to_owned()],
"tenka1_2019" => vec!["tenka1-2019".to_owned()],
dir_name => vec![dir_name.to_ascii_lowercase()],
};
for contest_id in contest_ids {
if !exists(&mut shell, &client, &contest_id)? {
different.insert((dir_name.clone(), contest_id.clone()));
}
}
if dir_name.starts_with("ARC") && dir_name.len() == 6 {
let problems = list_folder(
&mut shell,
&client,
&access_token,
&format!("/{}", dir_name),
)?;
if problems.iter().eq(&["A", "B", "C", "D"]) {
arc_abcd.insert(dir_name);
} else if problems.iter().eq(&["C", "D", "E", "F"]) {
arc_cdef.insert(dir_name);
} else if problems.iter().eq(&["A", "B", "C", "D", "E", "F"]) {
arc_abcdef.insert(dir_name);
} else {
bail!("?????: {:?}", problems);
}
}
}
if different.is_empty() {
shell.info("found all")?;
}
for (dir_name, contest_id) in different {
shell.info(format!("not found: {:?} -> {:?}", dir_name, contest_id))?;
}
if !arc_abcd.is_empty() {
for name in arc_abcd {
shell.info(format!("ARC - A, B, C, D: {:?}", name))?;
}
}
if !arc_cdef.is_empty() {
for name in arc_cdef {
shell.info(format!("ARC - C, D, E, F: {:?}", name))?;
}
}
if !arc_abcdef.is_empty() {
for name in arc_abcdef {
shell.info(format!("ARC - A, B, C, D, E, F: {:?}", name))?;
}
}
Ok(())
}
fn client() -> reqwest::Result<reqwest::blocking::Client> {
const USER_AGENT: &str = "qryxip@gmail.com";
const TIMEOUT: Option<Duration> = Some(Duration::from_secs(30));
reqwest::blocking::ClientBuilder::new()
.user_agent(USER_AGENT)
.timeout(TIMEOUT)
.build()
}
fn access_token() -> anyhow::Result<String> {
env::var("DROPBOX_ACCESS_TOKEN").with_context(|| "could not read `$DROPBOX_ACCESS_TOKEN`")
}
fn list_folder(
shell: &mut Shell,
client: &reqwest::blocking::Client,
access_token: &str,
path: &str,
) -> anyhow::Result<BTreeSet<String>> {
static SHARED_LINK_URL: &str =
"https://www.dropbox.com/sh/arnpe0ef5wds8cv/AAAk_SECQ2Nc6SVGii3rHX6Fa?dl=0";
#[derive(Deserialize)]
#[serde(untagged)]
enum ListFolderOutcome {
Ok(ListFolder),
Err(serde_json::Value),
}
#[derive(Deserialize)]
struct ListFolder {
entries: Vec<ListFolderEntry>,
}
#[derive(Deserialize)]
struct ListFolderEntry {
name: String,
}
let res = request_with_message(
shell,
client,
Method::POST,
"https://api.dropboxapi.com/2/files/list_folder",
|req| {
req.bearer_auth(access_token)
.json(&json!({ "shared_link": { "url": SHARED_LINK_URL }, "path": path }))
},
)?;
if res.status() == 200 {
let ListFolder { entries } = res.json()?;
Ok(entries
.into_iter()
.map(|ListFolderEntry { name }| name)
.collect())
} else {
let msg = res.text()?;
let msg = serde_json::to_string_pretty(&msg).unwrap_or(msg);
Err(anyhow!("{}", msg).context("API error"))
}
}
fn exists(
shell: &mut Shell,
client: &reqwest::blocking::Client,
contest_id: &str,
) -> anyhow::Result<bool> {
let status = request_with_message(
shell,
client,
Method::GET,
&format!("https://atcoder.jp/contests/{}", contest_id),
convert::identity,
)?
.status();
match status.as_u16() {
200 => Ok(true),
404 => Ok(false),
_ => bail!("{}", status),
}
}
fn request_with_message(
shell: &mut Shell,
client: &reqwest::blocking::Client,
method: Method,
url: &str,
modify_req: impl FnOnce(reqwest::blocking::RequestBuilder) -> reqwest::blocking::RequestBuilder,
) -> anyhow::Result<reqwest::blocking::Response> {
let req = modify_req(client.request(method.clone(), url));
shell.network(format!("{}: {}", method, url))?;
let res = req.send()?;
shell.network(res.status())?;
Ok(res)
}
struct Shell(BufferedStandardStream);
impl Shell {
fn new() -> Self {
Self(BufferedStandardStream::stderr(
if atty::is(atty::Stream::Stderr) {
ColorChoice::Auto
} else {
ColorChoice::Never
},
))
}
fn info(&mut self, message: impl fmt::Display) -> io::Result<()> {
self.print("info:", message, Color::Cyan)
}
fn network(&mut self, message: impl fmt::Display) -> io::Result<()> {
self.print("network:", message, Color::Magenta)
}
fn print(
&mut self,
status: impl fmt::Display,
message: impl fmt::Display,
color: Color,
) -> io::Result<()> {
self.0
.set_color(ColorSpec::new().set_bold(true).set_fg(Some(color)))?;
write!(self.0, "{}", status)?;
self.0.reset()?;
writeln!(self.0, " {}", message)?;
self.0.flush()
}
}
@qryxip
Copy link
Author

qryxip commented Aug 24, 2020

ARC[0-9]{3}にあたるのは44個あり、すべてA..Fの6問分が入っている。

ARC060
ARC061
ARC062
ARC063
ARC064
ARC065
ARC066
ARC067
ARC068
ARC069
ARC070
ARC071
ARC072
ARC073
ARC074
ARC075
ARC076
ARC077
ARC078
ARC079
ARC080
ARC081
ARC082
ARC083
ARC084
ARC085
ARC086
ARC087
ARC088
ARC089
ARC090
ARC091
ARC092
ARC093
ARC094
ARC095
ARC096
ARC097
ARC098
ARC099
ARC100
ARC101
ARC102
ARC103

@qryxip
Copy link
Author

qryxip commented Aug 24, 2020

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment