RustでZaimと東京ガスを連携させてみた【1】

最近、昨年の医療費控除を受けるために確定申告の準備などをしているのですが、家計簿ソフトで利用しているZaimにガスや水道代が勝手に計上されてくれると便利だな〜と思い、Zaim APIとSeleniumを使って実現してみようと思います。 Zaim Developers Center 東京ガスのCSVファイルをダウンロードする Rustのheadless-chromeを使って構築します。 とりあえずCargoパッケージ作ってライブラリを追加します。 cargo new sync-zaim cargo add headless-chrome 追加できました。 使い勝手を知るために、まずはGoogleにアクセスしてスクショをとってみます。 use headless_chrome::{protocol::cdp::Page::CaptureScreenshotFormatOption, Browser}; use std::fs; fn main() { let browser = Browser::default().unwrap(); let tab = browser.new_tab().unwrap(); tab.set_default_timeout(std::time::Duration::from_secs(200)); tab.navigate_to("https://www.google.com/").unwrap(); tab.wait_until_navigated().unwrap(); let jpeg_data = tab.capture_screenshot( CaptureScreenshotFormatOption::Jpeg, None, None, true, ).unwrap(); fs::write("screenshot.jpg", jpeg_data).unwrap(); } 実行すると、Googleの画面のスクショが保存されました。 この調子で東京ガスのCSVを取得できるようにしましょう。 コードはpythonですがここを参考にします。 Python(Selenium)で東京ガスのcsvをダウンロード|gadgetking 実行すると少しエラーは出ますが、ファイルダウンロードは可能になりました。 use headless_chrome::{protocol::cdp::Page::CaptureScreenshotFormatOption, Browser}; use headless_chrome::protocol::cdp::types::Method; use std::sync::Arc; use std::{env, thread, time, error::Error}; use std::{fs, path}; use serde::Serialize; const TOKYO_GAS_LOGIN_URL: &str = "https://members.tokyo-gas.co.jp/login.html"; const GAS_ID: &str = "東京ガスのログインID"; const GAS_PASSWD: &str = "東京ガスのログインパスワード"; // https://github.com/rust-headless-chrome/rust-headless-chrome/issues/187 #[derive(Serialize, Debug)] struct Command { behavior: &'static str, downloadPath: &'static str, } impl Method for Command { const NAME: &'static str = "Page.setDownloadBehavior"; type ReturnObject = serde_json::Value; } fn main() -> Result<(), Box<dyn Error>> { let browser = Browser::default().unwrap(); let tab = browser.new_tab().unwrap(); tab.set_default_timeout(std::time::Duration::from_secs(200)); // https://github.com/rust-headless-chrome/rust-headless-chrome/issues/280 tab.enable_request_interception(Arc::new(|transport, session_id, params| { println!("request!"); println!("transport: {:?}", transport); println!("session_id: {:?}", session_id); println!("params: {:?}", params); headless_chrome::browser::tab::RequestPausedDecision::Continue(None) }))?; tab.navigate_to(TOKYO_GAS_LOGIN_URL).unwrap(); tab.wait_until_navigated().unwrap(); let jpeg_data = tab.capture_screenshot( CaptureScreenshotFormatOption::Jpeg, None, None, true, ).unwrap(); fs::write("screenshot.jpg", jpeg_data).unwrap(); tab.wait_for_element("#loginId").unwrap().type_into(GAS_ID).unwrap(); tab.wait_for_element("#password").unwrap().type_into(GAS_PASSWD).unwrap(); tab.wait_for_element("#submit-btn").unwrap().click().unwrap(); // tab.wait_until_navigated().unwrap(); thread::sleep(time::Duration::from_secs(5)); let jpeg_data = tab.capture_screenshot( CaptureScreenshotFormatOption::Jpeg, None, None, true, ).unwrap(); fs::write("screenshot.jpg", jpeg_data).unwrap(); let path_string = env::current_dir()?.into_os_string().into_string().unwrap(); // println!("pwd: {}", path_string); let static_path_str: &'static str = Box::leak(path_string.into_boxed_str()); let command = Command { behavior: "allow", downloadPath: static_path_str, }; tab.call_method(command)?; // CSVファイルをダウンロード tab.navigate_to("https://members.tokyo-gas.co.jp/api/mieru/chargeAmountCsv.jsp?no=0&target=total") .unwrap(); thread::sleep(time::Duration::from_secs(30)); Ok(()) } 実行すると以下のようなエラーが出ます。 ...

2023年9月10日 · にあえん

prismaでfactoryboyみたいなやつが作りたい

作りたい。 factoryboyをご存知ですか? factory_boy — Factory Boy stable documentation pythonでテスト実装する際、ダミーデータの実装に役に立つライブラリなのですが、これがまー使いやすい。 ので、prismaでシード作るときにも同じような感じで使いたいなーと思い、実現方法を考えてみました。 factory-botを使ってみる 調べてみると、factory-botというのがありました。 Git これを使ってみたいと思います。 とりあえずディレクトリを作って初期化します。 mkdir prisma-factory-bot cd prisma-factory-bot npm init 次に、typescriptの実行基盤とお目当てのfactory-botをインストールします。 npm i -D typescript ts-node @types/node npm i factory-bot これで準備できました。 最も簡易的な形の実装は以下のとおりです。 import { factory } from "factory-bot"; class User { username: string; score: number; constructor(attrs: { username: string, score: number }) { this.username = attrs.username; this.score = attrs.score; } } factory.define('user', User, { username: 'Bob', score: 50, }); (async () => { const user = await factory.build<User>('user'); console.log(user); })(); とりあえず動く形になりました。 実行すると、生成されたUserが表示されます。 User { username: 'Bob', score: 50 } bulidMany関数を使うと、複数のモデルをいっぺんに作ることができます。 ...

2023年8月23日 · にあえん

OS自作入門【1】

wasmに入門したりprismaにハマったりいろいろありますが、以前から興味のあった自作OSというものに手を出してみます。 この書籍を参考にしています。 ゼロからのOS自作入門 | 内田 公太 |本 | 通販 | Amazon 機械語でHello World! まずはバイナリの編集に使用するエディターをダウンロードします。 sudo apt install okteta エディターがダウンロードできたら、ひたすらHello Worldを実装します。 以下に上がっていますが、本見ながら写経しました。チカレタ。。 mikanos-build/day01/bin/hello.efi at master · uchan-nos/mikanos-build · GitHub 実際はこんな感じです。痺れますね。 これをBOOTX64.efiとして保存します。 ちゃんとコーディングできていれば、sumコマンドでチェックサムが12430となっています。 % sum ../../study/custom_os/BOOTX64.efi 12430 2 ではこれをエミュレーターで実行します。 QEMUをダウンロードしましょう。 Download QEMU - QEMU 公式にダウンロードのためのshがあったので使ってみます。 wget https://download.qemu.org/qemu-8.1.0-rc4.tar.xz tar xvJf qemu-8.1.0-rc4.tar.xz cd qemu-8.1.0-rc4 ./configure make しかし./configureのあたりでエラーになってしまいました。。 % ./configure Using './build' as the directory for build output python determined to be '/home/username/.anyenv/envs/pyenv/shims/python3' python version: Python 3.8.12 mkvenv: Creating non-isolated virtual environment at 'pyvenv' mkvenv: checking for meson>=0.63.0 mkvenv: installing meson>=0.63.0 mkvenv: checking for sphinx>=1.6.0, sphinx-rtd-theme>=0.5.0 'sphinx>=1.6.0' not found: • Python package 'sphinx' was not found nor installed. • mkvenv was configured to operate offline and did not check PyPI. Sphinx not found/usable, disabling docs. ERROR: Cannot find Ninja なんかpython使っているっぽいです。 ...

2023年8月21日 · にあえん

Rustで100本ノック【1】

ろくにRustに触らないままでwasmをやってしまっていましたが、ちゃんと触っておこうと思って100本ノックに挑みました。 言語処理100本ノック 2015 その際の備忘録を残します。 Q00: 文字列の逆順 逆順にするだけなのに、結局ググってしまった。。 Conversion between String, str, Vec<u8>, Vec<char> in Rust · GitHub fn main() { // 文字列"stressed"の文字を逆に(末尾から先頭に向かって)並べた文字列を得よ. let s = "stressed"; let mut s = s.chars().collect::<Vec<_>>(); s.reverse(); print!("{}", s.iter().collect::<String>()); } collectの使い方とか、pythonみたいに容易に行かない感じがする。 Q01 さっきよりかはスムーズにできた。 でもイテレータの使い方とかちょっとググった。 fn main() { // 「パタトクカシーー」という文字列の1,3,5,7文字目を取り出して連結した文字列を得よ. let s = "パタトクカシーー"; for (i, c) in s.chars().enumerate() { if !([1,3,5,7].contains(&i)) { continue; } print!("{}", c); } println!(); } Q02 基本的に先程の2問と解き方は一緒だった。 fn main() { // 「パトカー」+「タクシー」の文字を先頭から交互に連結して文字列「パタトクカシーー」を得よ. let s1 = "パトカー"; let s2 = "タクシー"; let mut s1_vec = s1.chars().collect::<Vec<_>>(); let mut s2_vec = s2.chars().collect::<Vec<_>>(); s1_vec.reverse(); s2_vec.reverse(); let mut result: Vec<char> = vec![]; while s1_vec.len() > 0 && s2_vec.len() > 0 { if s1_vec.len() > 0 { result.push(s1_vec.pop().unwrap()); } if s2_vec.len() > 0 { result.push(s2_vec.pop().unwrap()); } } println!("{}", result.iter().collect::<String>()); } Q03 前の問題に比べると少し楽になりました。 ...

2023年8月20日 · にあえん

hugoのテーマを新しくしました

今のHugoのテーマから、記事の検索機能などもあるいい感じのテンプレートに乗り換えたいなーと思った際の備忘録です。 何個かのテーマをインストールして実際に起動して確認してます。 hugoplateを導入する デモを見る限り、リッチなUIや記事の検索機能などがあり結構いいなと思ったのがこのテーマです。 Hugoplate git submoduleを使用してテーマを追加します。 hugoプロジェクトのthemesディレクトリで以下のコマンドを実行します。 git submodule add https://github.com/zeon-studio/hugoplate.git config.tomlを変更します。 theme = "hugoplate" この状態で一旦ローカルから起動してみます。 hugo serve -D しかしエラーが出てしまいました。。 render of "page" failed: execute of template failed: template: _default/single.html:8:7: executing "_default/single.html" at <partial "essentials/head.html" .>: error calling partial: "/home/username/Project/personal/hugo-blog/themes/hugoplate/layouts/partials/essentials/head.html:13:3": execute of template failed: template: partials/essentials/head.html:13:3: executing "partials/essentials/head.html" at <partialCached "favicon" .>: error calling partialCached: partial "favicon" not found render of "page" failed: execute of template failed: template: _default/single.html:8:7: executing "_default/single.html" at <partial "essentials/head.html" .>: error calling partial: "/home/username/Project/personal/hugo-blog/themes/hugoplate/layouts/partials/essentials/head.html:13:3": execute of template failed: template: partials/essentials/head.html:13:3: executing "partials/essentials/head.html" at <partialCached "favicon" .>: error calling partialCached: partial "favicon" not found render of "page" failed: execute of template failed: template: _default/single.html:8:7: executing "_default/single.html" at <partial "essentials/head.html" .>: error calling partial: "/home/username/Project/personal/hugo-blog/themes/hugoplate/layouts/partials/essentials/head.html:13:3": execute of template failed: template: partials/essentials/head.html:13:3: executing "partials/essentials/head.html" at <partialCached "favicon" .>: error calling partialCached: partial "favicon" not found render of "page" failed: execute of template failed: template: _default/single.html:8:7: executing "_default/single.html" at <partial "essentials/head.html" .>: error calling partial: "/home/username/Project/personal/hugo-blog/themes/hugoplate/layouts/partials/essentials/head.html:13:3": execute of template failed: template: partials/essentials/head.html:13:3: executing "partials/essentials/head.html" at <partialCached "favicon" .>: error calling partialCached: partial "favicon" not found Rebuild failed: Failed to render pages: render of "page" failed: execute of template failed: template: _default/single.html:8:7: executing "_default/single.html" at <partial "essentials/head.html" .>: error calling partial: "/home/username/Project/personal/hugo-blog/themes/hugoplate/layouts/partials/essentials/head.html:13:3": execute of template failed: template: partials/essentials/head.html:13:3: executing "partials/essentials/head.html" at <partialCached "favicon" .>: error calling partialCached: partial "favicon" not found hugo v0.92.2+extended linux/amd64 BuildDate=2023-01-31T11:11:57Z VendorInfo=ubuntu:0.92.2-1ubuntu0.1 Reload Page なんかファビコンが足りないっぽい? ...

2023年8月18日 · にあえん

wasmに入門してみる【8】

今回で最後になります。 wasmに入門してみる【1】 wasmに入門してみる【2】 wasmに入門してみる【3】 wasmに入門してみる【4】 wasmに入門してみる【5】 wasmに入門してみる【6】 wasmに入門してみる【7】 wasmのサイズを小さくする いくつかの設定を追記することで、wasmのサイズを小さくすることができます。 Shrinking .wasm Size - Rust and WebAssembly Rustのリリースビルドみたいなものですね。 まず必要なパッケージをインストールします。 cargo install wasm-opt Cargo.tomlに設定を追記します。 [profile.release] lto = true opt-level = "z" 再ビルドする前に、一度どのくらいの大きさなのか見ておきましょう。 % wc -c pkg/wasm_game_of_life_bg.wasm 25136 pkg/wasm_game_of_life_bg.wasm 大体25KBくらいでした。 では先程インストールしたwasm-optで最適化していきます。 wasm-opt -Oz -o output.wasm pkg/wasm_game_of_life_bg.wasm 最適化できました。 % wc -c output.wasm 24955 output.wasm あんまり変わっていないですね。。 wasm-packでもビルドしてみて、wasmファイルに変化があるか確認してみます。 %wasm-pack build [INFO]: 🎯 Checking for the Wasm target... [INFO]: 🌀 Compiling to Wasm... Compiling wasm-game-of-life v0.1.0 (/home/username/Project/personal/wasm-game-of-life) warning: unused macro definition: `log` --> src/lib.rs:25:14 | 25 | macro_rules! log { | ^^^ | = note: `#[warn(unused_macros)]` on by default warning: `wasm-game-of-life` (lib) generated 1 warning Finished release [optimized] target(s) in 0.41s [INFO]: ⬇️ Installing wasm-bindgen... [INFO]: found wasm-opt at "/home/username/.cargo/bin/wasm-opt" [INFO]: Optimizing wasm binaries with `wasm-opt`... [INFO]: Optional fields missing from Cargo.toml: 'description', 'repository', and 'license'. These are not necessary, but recommended [INFO]: ✨ Done in 0.86s [INFO]: 📦 Your wasm pkg is ready to publish at /home/username/Project/personal/wasm-game-of-life/pkg. ビルドできたので確認します。 ...

2023年8月18日 · にあえん

wasmに入門してみる【7】

前回からの続きです。 wasmに入門してみる【1】 wasmに入門してみる【2】 wasmに入門してみる【3】 wasmに入門してみる【4】 wasmに入門してみる【5】 wasmに入門してみる【6】 今回はタイムプロファイリングの章です。 Time Profiling - Rust and WebAssembly タイムプロファイリングする ライフゲームのパフォーマンス向上の指針として、FPS(Frames Per Second)タイマーの導入を行います。 window.performance.nowという関数を利用して、 www/index.jsにfpsオブジェクトを実装します。 const fps = new class { constructor() { this.fps = document.getElementById("fps"); this.frames = []; this.lastFrameTimeStamp = performance.now(); } render() { // 最後のフレームレンダリングからの差分時間を1秒あたりのフレーム数に変換する。 const now = performance.now(); const delta = now - this.lastFrameTimeStamp; this.lastFrameTimeStamp = now; const fps = 1 / delta * 1000; // 最新の100個のタイミングだけを保存する。 this.frames.push(fps); if (this.frames.length > 100) { this.frames.shift(); } // 最新の100個のタイムの最大値、最小値、平均値を求めます。 let min = Infinity; let max = -Infinity; let sum = 0; for (let i = 0; i < this.frames.length; i++) { sum += this.frames[i]; min = Math.min(this.frames[i], min); max = Math.max(this.frames[i], max); } let mean = sum / this.frames.length; // fpsに描画する this.fps.textContent = ` Frames per Second: latest = ${Math.round(fps)} avg of last 100 = ${Math.round(mean)} min of last 100 = ${Math.round(min)} max of last 100 = ${Math.round(max)} `.trim(); } }; //...中略 const renderLoop = () => { fps.render(); // 追記 universe.tick(); drawGrid(); drawCells(); animationId = requestAnimationFrame(renderLoop); }; www/index.htmlにも要素を追加します。 ...

2023年8月17日 · にあえん

prisma client pythonが実行しているNodeはどこにあるのか

前回、prisma-client-pythonに入門しました。 しかし、どうやってNodeが動いているのかちょっとわからなかったので調べてみました。 Nodeの実態は? 現状、グローバルにprismaが使える状態ではないため、普通にprismaコマンドを実行しても実行できません。 % prisma zsh: command not found: prisma しかし、前回作ったパッケージ上であればprismaコマンドは実行できます。 % poetry run prisma This command is only intended to be invoked internally. Please run the following instead: prisma <command> e.g. prisma generate 説明を見るかぎり、ここで実行されているprismaは本家のCLIのラッパーとして実装されているようです。 Prisma Client Python exposes a CLI interface which wraps the Prisma CLI. This works by downloading a Node binary, if you don’t already have Node installed on your machine, installing the CLI with npm and running the CLI using Node. ...

2023年8月15日 · にあえん

prisma-client-pythonでpythonからORMを楽にやる

今、自分の中でORMツールとしてアツいのがprismaです。 Prisma | Instant Postgres plus an ORM for simpler db workflows モデル定義もマイグレーションも、ドキュメント読んだり実装してみたりすればするほどかなり使える書き方ができるので、すごく可能性を感じています。 そんなprismaですが、python用のクライアントがあるということで、勉強がてら触ってみたいと思います。 Prisma Client Python pythonプロジェクトの初期化 まずはpoetryでプロジェクトを初期化します。 poetry new prisma-client-python-playground 作成されたプロジェクトに依存性を追加します。 poetry add -D prisma さて、これでいつものprisma CLIが使えるようになりました。 早速Prismaの初期化をしていきましょう。 今回もsqliteを使用していきます。 % poetry run prisma init --datasource-provider sqlite ✔ Your Prisma schema was created at prisma/schema.prisma You can now open it in your favorite editor. Next steps: 1. Set the DATABASE_URL in the .env file to point to your existing database. If your database has no tables yet, read https://pris.ly/d/getting-started 2. Run prisma db pull to turn your database schema into a Prisma schema. 3. Run prisma generate to generate the Prisma Client. You can then start querying your database. More information in our documentation: https://pris.ly/d/getting-started 初期化できました。 ...

2023年8月13日 · にあえん

wasmに入門してみる【6】

前回からの続きです。 wasmに入門してみる【1】 wasmに入門してみる【2】 wasmに入門してみる【3】 wasmに入門してみる【4】 wasmに入門してみる【5】 ゲームの一時停止と再生機能 まずはゲームの一時停止と再生ができるようにコードを修正します。 実装はJS内のみで完結できます。 www/index.htmlに新しくボタンを追加します。 <body> <noscript>This page contains webassembly and javascript content, please enable javascript in your browser.</noscript> <!-- ボタンを追加 --> <button id="play-pause"></button> <canvas id="game-of-life-canvas"></canvas> <script src="./bootstrap.js"></script> </body> 次にこのボタンに対するハンドラの実装を行います。 // requestAnimationFrameが発行する返り値を格納する変数 // https://developer.mozilla.org/ja/docs/Web/API/window/requestAnimationFrame let animationId = null; // 再生/停止を行うボタンを取得 const playPauseButton = document.getElementById("play-pause"); // 再生時のハンドラを実装 const play = () => { playPauseButton.textContent = "⏸"; renderLoop(); }; // 停止時のハンドラを実装 const pause = () => { playPauseButton.textContent = "▶"; cancelAnimationFrame(animationId); animationId = null; }; // 停止しているか判定する関数 const isPaused = () => { return animationId === null; }; // 再生/停止ボタンにイベントリスナーを追加 playPauseButton.addEventListener("click", (event) => { if (isPaused()) { play(); } else { pause(); } }); const renderLoop = () => { universe.tick(); drawGrid(); drawCells(); animationId = requestAnimationFrame(renderLoop); // requestAnimationFrameの返り値を設定 }; drawGrid(); drawCells(); play(); // requestAnimationFrame(renderLoop);をplay関数に置き換え これで再生/停止ボタンが使えるようになります! ...

2023年8月12日 · にあえん