Web Analytics Made Easy -
StatCounter

めモらンだム

数年後の自分が首を捻らなくて済むようにするために残す、自分で使うためのアプリの設定やスクリプト類のメモ。改変等ご自由に / 内容が古いまま、間違ったままもあるので注意 / 書いている途中や、途中で放置もあり / 対象の仕様変更で動かなくなったもの多々。特に、WorkFlowyを対象として作ったものは全滅。

TaskumaのレポートをEvernoteから読み込みDynalistに画像付きで保存

2020-07-04

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

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

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

ただ、失敗する時も、数分・数十分・数時間置いてから試すと必ず読み込まれていた。

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

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

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

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

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つ


処理の最後にOPMLクリップボードにコピーされるので、それをDynalist(Web/アプリ)、Dyanwriteのアウトライン上の挿入したい位置にペーストする。

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

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

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




Taskumaに最近発生した問題点

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

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

直ったらしい。




Dynalistに最近発生した問題点

2019-09-04: DynalistのドキュメントへのOPMLの直接貼り付けが高い確率で失敗するようになった




開発上の色々

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




改変

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

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

例えば次のような場合

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

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

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


追記

Gyazoが無料版の場合はあまりセンシティブな画像はアップロードしない方が良い。
そういった場合は、
sorashima.hatenablog.com
を参照。