2022-01-05
画像はGyazoに保存してそれを参照する。
使うもの
- taskumaDL_Inline
- https://www.icloud.com/shortcuts/eff11da7dc8140269358be90aade5c0f
TaskumaのレポートをEvernoteから読み込み、OPML形式にするショートカット本体。
- Scriptableアプリ
- Evernoteに保存されたTaskumaのレポートはデータ量が多く、ショートカットだけで処理すると遅すぎて実用的ではない。そこで、データをJavascriptで処理するためにこのアプリを使った。
- GetFromDataJar
- Data Jarアプリに保存したGyazoのアクセス・トークンを読み込むために使うショートカット・モジュール。
- UpImg2Gyz/wTstTkn
- Gyazoに画像をアップロードするショートカット・モジュール。
使用例
上のデータは、以下のEvernote上のレポートを読んだ結果。
注意点:レポート送信直後だと高い確率で読み込みに失敗する
Evernoteにレボートを送信して、1時間後くらいにこのショートカットを実行しても読み込めないことがある。
たといEvernoteのアプリでそのノートを開けても。
Evernoteアプリが使っているであろう非公開APIとショートカットが使っているAPIが異なり、後者の方は反映までのタイムラグがかなりあるのだろう。
最低でも3時間ほど間を開けると読み込みの確実性が上がる。半日後だとまず確実に読み込めるはず。
ただ、Evernoteのサーバーの状態次第なので10分後でも読み込めることもある。
これに関してはEvernoteがFreeプランだろうとPersonalプランだろうと違いはなさそう。
1日経っても読めない場合はそれ以上待っても読み込める可能性は低い。Evernoteのノートブックから読み込めなかったレポートのノートを削除して、Taskumaの過去ログタブから該当日1日分のログをレポート送信しなおしてから読み込み直す。
ライフログをDynalistに一元化するにあたり、これまでは「TaskumaのログをDynawrite経由でDynalistに送る Workflow」及びそれに続くメモで作った仕組みを使用していた。 どうしてもDynalistで画像を見返したい要件ができたので、Evernoteから読み込んで上手くやれないかと、しばらく試行錯誤していた。 その途中で、DynalistにではなくScrapboxに保存するタイプの試作物(完成度は低い)が前回できた。 そして更にそれを改変して、Dynalistに保存するタイプがようやく何とか動いた。 Scrapboxに保存するタイプは、データ処理を高速化するためにショートカット内で無理やりJavascriptを走らせていた。しかし、無理やり走らせている関係でエラー表示が全く無い。そのため、ちょっとした修正や改変がとても大変だった。 そこで今回は、エラー表示やコンソール出力のあるJavascript実行アプリScriptableをメインにしてみた。 ただし、Evernoteからの読み込みなど厄介な処理は、Scriptableのスクリプトからショートカットアプリを呼んでやらせている。 Evernote(Web) Scriptableで動かすJavascriptは、 このScriptableのスクリプトと共に使うショートカットアプリのショートカットは、次の3つ 改変1 キーボードからわざわざ入力する項目と、このスクリプト+ショートカットで機械的に追加する項目では重要度が異なることが多いと思う。 例えば次のような場合 だったとすると、健康面について気になった時の記録を読み返したい時に「#健康」で検索すると、Taskumaから来た日々の血圧記録までズラズラと表示されてしまい困ったことになる。 そこで今回の改変で、「#健康 -#たすくま」で検索することで、たすくまから機械的に追記したものを排除できるようにした。 直ったらしい。 Evernoteからのcsvファイル読み込みは、Scriptableのスクリプトではなくショートカットで行っている。 Evernoteにレポート送信直後だと、たとえEvernoteアプリでcsvファイルを開けられる状態でも、時々ショートカットによる読み込みが失敗する。 多分、Evernoteのアプリ側のサーバーにはデータが届いていても、API側のサーバーにはデータがまだ届いていないとかそんなことが原因だろう。 ただ、失敗する時も、数分・数十分・数時間、1日置いてから試すか、あるいは、過去ログタブから1日だけ選んで新たにレポート送信して古いレポートは削除すると必ず読み込まれていた。 しかし、2020-06-24のデータだけは何度試しても読み込み失敗した。 このScriptableスクリプトとショートカットは1年以上前に作ったもの。 また、ショートカットとEvernoteとの連携を一度解除し、連携し直した。 連携し直しとショートカットを書き直しの両方をしたお蔭か?何度試しても読み込み失敗していたCSVファイルが読み込めるようになった。 1年以上変更を加えていないのに突然動かなくなったので、iOS 15.1のショートカットアプリ自体のエンバグだろう。 iOS 15.1のショートカットアプリは、「クリップボードにコピー」アクションを実行しても実際にクリップボードに変更が加えられるまでに間があり、「クリップボードにコピー」アクションの後に「待機」アクションで1〜3秒待ってやる必要があるという問題がある。 Evernoteからの「ノートを取得」アクションも同じ不具合があるのでは?と、2秒待機を入れたら動作が多少安定した。 これをやってもダメなときは電源を入れ直す。 iOS 15になってショートカットの仕組み自体に大きな変更が加えられた。機能が拡張されたのは良いが、同時にショートカットの動作が不安定になってしまった。 2019年に作ったときはスクリプトをインラインで実行する機能は存在しなかった。それでScriptableからショートカットをコールバックで呼ぶ形になったが、Scriptableから呼ばれたショートカットにはメモリなどの資源が十分に割り振られないのだろう。それがiOS 15.2になってより一層配厳しくなって上手く動かなくなったのでは? そこでショートカットを直接実行する形に変更した。 最小限の変更としたので、UpImg2Gyz/wTstTknやGetFromDataJarは依然必要となる。過去のバージョンのメモ
2019-05-14
しかし、これはTaskumaからカレンダーに書き込まれたデータを読み込んでいる関係で、画像は保存できない。
↑↓
Scriptable→ショートカット→Scriptable
↓↑
Gyazo(Web)const _baseURL = "shortcuts://x-callback-url/run-shortcut";
const _SHTCT_TSKM_EVN_DL = "taskumaDL";
const _DATANAMES = ['taskName','estimatedTime','timeSpan','startTime','endTime','rate','projectName','section','GmapLink','memo','check','tag'];
let getFromShortcuts = async (input) => {
let cb = new CallbackURL(_baseURL);
cb.addParameter("name", _SHTCT_TSKM_EVN_DL);
cb.addParameter("input", input);
return await cb.open();
}
function XMLescape(target) {
if (typeof target !== 'string') {
return target;
}
return target.replace(/[&'`"<>]/g, (match) => {
return {
'&': '&',
'\'': ''',
'`': '`',
'"': '"',
'<': '<',
'>': '>'
}[match]
}).replace(/(\\n|\n)/g,' ');
}
async function myalert(title, message) {
let a = new Alert();
a.title = title;
a.message = message;
a.addAction("OK");
await a.presentAlert();
}
async function main(){
// 昨日の日付を取得
let tday = new Date();
tday.setDate(tday.getDate() - 1);
// Dateピッカー(初期値=昨日)
let dp = new DatePicker();
dp.initialDate = tday;
tday = await dp.pickDate();
// Dateピッカーで得た日付をyyyyMMdd形式に変更
let year = tday.getFullYear();
let month = tday.getMonth() + 1;
let day = tday.getDate();
tday = year + ('0' + month).slice(-2) + ('0' + day).slice(-2);
// ショートカットを呼び出し、各種データを読み込む
let result = await getFromShortcuts(tday);
let resultObj = JSON.parse(result.result);
// console.log(resultObj);
let csv = resultObj.csv;
// console.log(csv);
let html = resultObj.html;
let additionalTag = resultObj.additionalTag;
// ショートカットは、配列の要素が一つしか無かった場合、
// 配列にならず、要素そのものになってしまうので、
// その事への対処
let allImgs = [];
if (resultObj.imgs.constructor == Object) {
allImgs[0] = resultObj.imgs;
} else {
allImgs = resultObj.imgs;
}
// CSVのデータを配列tasksに格納
let tasks = [];
let a = csv.replace(/\n/g,'\\n').replace(/,,/g,',\"\",').match(/\".*?\"/g);
let CSVdatas = a === null ? [] : a;
for (let i = 0; i < CSVdatas.length; i += 12) {
let task = {};
for (let j = 0; j < 12; j++) {
task[_DATANAMES[j]] = CSVdatas[i + j].replace(/\"/g,'');
}
task.imgs = [];
tasks.push(task);
}
// htmlのMemo(H4ヘッダーが有る行)から、各タスクにある画像の撮影日時を抜出し、何番目の画像か共々、配列tasksに追加。
const _R0 = /<h4/;
let memoLines = html.split('\n').filter( e => _R0.test(e) );
const _R1 = /(\d{4}\/\d{2}\/\d{2} \d{2}:\d{2})<br \/><img src/g;
let C_imgsInMemo = 0;
memoLines.forEach((aMemo, index) => {
let imgs = [];
let matches = [];
while ((matches = _R1.exec(aMemo)) !== null) {
let img = {};
img.time = matches[1];
img.num = C_imgsInMemo++;
imgs.push(img);
}
tasks[index].imgs = imgs;
});
// メモ(「@REM」と書かれていない場合)や、画像のあるタスクのトピックを作る
let topics =[];
tasks.forEach( (task, index) => {
if ( (task.memo != '' && !/@REM/.test(task.memo)) || task.imgs.length) {
let DL = {};
DL.content = task.taskName;
DL.note = '';
// 画像の処理
task.imgs.forEach( img => {
DL.note += `${img.time}\n![](${allImgs[img.num].thumb_url})[Link](${allImgs[img.num].url})\n`
});
// メモの処理
DL.note += task.memo + '\n';
// プロジェクトネームとタグ
let tagsString = '';
task.tag.split(',').forEach( tag => {
if (tag != '') tagsString += '#' + tag + ' '
});
DL.note += (task.projectName != '' ? '#' + task.projectName + ' ' : '') + tagsString + additionalTag;
topics.push(DL);
}
});
// タイムテーブル
if (tasks.length != 0) {
let DL = {};
DL.content = 'タイムテーブル';
DL.note = '';
tasks.forEach( task => {
DL.note += `${task.startTime.slice(-5)}〜${task.endTime.slice(-5)} **${task.taskName}** (${task.timeSpan.trim()}m)\n`
});
DL.note += '\n' + additionalTag;
topics.push(DL);
}
// 地図が有れば
if ( allImgs.length != C_imgsInMemo ) {
let DL = {};
DL.content = 'Taskマップ';
DL.note = `![](${allImgs[allImgs.length - 1].thumb_url})[Link](${allImgs[allImgs.length - 1].url})\n\n${additionalTag}`;
topics.push(DL);
};
// OPML作成
if (tasks.length != 0) {
let opml = '<?xml version=\"1.0\" encoding=\"utf-8\"?><opml version=\"2.0\"><body>' + topics.map(topic => '<outline text=\"' + XMLescape(topic.content) + '\" _note=\"' + XMLescape(topic.note) + '\"/>').join('') + '</body></opml>';
console.log(opml);
Pasteboard.copy(opml);
await myalert('クリップボードにOPMLをコピーしました。','Dynalist(Web/アプリ)、DynawriteのDynalist画面で、貼り付けたい位置にペーストして下さい。');
} else {
await myalert('実行タスクが一つもありません。','実行タスクが一つ以上ある日を指定して下さい。');
}
}
main();
製作上の色々
改変
前もって指定したタグを追加できるようにした。デフォルトは「#たすくま」。
もちろん、血圧が気になった時があって、それを後で検索したときにヒットさせたい場合は、Dynalistに保存された後に血圧記録からタグ「#たすくま」を消すことで重要度を「昇格」させればよい。
Taskumaに最近発生した問題点
現状のEvernoteへのレポート送信に不具合があることを見つけた。
ページの最後に表示される地図画像が、最近、毎回同じ画像になってしまっている。
そこで、この不具合をTaskuma開発者に確認してもらったところ、
地図画像作成の接続が不安定で、接続が失敗する場合があるそう。最近地図アプリのアップデートがあったので、接続先のAppleのサービス側が不安定な状態なのかもしれないと。Apple側の内部的な変更の影響の可能性も有るかもと。調査は続行するが、Apple側の問題の場合には、Appleの対処を待つしかなさそう。アドバイスとしては、タイミングを変えて何度か試すと、上手くいくかもしれないとも。現状だと、地図画像が正しく取得できなかった場合にもそのまま送信してしまう問題があるので、その場合は、メッセージを表示するなりの修正をする予定らしい。
2020-07-04
ショートカットでCSVファイルを読み込む時に読み込み成功/失敗の判定処理する部分を、ショートカットの最近の仕様に合わせて書き直した。( taskumaDL )
2021-11-21 iOS15.1で、Scriptableから呼ばれたショートカットが何もしていないように見える不具合が頻発。「待機」アクションで安定化。
2022-01-05 iOS 15.2から上手く動かなくなったので、ショートカット内でScriptableのRun Inline Scriptアクションを使う方法に変更
そこで、待機アクションやアラートアクションをところどころ挿入したり、プライバシーをリセットしたり、電源を入れ直したりなど様々な方法でなんとか誤魔化して動かしてきたが、2021-12-25以降はどうやってもScriptableから呼ばれたショートカットがまともに動かなくなってしまった。