園児ニアのメモ

ただのエンジニア。何でも屋みたいな扱い受けてます。

【AppleScript】ブラウザを開いて自動で入力する

勤怠など毎日入力するのが面倒な部分があるので作ってみました。
AppleScriptを使用しますのでMacのみ対応しています。

●やりたいこと
・勤怠入力で最初から定型文を入力する
・完全自動化もできるが勤怠なので作業内容などは色を変えて入力を指示だけにする

今回はZACという勤怠管理ツールを参考に書きます。
↓普通にブラウザで開いた勤怠入力画面
f:id:nanokanato:20170914121617p:plain:w300
起動時に今日の日付になるのは自動ですが...
毎日、出勤時間,退勤時間,作業時間などはある程度テンプレなど前日のデータを入力していてほしいです。

AppleScriptでテンプレ入力

AppleScriptとは、文字通りAppleが開発したMacOS用のオブジェクト指向スクリプト言語です。

AppleScriptは「Application/Utility/スクリプトエディタ.app」で開発することができます。
実行ボタンを押すことで実際にどんな動作をするか確認できます。

以下は実行することでGoogle Chromeを起動し、勤怠のページを開いて最初から入力していてほしい部分に定型文を入れるソースです。

tell application "Google Chrome" to run
tell application "Google Chrome" to activate
tell application "Google Chrome"
	tell window 1
		set newTab to make new tab with properties {URL:"https://hogehoge.zac.ai/hogehoge/Shinsei/Nippou.asp"}
		repeat while loading of active tab
			delay 1
		end repeat
		tell active tab
			-- 出勤時間の入力
			execute javascript "var start_time_hours = document.body.getElementsByTagName('select')[1].getElementsByTagName('option');"
			execute javascript "for(var i = 0; i < start_time_hours.length; i++){
								if(start_time_hours[i].value == '10'){
									start_time_hours[i].selected = true;
									break;
							 	}
							   }"
			-- 退勤時間の入力
			execute javascript "var end_time_hours = document.body.getElementsByTagName('select')[4].getElementsByTagName('option');"
			execute javascript "for(var i = 0; i < end_time_hours.length; i++){
								if(end_time_hours[i].value == '19'){
									end_time_hours[i].selected = true;
									break;
							 	}
							   }"
			
			-- 作業内容を登録
			execute javascript "var task_time_hours = document.body.getElementsByTagName('select')[57].getElementsByTagName('option');"
			execute javascript "for(var i = 0; i < task_time_hours.length; i++){
								if(task_time_hours[i].value == '8'){
									task_time_hours[i].selected = true;
									break;
							 	}
							   }"
			
			execute javascript "document.body.getElementsByTagName('select')[61].style.backgroundColor = 'skyblue';"
			execute javascript "document.body.getElementsByTagName('textarea')[6].style.backgroundColor = 'skyblue';"
		end tell
	end tell
end tell

AppleScriptを実行して開かれた勤怠入力画面
f:id:nanokanato:20170914121900p:plain:w300
・出勤時間と退勤時間はデフォルトを入力。
・作業時間もデフォルトを入力。
・作業内容の部分に色をつけて編集しないといけないことを指示。

若干ですが、煩わしい選択が少し減ることで毎日続けれる気がします。

スクリプトエディタで「ファイル > 書き出す...」を選択し、フォーマットをアプリケーションにすることで.appのアプリケーションファイルを作成できます。

毎日これをクリックすることで最初からテンプレートが入力された状態のページを開くことができます。

【GAS】RSSのURLを渡すと連想配列を返すライブラリ、ELERSSReaderを公開

RSSとはサイトなどの更新情報を受け取れるフォーマット、またはその仕組みなどを一般的に言います。

RSSについては詳しくは以下をどうぞ。
RSS - Wikipedia

そのRSSをGoogleAppScript(以下GAS)にて簡単に取得するライブラリを作りました。

中でやっていること

どこらへんが簡単になっているのかというと...

  1. 内部で受け取った引数のURLからRSSの情報を取得します。
  2. 取得したRSS情報をテキストからXMLに変換。
  3. XMLはGASでは使いづらいので連想配列の形式に変換。

という処理をやっています。

導入方法

ライブラリの追加に必要なプロジェクトキー

1J1FyM0gHx_X_bXb_qgGHnbzM1IIpGMmOMGaf_dzo7BwX_Cfop2ZieJLr

ELERSSReaderの使用方法

function myFunction() {
    var rss = ELERSSReader.GetRSS("http://www.hogehoge.rss");
    if (rss != null) {
    
    }
}

GetRSS()メソッドにRSSのURLを送るだけで連想配列が取得できます。

実際に「国土交通省 / 気象庁」のRSSを取得した結果が下の画像です。
f:id:nanokanato:20170912190810p:plain:w300

通常は「data」の中にArray型で入っています。
エラー時は「error」としてエラー内容の参照配列が入っています。

活用方法

RSSを読み取るだけでは活用は難しいですが、SNSと連携することで通知機能として活躍すると思います。

今回これを作った経緯として、
Unity公式のARKitの開発が「BitBucket」というサービスで公開されており、その進捗を自動で通知するために使っています。

Chatworkに投稿

madgenius.hateblo.jp

【Unity】unitypackageのzipを解凍するとフォルダができてしまう

これに関してはMacユーザーのみ、または解凍ソフトの問題だとは思うのですが...

.unitypackageのファイルをZIPに圧縮したものを受け取り、Macアーカイブユーティリティ.appという標準の解凍ソフトを使うと「ZIP → .unitypackage → フォルダ」というように解凍し過ぎてしまいます....

.unitypackageはフォルダになった時には削除されておりゴミ箱にも入っていない状態です。
なんかもう、これもどうせ解凍するんやろ??みたいなところがAppleって感じですね。

解決法としては単純でターミナルで以下を入力し、zipのみ解凍することを指示してやればOKです。

unzip [ファイル名]

【Unity】数字の画像でスコアを表現する

スコアなどを表示するときにフォントがあれば一番ですが、特殊な表示をしたい場合について書きます。

用意した0〜9の数字の画像を使ってそれをUI.Textのように簡単に使えるようにしてみたいと思います。

画像素材の用意

用意するのはこのような画像で0〜9までです。
f:id:nanokanato:20170908135201p:plain:w30

スクリプトの用意

以下のNumberText.csはUI.Textのように簡単な入力で画像を使ったスコアなどの表示をするためのクラスです。

このスクリプトを空のGameObjectに配置することで使用できます。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class NumberText : MonoBehaviour {
	public long num = 1234567890;
	public int digit = 16;
	public bool zeroFill = false;
	private List<Image> NumImageList = new List<Image>();
	[SerializeField] private Sprite[] spriteNumbers = new Sprite[10];
	
	// Update is called once per frame
	void Update () {
		if (NumImageList.Count == digit) {
			//桁数が揃っているので数値を表示する
			long num2 = num;
			int numDigit = 0;
			if (num2 > 0) {
				numDigit = ((int)Mathf.Log10(num2) + 1);
			}
			if (numDigit > digit) {
				//数値が桁数を超えている
				for (int i = 0; i < NumImageList.Count; i++) {
					Image numImage = NumImageList.ToArray () [i];
					if (numImage != null) {
						numImage.color = Color.white;
						numImage.sprite = spriteNumbers [spriteNumbers.Length - 1];
					}
				}
			} else {
				//数値が桁数を超えていない
				int[] numIndexs = new int[numDigit];
				for (int i = 0; i < numDigit; i++) {
					numIndexs [i] = (int)(num2 % 10); 
					num2 = num2 / 10;
				}
				for (int i = 0; i < NumImageList.Count; i++) {
					Image numImage = NumImageList.ToArray()[i];
					if (numImage != null) {
                                                if (numDigit == 0 && i == 0) {
							//数値が0だった時の処理(1桁目は必ず0で表示)
							numImage.color = Color.white;
							numImage.sprite = spriteNumbers[0];
						} else if (i < numIndexs.Length) {
							//数値を反映する
							numImage.color = Color.white;
							numImage.sprite = spriteNumbers[numIndexs[i]];
						} else {
							if (zeroFill) {
								//0埋め
								numImage.color = Color.white;
								numImage.sprite = spriteNumbers[0];
							} else {
								//非表示
								numImage.color = Color.clear;
							}
						}
					}
				}
			}
		} else {
			if (NumImageList.Count < digit) {
				//桁数が足りないので増やす
				GameObject numImageObj = new GameObject();
				if (numImageObj != null) {
					numImageObj.name = "NumberImage"+(NumImageList.Count+1);
					numImageObj.transform.SetParent (this.transform);
					RectTransform thisRect = this.GetComponent<RectTransform> ();
					if (thisRect != null) {
						Image numImage = numImageObj.AddComponent<Image> ();
						if (numImage != null) {
							numImage.color = Color.clear;
							RectTransform numImageRect = numImageObj.GetComponent<RectTransform> ();
							if (numImageRect != null) {
								if (spriteNumbers != null && spriteNumbers.Length > 0) {
									numImageRect.sizeDelta = new Vector2(spriteNumbers[0].bounds.size.x*(thisRect.sizeDelta.y/spriteNumbers[0].bounds.size.y), thisRect.sizeDelta.y);
									if (NumImageList.Count == 0) {
										numImageObj.transform.localPosition = new Vector3 (thisRect.sizeDelta.x / 2 - numImageRect.sizeDelta.x / 2, 0);
									} else {
										Image image = NumImageList.ToArray () [NumImageList.Count - 1];
										if (image != null) {
											numImageObj.transform.localPosition = new Vector3 (image.transform.localPosition.x - numImageRect.sizeDelta.x, 0);
										}
									}
									NumImageList.Add (numImage);
								}
							}
						}
					}
				}
			} else {
				//桁数が多いので減らす
				Image image = NumImageList.ToArray()[NumImageList.Count-1];
				if (image != null) {
					NumImageList.RemoveAt(NumImageList.Count-1);
					Destroy(image.gameObject);
				}
			}
		}
	}
}

中にコメントが書いているので何をしているかわかると思いますが説明。

主な設定項目
  • num - 表示したい数値
  • digit - 表示する最大桁数
    • numがdigitの桁数を超えた場合はdigitの桁数の最大の数値を表示する。(digitが4の時、9999など)
  • zeroFill - numがdigitの桁数より小さい場合に0埋めするかどうか
    • デフォルトはfalse、0埋めしない。
  • spriteNumbers - 0〜9までの数値の画像。
    • 0〜9の順番に合うように設定してください。
中でやっている処理について
  1. NumImageList(UI.Imageの配列)の数がdigit(最大桁数)と違う時、同じ桁になるようにUI.Imageを生成したり消したりする。
  2. NumImageListとdigitの桁数が同じになった時、numの桁数を計算する(計算結果:numDigit)
  3. numDigitがdigitより大きい時、digitの桁数で最大の数値を表示。
  4. numDigitがdigit以下の時、桁ごとの数値を取得する。(123の場合は1桁目が3、2桁目が2、3桁目が1など)
  5. 1桁目からNumImageListの同じ桁の位置のUI.Imageに画像を反映する。
  6. numDigitより大きい桁の場合、zeroFillがtrueなら0埋めする。falseなら非表示にする。

実際の表示

最大桁数内で0埋めしないパターン

f:id:nanokanato:20170908141411p:plain:w300
num:1234567890
digit:13
zeroFill:false

最大桁数が3桁分余ったので0埋めしたパターン

f:id:nanokanato:20170908141356p:plain:w300
num:1234567890
digit:13
zeroFill:true

最大桁数が少ないため最大値を表示したパターン

f:id:nanokanato:20170908141421p:plain:w300
num:1234567890
digit:9
zeroFill:false

【GAS】指定の日が何の日かをWikiから取得するライブラリ、ELEWikiDateを公開

はじめに

GoogleAppScript(以下GAS)でWikiからその日が何の日かを取得するライブラリを作りましたので公開します。

要望やバグ、不明点があればコメントにてよろしくお願いします。

ELEWikiDateの導入方法

2017/09/07のバージョンではアニメキャラの誕生日のみになってますが、今後有名人の誕生日や記念日などにも対応する予定です。

ライブラリの追加に必要なプロジェクトキー

1B9gyeBky02pDM_D0Y1wENia1qRLr5xmxUxVuZ3QzMet5OujEDPA_yALW

ELEWikiDateの使用方法

誕生日のキャラクターの情報を取得
function myFunction() {
    var birthdays = ELEWikiDate.GetBirthDayChara(new Date());
    if (birthdays != null) {

    }
}

「ELEWikiDate.GetBirthDayChara([日付])」で指定した日付が誕生日のキャラクター情報の配列が取得できます。

誕生日の人物の情報を取得
function myFunction() {
    var birthdays = ELEWikiDate.GetBirthDayNonFiction(new Date(), "声優");
    if (birthdays != null) {

    }
}

「ELEWikiDate.GetBirthDayChara([日付],[カテゴリ])」で指定したカテゴリに該当する指定の日付が誕生日の人物の情報の配列が取得できます。

【GAS】Twitter連携ライブラリ、ELETwitterを公開

目次

はじめに

GoogleAppScript(以下GAS)でTwitterと連携してツイートしたりフォローしたりというTwitterAPIの機能を実装したライブラリを作成したので公開します。
要望やバグ、不明点があればコメントにてよろしくお願いします。

ELETwitterの導入方法

2017/09/06のバージョンでは自分が普段使っている機能しか対応していませんが、今後全てに対応する予定です。

ライブラリの追加に必要なプロジェクトキー

1GLFAeiqOZHQ_xCI4HaigE5R18A0vKwXQE_PoJefcEPGkVUMG4OAoUegg

ELETwitterを使用するまでの設定

どこのスクリプトでもいいのでメソッド外に以下を記入してください。

ELETwitter.TWITTER_CONSUMER_KEY = 'CONSUMER_KEY';
ELETwitter.TWITTER_CONSUMER_SECRET = 'CONSUMER_SECRET';
ELETwitter.OAUTH_USER_KEY = 'ここはなんでもいいです';

ライブラリのメソッドを呼び出す前に指定すればOKですが、メソッドの外で呼び出す方がオススメです。
アカウントを使い分けたい場合は「OAUTH_USER_KEY」を、TwitterAPIを使い分けたい場合は「TWITTER_CONSUMER_KEY」「TWITTER_CONSUMER_SECRET」をメソッドの中などで書き換えてください。(一応複数アカウント対応)

「OAUTH_USER_KEY = 'ここはなんでもいいです';」の「ここはなんでもいいです」の部分は任意の文字を入れてください。
この文字をkeyとしてTwitterアカウントの連携情報を保存,取得します。

ELETwitterがありません!みたいなことを言われた場合は正しくライブラリを追加してバージョンが選択されていない可能性があります。
もしくは以下の識別子を変えた可能性があります。

また、「CONSUMER_KEY」「CONSUMER_SECRET」がわからない人は以下の記事を参考にどうぞ(記事内のKeyとSecretです)
madgenius.hateblo.jp

設定は以上です。

機能一覧

今後追加更新あると思いますが解説します。(最終更新:2017/09/06)

また、「TwitterLogin」と「TwitterReset」以外の全てのメソッドでTwitterAPIからの返り値を受け取れます。
失敗やエラー内容の確認などもそこで取得できます。

初回実行時、GASによるGoogleアカウントの認証と「Please login to Twitter:」というエラーが出ます。
エラーの指示にしたがってTwitter連携が必要です。

ログイン - TwitterLogin

function myFunction() {
    ELETwitter.TwitterLogin();
}

Twitter連携だけをするテスト用のメソッドです。

連携解除 - TwitterReset

function myFunction() {
    ELETwitter.TwitterReset();
}

連携情報保存 - TwitterAuthCallback

基本的には使わないメソッドですが、ライブラリがTwitter連携した時のコールバック先になっています。

複数ユーザーの詳細を取得 - TwitterUsersLookup

function myFunction() {
    var result = ELETwitter.TwitterUsersLookup(null, ['12345678','90123456']);
}

複数ユーザーのアカウント情報の詳細を取得します。

ミュート登録 - TwitterMutesCreate

function myFunction() {
    var result = ELETwitter.TwitterMutesCreate(null, '12345678');
}

ログインしているアカウントで特定のユーザーをミュートします。

フォロー - TwitterFollow

function myFunction() {
    var result = ELETwitter.TwitterFollow(null, '12345678');
}

ログインしているアカウントで特定のユーザーをフォローします。

リムーブ - TwitterUnfollow

function myFunction() {
    var result = ELETwitter.TwitterUnfollow(null, '12345678');
}

ログインしているアカウントで特定のユーザーをリムーブ(フォローを解除)します。

ツイート - TwitterSend

function myFunction() {
    var result = ELETwitter.TwitterSend('テスト');
}

ログインしているアカウントで特定の内容をツイート(投稿)します。

DM送信 - TwitterSendDM

function myFunction() {
    var result = ELETwitter.TwitterSendDM(null, '12345678', 'テスト');
}

ログインしているアカウントから特定のアカウントへ指定した内容のDMを送る。

フォロー覧取得 - TwitterFollowList

function myFunction() {
    var result = ELETwitter.TwitterFollowList('5000',null, '12345678', -1);
}

ログインしているアカウントから特定のアカウントのフォロー一覧を取得します。

フォロワー覧取得 - TwitterFollowerList

function myFunction() {
    var result = ELETwitter.TwitterFollowerList('5000',null, '12345678', -1);
}

ログインしているアカウントから特定のアカウントのフォロワー一覧を取得します。

ツイート検索 - TwitterSearch

function myFunction() {
    var result = ELETwitter.TwitterSearch('200', '園児エル');
}

ログインしているアカウントでツイートを検索します。

エラー処理

エラー処理は各メソッドを読んだ時に戻り値として返ってきます。

エラーの受け取り方

以下のような感じでエラーが受け取れます。

function myFunction() {
    var result = ELETwitter.TwitterLogin();
    if (result == null) {
        //TwitterLoginやTwitterResetの場合は返り値がありませんが、
        //TwitterSearchなど返り値があるものの場合はエラーです。
    } else {
        //返り値あり
        if (result.errors == null) {
            //エラーあり
            var error = result.errors[0];
            if (error != null) {
                //error.message ← エラー内容
                //error.code ← エラーコード
            }
        } else {
            //エラーなし
        }
    }
}

「error.code」が0の場合は主にライブラリ内でエラーが発生した時の通知です。
その他、TwitterAPIのエラーやGASのエラーもtry{}cache{}で取得すれば同じ形式で入ります。

エラー内容をログとして残したり、OAuth認証がないChatworkに送ったり、メールで送信することをオススメします。

主なエラー内容

Please login to Twitter:

Twitterにログインしていない、または前回の通信で無効なトークンと言われログアウトした場合に出ます。
「Please login to Twitter: https://twitter.com...etc」のようなメッセージになってます。
このURLをブラウザで開くとよく見るTwitterの連携を許可しますか?みたいなページに飛びます。
連携後「認証が完了しました。このタブは閉じても問題ありません。」と表示されれば連携完了です。

Consumer Key is required.

TwitterAPIのConsumerKeyが設定されていない時に発生するエラーです。
ELETwitterを使用するまでの設定で設定方法がありますので再度確認してください。

Invalid argument:

メソッドを呼び出す時の引数が不正な場合に発生するエラーです。
指定が必須の引数がnullの場合が多いのでご確認ください。
「Invalid argument: Invalid argument: message is null」と返ってきた場合はmessageがnullまたは空文字("")のため不正です。

Longer than 140 characters.

ツイートの140文字の制限を超えた時のエラーです。

その他

その他TwitterやGASのエラーが返ってきます。
Twitter:Error Codes & Responses

Q&A

  • Q.ログイン処理でログ出力されたURLにアクセスし、連携を許可したら「Error handling callback: token mismatch」というエラーが出ました。どうすればいいですか?
    • A.TwitterResetで連携解除してから再度連携してみてください。

【GAS】ライブラリの作成

GoogleAppScript(以下GAS)でプロジェクトをライブラリとして後悔する方法を記載します。

GASの用意

毎度ながら過去記事参照でお願いします。
madgenius.hateblo.jp

ライブラリ用プロジェクトの作成

ライブラリ用のプロジェクトを作成してください。

ライブラリにした時にメソッド名が被ると厄介なので頭文字的なのをメソッド名に入れるか、クラス化してください。

ライブラリとして安定版を保存

ライブラリとして読み込む時に「版を保存」する必要があります。
編集途中のものがライブラリになっていては困りますよね...

プロジェクトの「ファイル > 版を管理」を開き
f:id:nanokanato:20170906120229p:plain:w300

版を管理のポップアップで「新しいバージョンを保存」すれば完了です。
登録時の変更内容はライブラリを使う人にも見られますが、保存したユーザーは見られません。
f:id:nanokanato:20170906120442p:plain:w300

ライブラリを追加する時に必要なプロジェクトキーは「ファイル > プロジェクトのプロパティ」のスクリプトIDを使用してください。
f:id:nanokanato:20170906120733p:plain:w300

ライブラリ内で使っているライブラリは追加した状態で「版を保存」してください。

また、GASプロジェクトの共有権限を全体などに変更してください。
(GSuiteなどを使用の場合、同じユーザーなのに権限が無いと怒られる...)

実際にライブラリを使ってみた

ライブラリを使うために新規で別のプロジェクトを作りました。
「リソース > ライブラリ...」よりプロジェクトキーを入力してライブラリを追加してください。
f:id:nanokanato:20170906121147p:plain:w300

追加するとそのライブラリが表示されます。
f:id:nanokanato:20170906133614p:plain:w300

追加したライブラリのタイトルのリンクをクリックすることでライブラリへのアクセス権限があるかを確認できます。
f:id:nanokanato:20170906123147p:plain:w300

ライブラリのメソッドを呼び出す時は、「(ライブラリの識別子).(メソッド名)」です。
以下はその例。Twitter連携用のライブラリでメソッドはDM送信。

function myFunction() {
    ELETwitter.TwitterSendDM(null,"2272932474","テスト");
}

こんな感じでTwitterにDMがきました。
f:id:nanokanato:20170906135207p:plain:w300

実際に作ったライブラリはこちら
madgenius.hateblo.jp