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

2020-08-24時点でAtCoderでのscreen nameと合わないのは以下の32個:

info: 2019ddccqual
info: 2019exa
info: 2019nikkei_qual
info: 2019yahoo_qual
info: 2020_dwango_qual
info: 2020_hitachi
info: 2020_panasonic
info: ARC058_ABC042
info: ARC059_ABC043
info: CodeFestival2016EliminationTournament
info: CodeFestival2016Exhibition
info: CodeFestival2016Final
info: CodeFestival2016GrandFinal
info: CodeFestival2016QualA
info: CodeFestival2016QualB
info: CodeFestival2016QualC
info: CodeFestival2016Relay
info: CodeFestival2017Exhibition
info: CodeFestival2017Final
info: CodeFestival2017QualA
info: CodeFestival2017QualB
info: CodeFestival2017QualC
info: CodeFestival2017Relay
info: CodeFestival2017Tournament
info: MUJIN2017
info: diverta2_2019
info: diverta_2019
info: jsc2019
info: msolutions2019
info: nikkeiqual_2019
info: tenka1_2017
info: tenka1_2018
info: tenka1_2019

@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