Web Analytics Made Easy -
StatCounter

めモらンだム・ヤード

自分用のアプリ設定やスクリプト類の備忘録・覚え書き(Memorandum) / 作った物のライセンスはCC BY-NC-SAで。 / 内容が古いまま、間違ったまま、書いている途中、途中で放置など、手入れはあまり行き届いていない庭 / 対象の仕様変更で動かなくなったもの多々。WorkFlowy向けは全滅したので削除 / 製作物のインストール及び使用は各自の責任で。使用によって、利用者および第三者に損害が発生したとしても、当方は一切責任を負いかねます

TaskumaのレポートをEvernoteから読み込みDynalistに画像付きで保存(2022-01-05)

2022-01-05

画像はGyazoに保存してそれを参照する。

使うもの

  • taskumaDL_Inline
    https://www.icloud.com/shortcuts/eff11da7dc8140269358be90aade5c0f
     
    TaskumaのレポートをEvernoteから読み込み、OPML形式にするショートカット本体。

  • Scriptableアプリ
    Scriptable
    Developer Tools
    0円
    ユニバーサルアプリ: ○
    Evernoteに保存されたTaskumaのレポートはデータ量が多く、ショートカットだけで処理すると遅すぎて実用的ではない。そこで、データをJavascriptで処理するためにこのアプリを使った。

  • Data Jarアプリ
    GyazoにアップロードするのにGyazoAPIを使う。Gyazo APIにアクセスするために必要になるアクセス・トークンを保管しておくために使うショートカット用保管庫アプリ。
    Gyazoに送信するショートカットはこれ一つだけではないので、トークンを一カ所にまとめて管理しやすくするためにData Jarアプリを使った。

  • GetFromDataJar
    Data Jarアプリに保存したGyazoのアクセス・トークンを読み込むために使うショートカット・モジュール。

  • UpImg2Gyz/wTstTkn
    Gyazoに画像をアップロードするショートカット・モジュール。


使用例

f:id:sorashima:20220108223423j:plain:w311
起動すると、いつのレポートを取得するか聞いてくる。
初期値は前日の日付。日付を選んで「完了」。

f:id:sorashima:20220108223502j:plain:w311
最後にOPMLクリップボードにコピーされる。

Dynalistに貼り付ける前のアウトライン
この位置で貼り付けると

Dynalistに貼り付けた後のアウトライン
こんな感じに貼り付く


上のデータは、以下のEvernote上のレポートを読んだ結果。

読み込まれたEvernote上のtaskumaのレポート


注意点:レポート送信直後だと高い確率で読み込みに失敗する

Evernoteにレボートを送信して、1時間後くらいにこのショートカットを実行しても読み込めないことがある。
たといEvernoteのアプリでそのノートを開けても。
Evernoteアプリが使っているであろう非公開APIとショートカットが使っているAPIが異なり、後者の方は反映までのタイムラグがかなりあるのだろう。

最低でも3時間ほど間を開けると読み込みの確実性が上がる。半日後だとまず確実に読み込めるはず。
ただ、Evernoteのサーバーの状態次第なので10分後でも読み込めることもある。

これに関してはEvernoteがFreeプランだろうとPersonalプランだろうと違いはなさそう。

1日経っても読めない場合はそれ以上待っても読み込める可能性は低い。Evernoteのノートブックから読み込めなかったレポートのノートを削除して、Taskumaの過去ログタブから該当日1日分のログをレポート送信しなおしてから読み込み直す。




過去のバージョンのメモ

2019-05-14

ライフログをDynalistに一元化するにあたり、これまでは「TaskumaのログをDynawrite経由でDynalistに送る Workflow」及びそれに続くメモで作った仕組みを使用していた。
しかし、これはTaskumaからカレンダーに書き込まれたデータを読み込んでいる関係で、画像は保存できない。

どうしてもDynalistで画像を見返したい要件ができたので、Evernoteから読み込んで上手くやれないかと、しばらく試行錯誤していた。

その途中で、DynalistにではなくScrapboxに保存するタイプの試作物(完成度は低い)が前回できた。

そして更にそれを改変して、Dynalistに保存するタイプがようやく何とか動いた。

Scrapboxに保存するタイプは、データ処理を高速化するためにショートカット内で無理やりJavascriptを走らせていた。しかし、無理やり走らせている関係でエラー表示が全く無い。そのため、ちょっとした修正や改変がとても大変だった。

そこで今回は、エラー表示やコンソール出力のあるJavascript実行アプリScriptableをメインにしてみた。

ただし、Evernoteからの読み込みなど厄介な処理は、Scriptableのスクリプトからショートカットアプリを呼んでやらせている。

      Evernote(Web)
        ↑↓
Scriptable→ショートカット→Scriptable
        ↓↑
       Gyazo(Web)


ちなみに自分のレポート項目は

となっている。


Scriptableで動かすJavascriptは、

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 {
      '&': '&amp;',
      '\'': '&apos;',
      '`': '&#x60;',
      '"': '&quot;',
      '<': '&lt;',
      '>': '&gt;'
    }[match]
  }).replace(/(\\n|\n)/g,'&#10;');
}


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();


このScriptableスクリプトと共に使うショートカットアプリのショートカットは、次の3つ





製作上の色々

  • エラー表示が欲しくてScriptableを使うことを選択したが、最初のうちこそエラー表示していたのに、途中から無言で止まってしまうようになってしまった。シンタックスハイライトもスクリプトの途中からしなくなるし、スクリプトが少し長くなるとダメなのか?これでは、ショートカット内でJavascriptを動かすのとあまり変わらない。そんな竹槍戦法では不具合はまず解決しない。
  • RunJavascriptというアプリも試してみたが、コンソール出力が無さそうだった。また、スクリプトは他のアプリやファイルから受け取らなければならず、内部に保管できないような感じだった。また、日本語が化けてしまうので、データはショートカット側でユニコードエスケープしてからRunJavascriptに渡し、RunJavascriptから戻ってきたデータもショートカット側でユニコードアンエスケープしてやらないといけない等厄介だ ったので使用を諦めた。
  • Scriptableから直接Gyazoに画像をアップロードすることもできないことはないが、非同期処理のコールバック地獄を避ける為と、上記理由でバグの位置が見つけ辛いので、ややこしい処理は最初にショートカットに全部やらせることにした。。
  • DynalistのAPIによるinbox投稿も同じ理由で避けた。それに、APIでinboxの位置を指定することはまだできないからinboxに保存はまだ使い道がない。(iPhoneだけを使う場合の話。iPadならPCサイトを表示でinboxを指定することは可能。ただし、inbox投稿は、一分間に投稿できる数に制限があったりと、処理が大変そう。)




改変

改変1
前もって指定したタグを追加できるようにした。デフォルトは「#たすくま」。

キーボードからわざわざ入力する項目と、このスクリプト+ショートカットで機械的に追加する項目では重要度が異なることが多いと思う。

例えば次のような場合

  • 毎朝晩に血圧をTaskumaに記録していて、そのプロジェクトは「健康」。←最終的にDynalistに「#健康」というタグが付いて保存される。
  • 健康面について気になったことをDynalistに直接書いた時も、タグ「#健康」を付けて書く。

だったとすると、健康面について気になった時の記録を読み返したい時に「#健康」で検索すると、Taskumaから来た日々の血圧記録までズラズラと表示されてしまい困ったことになる。

そこで今回の改変で、「#健康 -#たすくま」で検索することで、たすくまから機械的に追記したものを排除できるようにした。
もちろん、血圧が気になった時があって、それを後で検索したときにヒットさせたい場合は、Dynalistに保存された後に血圧記録からタグ「#たすくま」を消すことで重要度を「昇格」させればよい。




Taskumaに最近発生した問題点

現状のEvernoteへのレポート送信に不具合があることを見つけた。
ページの最後に表示される地図画像が、最近、毎回同じ画像になってしまっている。
そこで、この不具合をTaskuma開発者に確認してもらったところ、

  • 地図画像作成の接続が不安定で、接続が失敗する場合があるそう。
  • 最近地図アプリのアップデートがあったので、接続先のAppleのサービス側が不安定な状態なのかもしれないと。
  • Apple側の内部的な変更の影響の可能性も有るかもと。
  • 調査は続行するが、Apple側の問題の場合には、Appleの対処を待つしかなさそう。
  • アドバイスとしては、タイミングを変えて何度か試すと、上手くいくかもしれないとも。
  • 現状だと、地図画像が正しく取得できなかった場合にもそのまま送信してしまう問題があるので、その場合は、メッセージを表示するなりの修正をする予定らしい。

直ったらしい。




2020-07-04

Evernoteからのcsvファイル読み込みは、Scriptableのスクリプトではなくショートカットで行っている。

Evernoteにレポート送信直後だと、たとえEvernoteアプリでcsvファイルを開けられる状態でも、時々ショートカットによる読み込みが失敗する。

多分、Evernoteのアプリ側のサーバーにはデータが届いていても、API側のサーバーにはデータがまだ届いていないとかそんなことが原因だろう。

ただ、失敗する時も、数分・数十分・数時間、1日置いてから試すか、あるいは、過去ログタブから1日だけ選んで新たにレポート送信して古いレポートは削除すると必ず読み込まれていた。

しかし、2020-06-24のデータだけは何度試しても読み込み失敗した。

このScriptableスクリプトとショートカットは1年以上前に作ったもの。
ショートカットでCSVファイルを読み込む時に読み込み成功/失敗の判定処理する部分を、ショートカットの最近の仕様に合わせて書き直した。( taskumaDL )

また、ショートカットとEvernoteとの連携を一度解除し、連携し直した。

連携し直しとショートカットを書き直しの両方をしたお蔭か?何度試しても読み込み失敗していたCSVファイルが読み込めるようになった。





2021-11-21 iOS15.1で、Scriptableから呼ばれたショートカットが何もしていないように見える不具合が頻発。「待機」アクションで安定化。

1年以上変更を加えていないのに突然動かなくなったので、iOS 15.1のショートカットアプリ自体のエンバグだろう。

iOS 15.1のショートカットアプリは、「クリップボードにコピー」アクションを実行しても実際にクリップボードに変更が加えられるまでに間があり、「クリップボードにコピー」アクションの後に「待機」アクションで1〜3秒待ってやる必要があるという問題がある。

Evernoteからの「ノートを取得」アクションも同じ不具合があるのでは?と、2秒待機を入れたら動作が多少安定した。

f:id:sorashima:20211121115052j:plain

これをやってもダメなときは電源を入れ直す。


2022-01-05 iOS 15.2から上手く動かなくなったので、ショートカット内でScriptableのRun Inline Scriptアクションを使う方法に変更

iOS 15になってショートカットの仕組み自体に大きな変更が加えられた。機能が拡張されたのは良いが、同時にショートカットの動作が不安定になってしまった。
そこで、待機アクションやアラートアクションをところどころ挿入したり、プライバシーをリセットしたり、電源を入れ直したりなど様々な方法でなんとか誤魔化して動かしてきたが、2021-12-25以降はどうやってもScriptableから呼ばれたショートカットがまともに動かなくなってしまった。

2019年に作ったときはスクリプトをインラインで実行する機能は存在しなかった。それでScriptableからショートカットをコールバックで呼ぶ形になったが、Scriptableから呼ばれたショートカットにはメモリなどの資源が十分に割り振られないのだろう。それがiOS 15.2になってより一層配厳しくなって上手く動かなくなったのでは?

そこでショートカットを直接実行する形に変更した。

最小限の変更としたので、UpImg2Gyz/wTstTknやGetFromDataJarは依然必要となる。

©︎ 2022 Sorashima