1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

thunderbird から todoist にタスクを登録する

Posted at

#Thunderbirdに「Todoistのタスクを登録する」ボタンを追加する

Firefox、Thunderbirdのアドオンを作ったことが無い私が挑戦してみた。
環境はWindows10、Thunderbird 52.1.1で作成した。

完成したのはこんなアドオンです

▼追加したボタン
1.png

▼登録確認画面
1.png

##そもそも、なぜこんなアドオンを開発しようとしたか

###■公式アドオンがない

公式Todoistが、Thunderbird対応を止めてしまったので、Thunderbird 52.1.1で受け取ったメールを簡単にタスク化するには、メール本文などをコピペするしかなかった。(代わりになるアドオンなどは無さそう?)

###■タスク管理方法

下記のような方法でタスクを管理しようとしたが、性に合わなくて続かなかった。

  • Lightningアドオンを使う方法
  • 受信箱をタスクリストにする方法
  • 既読にしない方法
  • Thunderbirdのタグをつける方法など

唯一Todoistは続いている。
あまり有料ソフトは使わないのだが、Todoistは年会費を払っている。

Todoistは、いつまでに、優先度付けなどを活用し、タスクを管理するサービス
私は、進捗管理をしない前提(コメントの内容やサブタスクの消化具合で判断している)

##アドオンの仕様を検討した

  • メールを選択して、ヘッド表示部(Fromなどのヘッダ情報が表示される部分)にある返信ボタンの横に、ボタンを追加する
  • タスクのタイトルは、メールの件名or本文の選択したテキスト
  • タスクに自動的にコメントを追加する
  • コメントの内容は、メールのヘッダ情報の一部(日付、件名、Fromなど)とメール本文
  • 追加したボタンを押したら登録確認画面が表示されて、登録ボタンを押したら、APIで登録される

これが実現できれば、とりあえず使えそうだなということで、仕様はなるべく簡単にした。

###■あきらめたこと

・各自のTodoistの「APIトークン」を設定する画面を作ること
 → 設定エディタに直接登録することにした
・タスクを追加するプロジェクトを選択すること
・期限を自由に選択すること
・タスクに書かれたリンクをクリックすると、メールが開くこと(できないよね?)
・関連メールのやり取り(スレッド)のタスクは、サブタスクにすること
 ヘッダ情報をコメントに埋め込めば、できるかなぁ

などなど。

インボックスで、今日の期限のタスクに表示されるので、ブラウザ上で修正することにした。

##やったこと

###(1) 開発環境準備

・thunderebirdの開発用プロファイルを準備する

私はポータブル版だったので、普通とやり方が少し違うのかも?

C:\ThunderbirdPortable\App\Thunderbird\thunderbird.exe -p
だったか(覚えていない)
C:\ThunderbirdPortable\App\Thunderbird\thunderbird.exe -ProfileManager
として起動後、プロファイルを新規作成した。

・開発用希望batの作成

batファイルを作って、中身を
C:\ThunderbirdPortable\App\Thunderbird\thunderbird.exe -p "開発用"
とした。

・いくつかの設定を変更
このサイトを参考にして、
ツール>オプション>詳細>設定エディタ
の画面で変更した。(ここで変更することすら知らなかったので苦労した)

・作業ディレクトリの準備
C:\develop\t2t
を作成した。ここで開発する。

C:\Users\xxxx\AppData\Roaming\Thunderbird\Profiles\xxxxx.開発用\extensions
に新規で
thunderbirdToDoist@sample.com
というファイルを作成した。

ファイルの中身は、
C:\develop\t2t
とした。

###(2) 開発開始

詳しくは、先人たちのWEBと、添付したソースを見てもらいたい。
正確な内容は理解していないので。
・ソースにコメントを少しだけ入れた。(途中で飽きた)
・面倒なので、インデントもテキトウ(--;)
・デバッグログなども残したまま

※javascriptは全然わからない、firefox/thunderebird開発経験ゼロといった
 プログラム初心者なので、色々ご容赦いただきたい。
 質問などされても全く分からない。

###▼タスク登録する部分だけ少し解説(ソースのコメントで)
あと、登録数に制限があるのかわからないが、日を跨いだら同じソースでも
登録に成功したということがあった。
なにかTodoist側で問題でもあったのだろうか・・・?

apiトークンは、Todiostの設定画面から拾ってくる、
設定エディタで「extensions.t2t.setting.apikey」として登録済みとする。

//APIトークンを、設定から持ってくる
let prefs = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefBranch);
let apitoken = prefs.getCharPref("extensions.t2t.setting.apikey");


//タスクを登録する
  var url = "https://todoist.com/API/v7/sync",
      formData, response;

  formData = {
    "token": apitoken,
    "commands": JSON.stringify(
    [
      { "type"    : "item_add",
        "uuid": uuid,    //なんの文字列でも良さそう?
        "temp_id" : uuid+"a", //<<<<ここのtemp_idと、コメント(note_add)のitem_idを同じにしないと、コメントが登録されない(中身はなんの文字列でも良さそう?)
        "args": {
           "content": taskname,   //タスク名
           "date_string": "today" //タスク期限を今日にする
        }
      },
      {
        "type": "note_add", //タスクコメントを追加する
        "uuid": uuid+"c",  //なんの文字列でも良さそう?
        "temp_id": uuid+"b",    //なんの文字列でも良さそう?
        "args": {
           "content": comment,   //コメント本文
           "item_id": uuid+"a"   //item_addと同じ文字列
        }
      }
    ])
  }; 
...

あとは、XMLHttpRequestでテキトウにPOST送信した。

※先人達のサンプルをコピペしていじっただけです。感謝。
※ソースは自由に改変してもらって良いので、誰かもっと良いものに仕上げてください。お願いします。

##ソース

###taskAdd.xul

<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>

<dialog id="asdf" title="Add task to todoist"
        buttons="accept,cancel"
        buttonlabelaccept="Add Task"
        buttonaccesskeyaccept="a"
        ondialogaccept="return doOK();"
        buttonlabelcancel="Cancel"
        buttonaccesskeycancel="c"
        ondialogcancel="return doCancel();"
        onload="onLoad();"
        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">

<vbox>
<hbox>
<description value="タスク名:" /><textbox id="txttaskname" width="700px" />
</hbox>
<hbox>
<description value="コメント:" /><textbox id="txtcomment" multiline="true" height="400px" width="700px" />
</hbox>
</vbox>

<script type="application/x-javascript" src="taskAdd.js" />

</dialog>
...

###taskAdd.js

function onLoad() {
  //カーソルを先頭に
  document.getElementById("txttaskname").value = window.arguments[0].taskname;
  document.getElementById("txtcomment").value = window.arguments[0].comment;
  document.getElementById("txtcomment").selectionStart = 0;
  document.getElementById("txtcomment").selectionEnd = 0;
  document.getElementById("txtcomment").focus();

}


function doCancel()
{
  //alert("You pressed Cancel!");
  //return true;
  
  return true;
}


function doOK()
{
  //alert("You pressed OK!");
  //return true;
  
  
  //1:タスク名
  //2:uuid = fromaddress+date
  //3:コメント内容:本文
  createTask(window.arguments[0].taskname,
             window.arguments[0].uuid,
             window.arguments[0].comment);

  return true;

}




// タスク登録
function createTask(taskname, uuid, comment) {


let prefs = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefBranch);
let apitoken = prefs.getCharPref("extensions.t2t.setting.apikey");
//nsPreferences.setUnicharPref("extensions.t2t.setting.apitoken", 123);

console.log(uuid);

  var url = "https://todoist.com/API/v7/sync",
      formData, response;

  formData = {
    "token": apitoken,
    "commands": JSON.stringify(
    [
      { "type"    : "item_add",
        "uuid": uuid,
        "temp_id" : uuid+"a",
        "args": {
           "content": taskname,
           "date_string": "today"
        }
      },
      {
        "type": "note_add",
        "uuid": uuid+"c",
        "temp_id": uuid+"b",
        "args": {
           "content": comment,
           "item_id": uuid+"a"
        }
      }
    ])
  };



var xmlHttpRequest = new XMLHttpRequest();
xmlHttpRequest.onreadystatechange = function()
{

    var READYSTATE_COMPLETED = 4;
    var HTTP_STATUS_OK = 200;

    if( xmlHttpRequest.readyState == READYSTATE_COMPLETED
     && xmlHttpRequest.status == HTTP_STATUS_OK)
    {
        // レスポンスの表示
        console.log( xmlHttpRequest.responseText );
        alert("タスク登録 成功");// +  xmlHttpRequest.responseText  );
    }

    //ステータスNGの時もレスポンスが見たい
	console.log( "-------------------------" );
	console.log( xmlHttpRequest.readyState );
	console.log( xmlHttpRequest.status );
	console.log( xmlHttpRequest.responseText );
	console.log( "^^^^^^^^^^^^^^^^^^^^^^^^^" );

}

xmlHttpRequest.open( 'POST', url, false );//非同期true

// サーバに対して解析方法を指定する
xmlHttpRequest.setRequestHeader( 'Content-Type', 'application/x-www-form-urlencoded' );

// データをリクエスト ボディに含めて送信する
xmlHttpRequest.send( EncodeHTMLForm( formData ) );
/*
if (xmlHttpRequest.status===200){
console.log("OK");
}else{
console.log("NG");
}
*/

}

function EncodeHTMLForm( data )
{
    var params = [];

    for( var name in data )
    {
        var value = data[ name ];
        var param = encodeURIComponent( name ).replace( /%20/g, '+' )
            + '=' + encodeURIComponent( value ).replace( /%20/g, '+' );

        params.push( param );
    }

    return params.join( '&' );
} 
...

###taskAddConfirm.xul

<?xml version="1.0"?>
<overlay id="t2t"
        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">

<script type="application/x-javascript" src="taskAddConfirm.js" />

<toolbox id="header-view-toolbox" position="1" >
  <toolbar position="1">
    <toolbarbutton label="タスク追加"  oncommand="taskAddConfirm(this)" />
  </toolbar>
</toolbox>

</overlay>
...

###taskAddConfirm.js

function taskAddConfirm( me ) 
{

/* ログを出力する方法
    var consoleService = Components.
            classes["@mozilla.org/consoleservice;1"].
            getService( Components.interfaces.nsIConsoleService );
    consoleService.logStringMessage( 'Some messages' );
*/


var tname;    //タスク名(メール件名or選択した文字列)
var comm;     //コメント(メール本文)


//本文を選択していなければ、タスク名はメール件名。
//本文を選択していたら、タスク名は、選択文字列


//メッセージペインで選択した文字列の取得方法
//me.label = document.getElementById("messagepane").contentDocument.getSelection().toString().length;


//メッセージペインで選択した文字列の取得
//文字列を選択していたら、その文字列がタスク名となる。
//文字列を選択していなければ、メール件名が、タスク名となる。
var deltag;
deltag = document.getElementById("messagepane").contentDocument.getSelection().toString();
if (deltag.length == 0) {
  tname = document.getAnonymousElementByAttribute(document.getElementById('expandedsubjectBox'), 'anonid', 'headerValue').textContent;
} else {
  tname = deltag;
}


//タスクのコメント内容は、次の順序
//From、受信日時、件名
//空行を2つ
//メール本文(HTMLタグとCSSタグは除去するが、&amp;などは元に戻す)

let strEmail = document.getAnonymousElementByAttribute(document.getElementById('expandedfromBox'), 'headerName', 'from').getAttribute('emailAddress');
let strFrom = document.getAnonymousElementByAttribute(document.getElementById('expandedfromBox'), 'headerName', 'from').getAttribute('fullAddress');
let strSubject = document.getAnonymousElementByAttribute(document.getElementById('expandedsubjectBox'), 'anonid', 'headerValue').textContent;
let strDate = document.getElementById('dateLabel').textContent;

var buf;
buf = strFrom +"\r\n" + strDate + "\r\n" + strSubject + "\r\n\r\n";

deltag = document.getElementById("messagepane").contentDocument.body.innerHTML;
//  deltag = document.getElementById("messagepane").contentDocument.body.innerHTML;

  deltag = deltag.replace(/<style.*?>[.\s\S]*?<\/style.*?>/g,'');
  deltag = deltag.replace(/<script.*?>[.\s\S]*?<\/script.*?>/g,'');
  deltag = deltag.replace(/<("[^"]*"|'[^']*'|[^'">])*>/g,'');
  deltag = deltag.replace(/ */gm,'');
  deltag = deltag.replace(/^\r*\n+/gm,'');
  //deltag = deltag.replace(/([ \s\S]){2,}/g,'\r\n');
  //comm = unescape(deltag);

comm = buf + unescapeHTML(deltag);
//comm = buf + deltag;



var uid = new Date().getTime().toString();
var params = {taskname: tname,   //タスク名
              comment: comm,     //コメント
              uuid: uid};        //uuid

          
window.openDialog("chrome://t2t/content/taskAdd.xul","addtask",
                  "chrome,scrollbars,resizable,centerscreen,height=500,width=800", params).focus();

}


function unescapeHTML(html) {
   var htmlNode = document.createElement("div");
   htmlNode.innerHTML = html;
   if(htmlNode.innerText !== undefined)
      return htmlNode.innerText; // IE
   return htmlNode.textContent; // FF
}

...
1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?