- 投稿日:2019-10-01T23:35:55+09:00
javascriptのMapは中身が入っていてもJSON.stringifyすると空に見える
掲題の通り
const hoge = new Map(); hoge.set(1,1); console.log(JSON.stringify(hoge));// {} が出力される正しい動作なので慌てずに中身をentriesなどで取り出して検証しましょう。
- 投稿日:2019-10-01T23:18:57+09:00
Amazon Photosで一括削除できなかったのでスクリプト書いた
きっかけ
アマゾンのプライム会員になると、Amazon Photosというサービスを利用できます。
外部リンク : Amazonプライムフォトが超最強!RAWも無制限にバックアップ可能
画像のみなら容量制限がなく、枚数制限もなく、劣化もしないという神サービスなのですが
画像枚数が一定枚数を超えると一括削除できません。最初の方は1枚選ぶと「すべて選択」というボタンが出現していたのですが、3000枚を超えたあたり?で出なくなったようです。
1枚ずつ選んで消すなんて修行はしたくなかったので、スクリプト書きました。画像を選択してスクロールしていくだけのスクリプトです。
ChromeのデベロッパーツールからConsoleを開いて(Ctrl + Shift + J)貼り付けて実行すると
3000枚まで自動選択されます。※一度に選択できる上限削除できたらリロードして貼り付けて~を繰り返してください。
うまくいかない場合はtimeとかいじってみてください。
const time = 0; let selectCount = 0; let ngCount = 0; const id = setInterval( async () => { try { let tmp = 0; document.querySelectorAll('button[class*=selector]').forEach(element => { if(!element.className.includes("selected")) { element.click(); tmp++; ngCount = 0; } }); selectCount += tmp; if(tmp === 0) { if(ngCount > 10) { end(id); } else { ngCount++; } } else if(selectCount >= 3000) { end(id); } await new Promise(resolve => setTimeout(() => resolve(), time)); scrollBy(0, 400); } catch (error) { end(id, false); } }) const end = (id, click = true) => { if(click) { document.querySelector("button[class=trash]").click(); } clearInterval(id); }
- 投稿日:2019-10-01T16:55:56+09:00
localstorageに直接true/falseを格納しようとした時にハマったこと
ハマったこと
localstorage上にtrue/falseを格納して、取り出してifしようとしたところ、falseを取り出してるのにifをtrueで抜けてきた
localstorage.setItem('setVariable',true)参考記事
・JavaScriptで"false"が文字列扱いされる場合の覚書
解決方法
原因としては、true/falseを格納してもStringで格納されてしまうので、1/0で格納する。
localstorage.setItem('setVariable',1)取り出して来た時にparseIntで数値化して、Booleanで真偽値型に変更する
Boolean(parseInt(localstorage.getItem('setVariable'),10))
- 投稿日:2019-10-01T14:36:34+09:00
【GAS】雨が降りそうな日に通知してくれる LINE BOT を作る
作るもの
雨が降りそうな日の朝にLINEでメッセージを届けるようにします。
(1個だけごみ捨てのメッセージが混ざってますが、これは別です。)
使うもの
・LINE Developersアカウント
・Google App Script
・Liverdoor Weather APIおおまかな流れ
・LINE Developersに登録
・プロパイダを作成
・チャネル(BOTのアカウント)を作成
・BOTと友達になる
・GASでAPIを叩いて雨が降りそうか判定するスクリプトを作る
・GASからLINE BOTを介して自分にメッセージを送らせる
・毎朝このスクリプトを実行するように設定するLINEのDevelopers設定編
LINE Developersに登録
https://developers.line.biz/ja/
LINEアカウントを持っていれば簡単に登録出来ると思います。
※UIなどは記事執筆時点のものなので多少変わるかもしれません
プロパイダを作成
ログインが済むと上のような画面になると思うので、新規プロバイダを作成します。
普通に開発者自身の情報を入れて行けば良いです。チャネルの作成
次にチャネルの作成です。「新規チャネル作成」を押します。
チャネルと言うと何かややこしいですが、BOTの登録だと思ったら良いと思います。今回の用途では「Messaging API」を選択します。
次の画面で、アプリ名やアイコンなど好きに設定したらええんちゃうかな。作ったBOTと友達になる
作成したチャネルの設定画面の下の方に行くと、QRコードがあります。
ここから友達登録をすると良いと思います。
メモっておくもの
Google App Script編
Googleアカウントは持っている前提で行きます。
https://www.google.com/script/start/
ここの「Start Scripting」から App Script を始めることが出来ます。
ダッシュボードに入ったら左上くらいにある「新規スクリプト」を押してスクリプト編集画面を開きます。具体的なコードの例
とりあえず先にコードを貼ります。
function main() { // お天気APIを読んで結果をもらう const result = callWeatherAPI(); // 正常にAPIから応答があるか if (result.getResponseCode() !== 200) { // 異常があればメッセージを失敗を知らせるものにする var message = "何か失敗しました。レスポンスコード:" + result.getResponseCode(); } else { // 正常ならデータをJSONにパース var data = JSON.parse(result.getContentText()).forecasts[0].telop; // 結果に雨という文字が含まれないなら何もせず中断する if(data.split("").indexOf("雨") == -1) { return; } // メッセージを設定 var message = "今日は雨が降るかもしれません。\n\n予報:" + data; } // LINEへ送信 var line_access_token = "YOUR_ACCESS_TOKEN"; var line_to = "YOUR_LINE_TO"; sendToLine(line_access_token, line_to, message); } // ******************************************* // Livedoorのお天気APIを叩いてレスポンスをもらう関数 // ******************************************* function callWeatherAPI() { const response = UrlFetchApp.fetch( "http://weather.livedoor.com/forecast/webservice/json/v1?city=400010", // TODO: set your city number { "method" : "get", "headers" : { "Content-Type": "application/json" }, "muteHttpExceptions": true } ); return response; } // ******************************************* // LINEボットへ送信する関数 // ******************************************* function sendToLine(access_token, to, message) { var url = "https://api.line.me/v2/bot/message/push"; var headers = { "Content-Type" : "application/json; charset=UTF-8", 'Authorization': 'Bearer ' + access_token, }; var postData = { "to" : to, "messages" : [ { 'type':'text', 'text': message, } ] }; var options = { "method" : "post", "headers" : headers, "payload" : JSON.stringify(postData) }; return UrlFetchApp.fetch(url, options); }このままでは動かないので、編集しないといけない部分があります。
LINEの設定部分
中盤のこの部分
var line_access_token = "YOUR_ACCESS_TOKEN"; var line_to = "YOUR_LINE_TO";ここをそれぞれLINE Developers編でメモったアクセストークンと自分のIDに差し替える必要あり。
どの都市の天気を取得するかの設定
const response = UrlFetchApp.fetch( "http://weather.livedoor.com/forecast/webservice/json/v1?city=400010",デフォルトだと九州の久留米になっています。
都市のIDやパラメータの設定などはこちら参照。
今のところAPI KEYなどは不要なようです。http://weather.livedoor.com/weather_hacks/webservice
関数を実行する
編集画面上部の再生ボタンみたいなマークを押すと関数を実行出来ます。
この時、実行する関数は「main()」関数にします。
「関数を選択」からmain関数が指定されているのを確認してください。各項目が正常に指定できていれば、さっき作ったボットからメッセージが送られてくる...と言いたいところですが、雨の予報でなければ送られてきません。
// 結果に雨という文字が含まれないなら何もせず中断する /* if(data.split("").indexOf("雨") == -1) { return; } */この部分をこのようにコメントアウトすれば、とりあえずどんな予報でも「今日は雨が降るかもしれません: 予報内容」というメッセージが届くと思います。
挙動を確認したらコメントアウトを元に戻しておきましょう。
定期実行を行う
今度はこれを自動的に毎朝実行出来るようにしたいです。
GASはかなり簡単に定期実行が出来るようになってます。まずスクリプト編集画面上部にある時計みたいなアイコンをクリックします。
するとトリガー一覧画面に移るので「トリガーを追加」をクリック。
実行する関数を「main」にして、「日付ベースのタイマー」を選択。
時間は好みで良いです、朝の6時~7時とかが良いんじゃないでしょうか。結果
毎朝、天気予報が雨の場合はメッセージが届くようになります。
冒頭と同じ画像ですが...以下のように。
- 投稿日:2019-10-01T08:56:24+09:00
React-ReduxだけどReducerもActionも書かず、Dispatchすら使わず、データも何となく受け取れるようにする方法
React-ReduxだけどReducerもActionも書かず、Dispatchすら使わず、データも何となく受け取れるようにする方法
前回
Reduxがあまりにも面倒なので、何もかも隠蔽しつつ、その力を引き出すことにしたとにかく面倒くさいFlux
React上での状態管理は、ことごとくがFluxの考え方を用いて作られています。
Actionのコマンドを定義してDispatchで送信し、Reducerで処理してStoreに書き込み、ViewがStoreの更新を察知して処理を行うという流れです。これらの処理の厄介なところは、全部が全部、記述箇所がバラバラになってしまうということです。
とにかく何もかも面倒くさいので、この地獄から抜け出す方法を探しました。結果として、上記に書いたようなことを一切書かず、簡単にStoreデータを操作する方法にたどり着きました。
環境設定と必要なパッケージ
今回のサンプルプログラムを動かすにはReact+TypeScript環境を用意してください。
また、以下のパッケージをインストールする必要があります。npm -D i @jswf/redux-moduleサンプルプログラム
HooksのFunctionコンポーネントと、Classコンポーネントでの利用法を同時に載せているので、プログラムが少々長くなっています。
中でやっているのはinputの内容をコンポーネント間で共有するというものです。
もしこれをやるためだけにReduxの書式を真面目に書いたら、確実に無駄地獄へ落ちることが出来るでしょう。コンポーネント間のデータ共有にはReduxModuleというクラスを継承して利用します。
クラスを作ると、クラスごとに一つStore領域が割り当てられます。
そのため同じクラスを使用する限り、同じデータを参照することが出来ます。
また、読み書きの受付も全てこのReduxModule継承クラスが担当するので、処理をあちこちに書く必要はありませんhttps://github.com/JavaScript-WindowFramework/redux-module-sample
index.tsximport React, { Component } from "react"; import * as ReactDOM from "react-dom"; import { createStore } from "redux"; import { Provider } from "react-redux"; import { ModuleReducer, useModule, ReduxModule, mapModule, mapConnect } from "@jswf/redux-module"; /** *データ構造の定義(TypeScript使用時) * * @export * @interface TestState */ export interface TestState { msg: string; } /** *Storeアクセス用クラス *(クラスごとに自動的にStoreに領域を確保する) * @export * @class TestModule * @extends {ReduxModule<TestState>} */ export class TestModule extends ReduxModule<TestState> { //ここに初期値を設定可能 protected static defaultState: TestState = { msg: "初期値" }; //以下のようなアクセス用のメソッドは、必ずしも作る必要は無い //getStateとsetStateはpublicなので、外から直接書き換えてしまってもOK public getMessage() { return this.getState("msg")!; } public setMessage(msg: string) { this.setState({ msg }); } } /** *Hooks用サンプル * * @returns */ function HooksApp() { //モジュールのインスタンスを受け取る //useModuleの使用可能場所の制限は他のhookと同じ const testModule = useModule(TestModule); //以下のようにPrefixを付けると、同じクラスが違う領域を持つことも出来る //const testModule = useModule(TestModule,"Prefix"); return ( <> <div>FunctionComponent</div> <input value={testModule.getMessage()} onChange={e => testModule.setMessage(e.target.value)} /> <hr /> </> ); } /** *Class用サンプル * * @class _ClassApp * @extends {Component} */ class _ClassApp extends Component { render() { //モジュールのインスタンスを受け取る //Hooksと名前と引数が微妙に違うので注意 const testModule = mapModule(this.props, TestModule); return ( <> <div>ClassComponent</div> <input value={testModule.getMessage()} onChange={e => testModule.setMessage(e.target.value)} /> <hr /> </> ); } } //クラスコンポーネントを利用する場合は以下の方法でマッピングする //ここで宣言したモジュール以外はクラスで使用できない //モジュールは配列で複数指定も可能 const ClassApp = mapConnect(_ClassApp, TestModule); //Reduxに専用のReducerを関連付ける //他のReducerと併用することも可能 const store = createStore(ModuleReducer); ReactDOM.render( <Provider store={store}> <HooksApp /> <ClassApp /> </Provider>, document.getElementById("root") as HTMLElement );必要なことのまとめ
1 ModuleReducerをReduxのStoreに関連付ける
2 ReduxModuleを継承したデータクラスを作成する
3 Classコンポーネントを使う場合はmapConnectで使用するデータクラスを関連付ける
4 useModule/mapModuleでデータクラスを呼び出す
5 setState/getStateでデータの読み書きを行う一応解説しておく付加機能
蛇足になるので最小限にとどめますが、データクラスは外部参照機能も付いています。
別のデータクラスの機能が必要な場合はincludesに利用するモジュールを指定しておけば、getModuleで対象のクラスを呼び出すことが出来ます。export class OtherModule extends ReduxModule { static includes = [TestModule] public getMessage() { return this.getModule(TestModule).getState("msg")!; } public setMessage(msg: string) { this.getModule(TestModule).setState({ msg }); } }まとめ
とにかく状態管理が楽になりました。これを使うことによって、コンポーネント間の手続きの大部分が省略可能となります。コンポーネント間のデータ共有にReduxを使いたいけれど、Flux的な書き方が嫌だと考えているのならぜひ使ってみてください。
- 投稿日:2019-10-01T08:48:28+09:00
jsPDFでiOSに対応するよ
jsPDFはフロントエンドでPDFを生成できるとっても便利なライブラリー。早速試してみましょう。
const doc = new jsPDF({ orientation: 'p', unit: 'px', format: 'a4', putOnlyUsedFonts: true }) const fileName = 'hoge.pdf' doc.setFontSize(10).text('Hoge', 10, 10) doc.save(fileName)とっても簡単ですね。でも、あれ? iOSだとPDF出力できませんね。どうやらiOSはFileSystemがないので、直接PDFダウンロードができないようです。
というわけで、iOSの時はダウンロードしないで、新しいタブにPDFを直接表示してみましょう。const ua = window.navigator.userAgent.toLowerCase() const isIos = ua.indexOf('iphone') !== -1 || ua.indexOf('ipod') !== -1 || ua.indexOf('ipad') !== -1 if (isIos) { window.open(doc.output('bloburl', { filename: fileName })) } else { doc.save(fileName) }これで新しいタブにPDFが表示されました。
ユーザーはそこからPDFを保存したりメールで送ったりできるようになりましたとさ。
- 投稿日:2019-10-01T08:48:28+09:00
jsPDFでモバイルに対応するよ
jsPDFはフロントエンドでPDFを生成できるとっても便利なライブラリー。早速試してみましょう。
const doc = new jsPDF({ orientation: 'p', unit: 'px', format: 'a4', putOnlyUsedFonts: true }) const fileName = 'hoge.pdf' doc.setFontSize(10).text('Hoge', 10, 10) doc.save(fileName)とっても簡単ですね。でも、あれ? iOS SafariだとPDF出力できませんね。どうやらiOSはFileSystemがないので、直接PDFダウンロードができないようです。
というわけで、モバイルの時はダウンロードしないで、新しいタブにPDFを直接表示してみましょう。if (/android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(navigator.userAgent.toLowerCase())) { window.open(doc.output('bloburl', { filename: fileName })) } else { doc.save(fileName) }これでモバイルでは新しいタブにPDFが表示されました。
ユーザーはブラウザのシェア機能を使って、PDFを保存したりメールで送ったりできるようになりましたとさ。