From 6ca1380b9517d89ea0e9ba88c1c2e47939a6f9dc Mon Sep 17 00:00:00 2001 From: creamhead Date: Wed, 24 Jun 2026 15:11:28 +0900 Subject: [PATCH] fix: resolve RTM API task addition parsing and signature validation errors --- main.js | 32 +++++++++++++-------------- src/main.ts | 63 ++++++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 66 insertions(+), 29 deletions(-) diff --git a/main.js b/main.js index 892d363..4ff14f7 100644 --- a/main.js +++ b/main.js @@ -3,24 +3,24 @@ THIS IS A GENERATED/BUNDLED FILE BY ESBUILD if you want to view the source, please visit the github repository of this plugin */ -var Q=Object.defineProperty;var at=Object.getOwnPropertyDescriptor;var ot=Object.getOwnPropertyNames;var ct=Object.prototype.hasOwnProperty;var lt=(C,g)=>{for(var s in g)Q(C,s,{get:g[s],enumerable:!0})},mt=(C,g,s,d)=>{if(g&&typeof g=="object"||typeof g=="function")for(let e of ot(g))!ct.call(C,e)&&e!==s&&Q(C,e,{get:()=>g[e],enumerable:!(d=at(g,e))||d.enumerable});return C};var dt=C=>mt(Q({},"__esModule",{value:!0}),C);var gt={};lt(gt,{default:()=>q});module.exports=dt(gt);var u=require("obsidian");function K(C){function g(t,k){return t<>>32-k}function s(t,k){let b=t&1073741824,T=k&1073741824,S=t&2147483648,D=k&2147483648,F=(t&1073741823)+(k&1073741823);return b&T?F^2147483648^S^D:b|T?F&1073741824?F^3221225472^S^D:F^1073741824^S^D:F^S^D}function d(t,k,b){return t&k|~t&b}function e(t,k,b){return t&b|k&~b}function m(t,k,b){return t^k^b}function c(t,k,b){return k^(t|~b)}function h(t,k,b,T,S,D,F){return t=s(t,s(s(d(k,b,T),S),F)),s(g(t,D),k)}function f(t,k,b,T,S,D,F){return t=s(t,s(s(e(k,b,T),S),F)),s(g(t,D),k)}function w(t,k,b,T,S,D,F){return t=s(t,s(s(m(k,b,T),S),F)),s(g(t,D),k)}function p(t,k,b,T,S,D,F){return t=s(t,s(s(c(k,b,T),S),F)),s(g(t,D),k)}function y(t){var it,rt;let k,b=t.length,T=b+8,D=((T-T%64)/64+1)*16,F=new Array(D).fill(0),G=0,P=0;for(;P>>29,F}function A(t){let k="",b="",T,S;for(S=0;S<=3;S++)T=t>>>S*8&255,b="0"+T.toString(16),k=k+b.slice(b.length-2);return k}function E(t){t=t.replace(/\r\n/g,` -`);let k="";for(let b=0;b127&&T<2048?(k+=String.fromCharCode(T>>6|192),k+=String.fromCharCode(T&63|128)):(k+=String.fromCharCode(T>>12|224),k+=String.fromCharCode(T>>6&63|128),k+=String.fromCharCode(T&63|128))}return k}function o(t,k){var b;return(b=t[k])!=null?b:0}let x=7,L=12,R=17,$=22,v=5,B=9,N=14,j=20,_=4,M=11,V=16,I=23,O=6,W=10,U=15,H=21;C=E(C);let l=y(C),tt,et,st,nt,n=1732584193,i=4023233417,r=2562383102,a=271733878;for(let t=0;t{this.checkAuth()&&await this.fetchAndProcessTasks("status:incomplete",!1,e=>{this.insertTasksToEditor(s,e)})}}),this.addCommand({id:"select-rtm-tasks",name:"Select and import tasks",editorCallback:async(s,d)=>{this.checkAuth()&&await this.fetchAndProcessTasks("status:incomplete",!0,e=>{this.insertTasksToEditor(s,e)})}}),this.addCommand({id:"import-rtm-tasks-custom",name:"Download tasks (Custom Filter)",editorCallback:async(s,d)=>{this.checkAuth()&&new J(this.app,e=>{this.fetchAndProcessTasks(e,!0,m=>{this.insertTasksToEditor(s,m)})}).open()}}),this.addCommand({id:"create-note-from-rtm-task",name:"Create note from RTM task",callback:async()=>{this.checkAuth()&&await this.fetchAndProcessTasks("status:incomplete",!0,s=>{this.createNotesFromTasks(s)})}}),this.addCommand({id:"add-rtm-task",name:"Add cursor line to RTM",editorCallback:async(s,d)=>{this.checkAuth()&&await this.addTaskFromEditor(s,d)}}),this.addCommand({id:"complete-rtm-task",name:"Complete task at cursor",editorCallback:async(s,d)=>{this.checkAuth()&&await this.completeTaskInEditor(s)}})}checkAuth(){return!this.settings.apiKey||!this.settings.sharedSecret?(new u.Notice("Please set your API Key and Secret in settings."),!1):this.settings.authToken?!0:(new u.Notice("Please authenticate with RTM from settings."),!1)}async fetchListsMap(){var d;let s={};try{let e=await this.callRtmApi("rtm.lists.getList");if((d=e.rsp.lists)!=null&&d.list){let m=Array.isArray(e.rsp.lists.list)?e.rsp.lists.list:[e.rsp.lists.list];for(let c of m)c!=null&&c.id&&(c!=null&&c.name)&&(s[c.id]=c.name)}}catch(e){console.error("List fetch error:",e)}return s}async fetchAndProcessTasks(s,d,e){var m,c,h,f;new u.Notice("Fetching tasks...");try{let w=await this.fetchListsMap(),p=await this.callRtmApi("rtm.tasks.getList",{filter:s,notes:"1"});if(!((m=p.rsp.tasks)!=null&&m.list)){new u.Notice("No tasks found.");return}let y=[],A=Array.isArray(p.rsp.tasks.list)?p.rsp.tasks.list:[p.rsp.tasks.list];for(let E of A){if(!E.taskseries)continue;let o=Array.isArray(E.taskseries)?E.taskseries:[E.taskseries];for(let x of o){let L=x.name,R=Array.isArray(x.task)?x.task[0]:x.task,$=E.id;!$&&x.list_id&&($=x.list_id),$||($="MISSING");let v="";R.due&&R.due!==""&&(v=`\u{1F4C5} ${R.due.split("T")[0]}`);let B="";R.start&&R.start!==""&&(B=`\u{1F6EB} ${R.start.split("T")[0]}`);let N="";switch(R.priority){case"1":N="\u{1F53A}";break;case"2":N="\u{1F53C}";break;case"3":N="\u{1F53D}";break}let j=(c=w[$])!=null?c:"",_=[];(h=x.tags)!=null&&h.tag&&(Array.isArray(x.tags.tag)?x.tags.tag:[x.tags.tag]).forEach(I=>_.push(I));let M=[];(f=x.notes)!=null&&f.note&&(Array.isArray(x.notes.note)?x.notes.note:[x.notes.note]).forEach(I=>{var W;let O=(W=I.$t)!=null?W:String(I);O&&M.push(O)}),y.push({name:L,due:v,start:B,priority:N,listName:j,tags:_,notes:M,rtmId:{list:$,series:x.id,task:R.id},rawPriority:R.priority,rawDue:R.due})}}d?new z(this.app,y,E=>{e(E)}).open():e(y)}catch(w){console.error(w),new u.Notice("Fetch error.")}}insertTasksToEditor(s,d){if(d.length===0){new u.Notice("No tasks selected.");return}let e="";for(let m of d){let c="";m.listName&&(c=` #${m.listName.replace(/[\s,.]+/g,"_")}`);let h=m.tags.map(y=>` #${y}`).join(""),f=`[\u{1F42E}](rtm:${m.rtmId.list}:${m.rtmId.series}:${m.rtmId.task})`,w=m.priority?` ${m.priority}`:"",p=m.due?` ${m.due}`:"";if(e+=`- [ ] ${f} ${m.name}${w}${p}${c}${h} -`,this.settings.importWithNotesAndLink){let y=`https://www.rememberthemilk.com/app/#all/${m.rtmId.task}`;e+=` - RTM Link: ${y} -`,m.notes&&m.notes.length>0&&m.notes.forEach(A=>{A.split(` -`).forEach(o=>{o.trim()!==""&&(e+=` - Note: ${o} -`)})})}}s.replaceSelection(e),new u.Notice(`${d.length} tasks inserted.`)}async createNotesFromTasks(s){if(s.length===0){new u.Notice("No tasks selected.");return}let d=0;for(let e of s){let m=e.name.replace(/[\\/:*?"<>|]/g,"-"),c=this.settings.noteCreationFolder.trim();c.endsWith("/")&&(c=c.slice(0,-1)),c.startsWith("/")&&(c=c.slice(1)),c&&await this.ensureFolderExists(c);let h=c?`${c}/${m}.md`:`${m}.md`;this.app.vault.getAbstractFileByPath(h)&&(h=c?`${c}/${m}-${Date.now()}.md`:`${m}-${Date.now()}.md`);let f=e.tags.map(y=>`#${y}`).join(" "),w=e.listName?`#${e.listName.replace(/[\s,.]+/g,"_")}`:"",p=`# ${e.name} +var Q=Object.defineProperty;var ot=Object.getOwnPropertyDescriptor;var at=Object.getOwnPropertyNames;var ct=Object.prototype.hasOwnProperty;var lt=($,k)=>{for(var s in k)Q($,s,{get:k[s],enumerable:!0})},mt=($,k,s,d)=>{if(k&&typeof k=="object"||typeof k=="function")for(let e of at(k))!ct.call($,e)&&e!==s&&Q($,e,{get:()=>k[e],enumerable:!(d=ot(k,e))||d.enumerable});return $};var dt=$=>mt(Q({},"__esModule",{value:!0}),$);var gt={};lt(gt,{default:()=>q});module.exports=dt(gt);var h=require("obsidian");function V($){function k(t,f){return t<>>32-f}function s(t,f){let y=t&1073741824,T=f&1073741824,R=t&2147483648,I=f&2147483648,C=(t&1073741823)+(f&1073741823);return y&T?C^2147483648^R^I:y|T?C&1073741824?C^3221225472^R^I:C^1073741824^R^I:C^R^I}function d(t,f,y){return t&f|~t&y}function e(t,f,y){return t&y|f&~y}function m(t,f,y){return t^f^y}function c(t,f,y){return f^(t|~y)}function w(t,f,y,T,R,I,C){return t=s(t,s(s(d(f,y,T),R),C)),s(k(t,I),f)}function g(t,f,y,T,R,I,C){return t=s(t,s(s(e(f,y,T),R),C)),s(k(t,I),f)}function p(t,f,y,T,R,I,C){return t=s(t,s(s(m(f,y,T),R),C)),s(k(t,I),f)}function u(t,f,y,T,R,I,C){return t=s(t,s(s(c(f,y,T),R),C)),s(k(t,I),f)}function A(t){var it,rt;let f,y=t.length,T=y+8,I=((T-T%64)/64+1)*16,C=new Array(I).fill(0),G=0,v=0;for(;v>>29,C}function x(t){let f="",y="",T,R;for(R=0;R<=3;R++)T=t>>>R*8&255,y="0"+T.toString(16),f=f+y.slice(y.length-2);return f}function E(t){t=t.replace(/\r\n/g,` +`);let f="";for(let y=0;y127&&T<2048?(f+=String.fromCharCode(T>>6|192),f+=String.fromCharCode(T&63|128)):(f+=String.fromCharCode(T>>12|224),f+=String.fromCharCode(T>>6&63|128),f+=String.fromCharCode(T&63|128))}return f}function a(t,f){var y;return(y=t[f])!=null?y:0}let b=7,D=12,F=17,S=22,P=5,L=9,N=14,W=20,_=4,M=11,U=16,B=23,O=6,j=10,K=15,H=21;$=E($);let l=A($),tt,et,st,nt,n=1732584193,i=4023233417,r=2562383102,o=271733878;for(let t=0;t{this.checkAuth()&&await this.fetchAndProcessTasks("status:incomplete",!1,e=>{this.insertTasksToEditor(s,e)})}}),this.addCommand({id:"select-rtm-tasks",name:"Select and import tasks",editorCallback:async(s,d)=>{this.checkAuth()&&await this.fetchAndProcessTasks("status:incomplete",!0,e=>{this.insertTasksToEditor(s,e)})}}),this.addCommand({id:"import-rtm-tasks-custom",name:"Download tasks (Custom Filter)",editorCallback:async(s,d)=>{this.checkAuth()&&new J(this.app,e=>{this.fetchAndProcessTasks(e,!0,m=>{this.insertTasksToEditor(s,m)})}).open()}}),this.addCommand({id:"create-note-from-rtm-task",name:"Create note from RTM task",callback:async()=>{this.checkAuth()&&await this.fetchAndProcessTasks("status:incomplete",!0,s=>{this.createNotesFromTasks(s)})}}),this.addCommand({id:"add-rtm-task",name:"Add cursor line to RTM",editorCallback:async(s,d)=>{this.checkAuth()&&await this.addTaskFromEditor(s,d)}}),this.addCommand({id:"complete-rtm-task",name:"Complete task at cursor",editorCallback:async(s,d)=>{this.checkAuth()&&await this.completeTaskInEditor(s)}})}checkAuth(){return!this.settings.apiKey||!this.settings.sharedSecret?(new h.Notice("Please set your API Key and Secret in settings."),!1):this.settings.authToken?!0:(new h.Notice("Please authenticate with RTM from settings."),!1)}async fetchListsMap(){var d;let s={};try{let e=await this.callRtmApi("rtm.lists.getList");if((d=e.rsp.lists)!=null&&d.list){let m=Array.isArray(e.rsp.lists.list)?e.rsp.lists.list:[e.rsp.lists.list];for(let c of m)c!=null&&c.id&&(c!=null&&c.name)&&(s[c.id]=c.name)}}catch(e){console.error("List fetch error:",e)}return s}async fetchAndProcessTasks(s,d,e){var m,c,w,g;new h.Notice("Fetching tasks...");try{let p=await this.fetchListsMap(),u=await this.callRtmApi("rtm.tasks.getList",{filter:s,notes:"1"});if(!((m=u.rsp.tasks)!=null&&m.list)){new h.Notice("No tasks found.");return}let A=[],x=Array.isArray(u.rsp.tasks.list)?u.rsp.tasks.list:[u.rsp.tasks.list];for(let E of x){if(!E.taskseries)continue;let a=Array.isArray(E.taskseries)?E.taskseries:[E.taskseries];for(let b of a){let D=b.name,F=Array.isArray(b.task)?b.task[0]:b.task,S=E.id;!S&&b.list_id&&(S=b.list_id),S||(S="MISSING");let P="";F.due&&F.due!==""&&(P=`\u{1F4C5} ${F.due.split("T")[0]}`);let L="";F.start&&F.start!==""&&(L=`\u{1F6EB} ${F.start.split("T")[0]}`);let N="";switch(F.priority){case"1":N="\u{1F53A}";break;case"2":N="\u{1F53C}";break;case"3":N="\u{1F53D}";break}let W=(c=p[S])!=null?c:"",_=[];(w=b.tags)!=null&&w.tag&&(Array.isArray(b.tags.tag)?b.tags.tag:[b.tags.tag]).forEach(B=>_.push(B));let M=[];(g=b.notes)!=null&&g.note&&(Array.isArray(b.notes.note)?b.notes.note:[b.notes.note]).forEach(B=>{var j;let O=(j=B.$t)!=null?j:String(B);O&&M.push(O)}),A.push({name:D,due:P,start:L,priority:N,listName:W,tags:_,notes:M,rtmId:{list:S,series:b.id,task:F.id},rawPriority:F.priority,rawDue:F.due})}}d?new z(this.app,A,E=>{e(E)}).open():e(A)}catch(p){console.error(p),new h.Notice("Fetch error.")}}insertTasksToEditor(s,d){if(d.length===0){new h.Notice("No tasks selected.");return}let e="";for(let m of d){let c="";m.listName&&(c=` #${m.listName.replace(/[\s,.]+/g,"_")}`);let w=m.tags.map(A=>` #${A}`).join(""),g=`[\u{1F42E}](rtm:${m.rtmId.list}:${m.rtmId.series}:${m.rtmId.task})`,p=m.priority?` ${m.priority}`:"",u=m.due?` ${m.due}`:"";if(e+=`- [ ] ${g} ${m.name}${p}${u}${c}${w} +`,this.settings.importWithNotesAndLink){let A=`https://www.rememberthemilk.com/app/#all/${m.rtmId.task}`;e+=` - RTM Link: ${A} +`,m.notes&&m.notes.length>0&&m.notes.forEach(x=>{x.split(` +`).forEach(a=>{a.trim()!==""&&(e+=` - Note: ${a} +`)})})}}s.replaceSelection(e),new h.Notice(`${d.length} tasks inserted.`)}async createNotesFromTasks(s){if(s.length===0){new h.Notice("No tasks selected.");return}let d=0;for(let e of s){let m=e.name.replace(/[\\/:*?"<>|]/g,"-"),c=this.settings.noteCreationFolder.trim();c.endsWith("/")&&(c=c.slice(0,-1)),c.startsWith("/")&&(c=c.slice(1)),c&&await this.ensureFolderExists(c);let w=c?`${c}/${m}.md`:`${m}.md`;this.app.vault.getAbstractFileByPath(w)&&(w=c?`${c}/${m}-${Date.now()}.md`:`${m}-${Date.now()}.md`);let g=e.tags.map(A=>`#${A}`).join(" "),p=e.listName?`#${e.listName.replace(/[\s,.]+/g,"_")}`:"",u=`# ${e.name} -`;p+=`- **RTM Link**: [\u{1F42E} Open Task](rtm:${e.rtmId.list}:${e.rtmId.series}:${e.rtmId.task}) -`,e.due&&(p+=`- **Due**: ${e.due} -`),e.start&&(p+=`- **Start**: ${e.start} -`),e.priority&&(p+=`- **Priority**: ${e.priority} -`),(w||f)&&(p+=`- **Tags**: ${w} ${f} -`),p+=` +`;u+=`- **RTM Link**: [\u{1F42E} Open Task](rtm:${e.rtmId.list}:${e.rtmId.series}:${e.rtmId.task}) +`,e.due&&(u+=`- **Due**: ${e.due} +`),e.start&&(u+=`- **Start**: ${e.start} +`),e.priority&&(u+=`- **Priority**: ${e.priority} +`),(p||g)&&(u+=`- **Tags**: ${p} ${g} +`),u+=` --- -`,e.notes.length>0?(p+=`## Notes +`,e.notes.length>0?(u+=`## Notes -`,e.notes.forEach(y=>{p+=`${y} +`,e.notes.forEach(A=>{u+=`${A} -`})):p+=`(No notes in RTM) -`;try{await this.app.vault.create(h,p),d++}catch(y){console.error(`Failed to create file: ${h}`,y),new u.Notice(`Failed to create note for "${e.name}"`)}}new u.Notice(`${d} notes created.`)}async addTaskFromEditor(s,d){var f;let e=s.getCursor(),c=s.getLine(e.line).replace(/^[-*] \[[ x]\] /,"").replace(/^[-*] /,"").trim();if(!c){new u.Notice("Task name is empty.");return}new u.Notice(`Adding: ${c}`);let h=d.file?d.file.basename:"";try{let w=await this.getTimeline(),y=(f=(await this.callRtmApi("rtm.tasks.add",{timeline:w,name:c,parse:"1"})).rsp.tasks)==null?void 0:f.list,A=Array.isArray(y)?y[0]:y,E=A.id,o=A.taskseries,x=Array.isArray(o)?o[0]:o,L=x.id,$=(Array.isArray(x.task)?x.task[0]:x.task).id;if(this.settings.defaultDueForNewTask==="today")try{await this.callRtmApi("rtm.tasks.setDueDate",{timeline:w,list_id:E,taskseries_id:L,task_id:$,due:"today",parse:"1"})}catch(N){console.error("Failed to set due date",N)}if(h)try{await this.callRtmApi("rtm.tasks.notes.add",{timeline:w,list_id:E,taskseries_id:L,task_id:$,note_title:"Obsidian Link",note_text:`From Obsidian Note: [[${h}]]`})}catch(N){console.error("Failed to add note to RTM task",N)}let B=`- [ ] ${`[\u{1F42E}](rtm:${E}:${L}:${$})`} ${c}`;s.setLine(e.line,B),new u.Notice("Added to RTM!")}catch(w){console.error("Add Task Error:",w),new u.Notice("Add error.")}}async completeTaskInEditor(s){let d=s.getCursor(),e=s.getLine(d.line),m=/\(rtm:([\w\d]+):([\w\d]+):([\w\d]+)\)/,c=e.match(m);if(!c){new u.Notice("Error: No RTM Link found.");return}let h=c[1],f=c[2],w=c[3];if(!h||h==="MISSING"){new u.Notice("Error: List ID invalid.");return}new u.Notice("Completing task...");try{let p=await this.getTimeline();await this.callRtmApi("rtm.tasks.complete",{timeline:p,list_id:h,taskseries_id:f,task_id:w});let y=e.replace("- [ ]","- [x]");s.setLine(d.line,y),new u.Notice("Task completed!")}catch(p){console.error("Completion Error:",p),new u.Notice("Completion error.")}}async getTimeline(){var e;if(this.timeline)return this.timeline;let d=(e=(await this.callRtmApi("rtm.timelines.create")).rsp.timeline)!=null?e:"";return this.timeline=d,d}async callRtmApi(s,d={}){var A;let e=this.settings.apiKey,m=this.settings.sharedSecret;if(!e||!m)throw new Error("API Key/Secret missing");let c={...d,method:s,api_key:e,format:"json",auth_token:this.settings.authToken},h=Object.keys(c).sort(),f=m;for(let E of h)f+=E+c[E];c.api_sig=K(f);let w=`${Z}?${new URLSearchParams(c).toString()}`,y=(await(0,u.requestUrl)({url:w})).json;if(y.rsp.stat!=="ok")throw console.error("API Error:",y),new Error(`API Error: ${(A=y.rsp.err)==null?void 0:A.msg}`);return y}async ensureFolderExists(s){if(!s)return;let d=s.split("/"),e="";for(let m of d)if(m&&(e=e===""?m:`${e}/${m}`,!this.app.vault.getAbstractFileByPath(e)))try{await this.app.vault.adapter.mkdir(e)}catch(c){console.error(`Failed to create folder: ${e}`,c)}}async loadSettings(){this.settings=Object.assign({},ht,await this.loadData())}async saveSettings(){await this.saveData(this.settings)}},z=class extends u.Modal{constructor(g,s,d){super(g),this.tasks=s,this.selected=new Array(s.length).fill(!1),this.onSubmit=d}onOpen(){let{contentEl:g}=this;new u.Setting(g).setName(`Select Tasks (${this.tasks.length})`).setHeading();let s=g.createDiv({cls:"rtm-task-list"});this.tasks.forEach((c,h)=>{var y;let f=s.createDiv({cls:"rtm-task-item"}),w=f.createEl("input",{type:"checkbox"});w.checked=(y=this.selected[h])!=null?y:!1,w.onchange=A=>{this.selected[h]=A.target.checked};let p=f.createSpan({cls:"rtm-task-label"});p.createEl("b",{text:c.name}),c.listName&&p.createEl("small",{text:` [${c.listName}]`,cls:"rtm-task-list-name"}),c.due&&p.createEl("small",{text:` ${c.due}`,cls:"rtm-task-due"}),c.notes&&c.notes.length>0&&p.createEl("small",{text:" \u{1F4DD}"})});let d=g.createDiv({cls:"rtm-btn-row"}),e=d.createEl("button",{text:"Select All"});e.onclick=()=>{let c=this.selected.every(Boolean);this.selected.fill(!c),s.querySelectorAll('input[type="checkbox"]').forEach(f=>f.checked=!c),e.textContent=c?"Select All":"Deselect All"};let m=d.createEl("button",{text:"Process Selected"});m.className="mod-cta",m.onclick=()=>{let c=this.tasks.filter((h,f)=>this.selected[f]);this.onSubmit(c),this.close()}}onClose(){this.contentEl.empty()}},J=class extends u.Modal{constructor(g,s){super(g),this.onSubmit=s}onOpen(){let{contentEl:g}=this;g.createEl("h3",{text:"Filter RTM Tasks"}),new u.Setting(g).setName("Search Query").setDesc("e.g. list:Inbox, due:today").addText(s=>s.setValue("status:incomplete").onChange(d=>{}).inputEl.addEventListener("keydown",d=>{d.key==="Enter"&&(this.onSubmit(s.getValue()),this.close())})),new u.Setting(g).addButton(s=>s.setButtonText("Search").setCta().onClick(()=>{let d=g.querySelector("input");d&&(this.onSubmit(d.value),this.close())}))}onClose(){this.contentEl.empty()}},X=class extends u.PluginSettingTab{constructor(g,s){super(g,s),this.plugin=s}display(){let{containerEl:g}=this;g.empty(),new u.Setting(g).setName("Connection").setHeading(),g.createEl("p",{text:`Version: ${this.plugin.manifest.version} (Build: 6/13/2026, 6:53:51 PM)`,cls:"setting-item-description rtm-version-info"}),new u.Setting(g).setName("API Key").addText(e=>e.setValue(this.plugin.settings.apiKey).onChange(async m=>{this.plugin.settings.apiKey=m,await this.plugin.saveSettings()})),new u.Setting(g).setName("Shared Secret").addText(e=>e.setValue(this.plugin.settings.sharedSecret).onChange(async m=>{this.plugin.settings.sharedSecret=m,await this.plugin.saveSettings()}));let d=new u.Setting(g).setName("Auth");this.plugin.settings.authToken&&d.setDesc("\u2705 Authorized"),d.addButton(e=>e.setButtonText("Start Auth").onClick(async()=>await this.startAuthProcess())),new u.Setting(g).setName("Default Due Date").setDesc("Setting for new tasks created from editor").addDropdown(e=>e.addOption("none","No Due Date").addOption("today","Today").setValue(this.plugin.settings.defaultDueForNewTask).onChange(async m=>{this.plugin.settings.defaultDueForNewTask=m,await this.plugin.saveSettings()})),new u.Setting(g).setName("Note Creation Folder").setDesc("Folder to create notes from RTM tasks.").addDropdown(e=>{e.addOption("","/ (Vault Root)"),this.app.vault.getAllLoadedFiles().filter(c=>c instanceof u.TFolder).forEach(c=>{c.path!=="/"&&e.addOption(c.path,c.path)}),e.setValue(this.plugin.settings.noteCreationFolder),e.onChange(async c=>{this.plugin.settings.noteCreationFolder=c,await this.plugin.saveSettings()})}),new u.Setting(g).setName("Import with Notes and RTM Link").setDesc("When inserting tasks to the editor, also include RTM task notes and links as sub-items.").addToggle(e=>e.setValue(this.plugin.settings.importWithNotesAndLink).onChange(async m=>{this.plugin.settings.importWithNotesAndLink=m,await this.plugin.saveSettings()}))}async startAuthProcess(){var y;let g=this.plugin,s=g.settings.apiKey,d=g.settings.sharedSecret;if(!s||!d){new u.Notice("Enter keys first");return}let e={method:"rtm.auth.getFrob",api_key:s,format:"json"},m=d;Object.keys(e).sort().forEach(A=>m+=A+e[A]),e.api_sig=K(m);let h=(y=(await(0,u.requestUrl)({url:`${Z}?${new URLSearchParams(e).toString()}`})).json.rsp.frob)!=null?y:"",f={api_key:s,perms:"delete",frob:h},w=d;Object.keys(f).sort().forEach(A=>w+=A+f[A]);let p=K(w);window.open(`${ut}?api_key=${s}&perms=delete&frob=${h}&api_sig=${p}`),new Y(this.app,h,g,()=>{this.display()}).open()}},Y=class extends u.Modal{constructor(g,s,d,e){super(g),this.frob=s,this.plugin=d,this.onSuccess=e}onOpen(){let{contentEl:g}=this;new u.Setting(g).addButton(s=>s.setButtonText("Finish Auth").setCta().onClick(async()=>{let d=this.plugin.settings.apiKey,e=this.plugin.settings.sharedSecret,m={method:"rtm.auth.getToken",api_key:d,format:"json",frob:this.frob},c=e;Object.keys(m).sort().forEach(h=>c+=h+m[h]),m.api_sig=K(c);try{let f=(await(0,u.requestUrl)({url:`${Z}?${new URLSearchParams(m).toString()}`})).json;f.rsp.auth?(this.plugin.settings.authToken=f.rsp.auth.token,await this.plugin.saveSettings(),new u.Notice("Success!"),this.onSuccess(),this.close()):new u.Notice("Failed.")}catch(h){new u.Notice("Error.")}}))}onClose(){this.contentEl.empty()}}; +`})):u+=`(No notes in RTM) +`;try{await this.app.vault.create(w,u),d++}catch(A){console.error(`Failed to create file: ${w}`,A),new h.Notice(`Failed to create note for "${e.name}"`)}}new h.Notice(`${d} notes created.`)}async addTaskFromEditor(s,d){let e=s.getCursor(),c=s.getLine(e.line).replace(/^[-*] \[[ x]\] /,"").replace(/^[-*] /,"").trim();if(!c){new h.Notice("Task name is empty.");return}new h.Notice(`Adding: ${c}`);let w=d.file?d.file.basename:"";try{let g=await this.getTimeline(),p=await this.callRtmApi("rtm.tasks.add",{timeline:g,name:c,parse:"1"}),u=p.rsp.list;if(!u)throw console.error("API Response does not contain 'list':",p),new Error("API Response missing 'list'. Check console for details.");let A=Array.isArray(u)?u[0]:u;if(!A.id)throw console.error("API Response list missing 'id':",p),new Error("API Response list missing 'id'. Check console for details.");let x=A.id,E=A.taskseries;if(!E)throw console.error("API Response does not contain 'taskseries':",p),new Error("API Response missing 'taskseries'. Check console for details.");let a=Array.isArray(E)?E[0]:E;if(!a.id)throw console.error("API Response series missing 'id':",p),new Error("API Response series missing 'id'. Check console for details.");let b=a.id,D=a.task;if(!D)throw console.error("API Response does not contain 'task':",p),new Error("API Response missing 'task'. Check console for details.");let F=Array.isArray(D)?D[0]:D;if(!F.id)throw console.error("API Response taskObj missing 'id':",p),new Error("API Response taskObj missing 'id'. Check console for details.");let S=F.id;if(this.settings.defaultDueForNewTask==="today")try{await this.callRtmApi("rtm.tasks.setDueDate",{timeline:g,list_id:x,taskseries_id:b,task_id:S,due:"today",parse:"1"})}catch(N){console.error("Failed to set due date",N)}if(w)try{await this.callRtmApi("rtm.tasks.notes.add",{timeline:g,list_id:x,taskseries_id:b,task_id:S,note_title:"Obsidian Link",note_text:`From Obsidian Note: [[${w}]]`})}catch(N){console.error("Failed to add note to RTM task",N)}let L=`- [ ] ${`[\u{1F42E}](rtm:${x}:${b}:${S})`} ${c}`;s.setLine(e.line,L),new h.Notice("Added to RTM!")}catch(g){console.error("Add Task Error:",g),new h.Notice("Add error.")}}async completeTaskInEditor(s){let d=s.getCursor(),e=s.getLine(d.line),m=/\(rtm:([\w\d]+):([\w\d]+):([\w\d]+)\)/,c=e.match(m);if(!c){new h.Notice("Error: No RTM Link found.");return}let w=c[1],g=c[2],p=c[3];if(!w||w==="MISSING"){new h.Notice("Error: List ID invalid.");return}new h.Notice("Completing task...");try{let u=await this.getTimeline();await this.callRtmApi("rtm.tasks.complete",{timeline:u,list_id:w,taskseries_id:g,task_id:p});let A=e.replace("- [ ]","- [x]");s.setLine(d.line,A),new h.Notice("Task completed!")}catch(u){console.error("Completion Error:",u),new h.Notice("Completion error.")}}async getTimeline(){var e;if(this.timeline)return this.timeline;let d=(e=(await this.callRtmApi("rtm.timelines.create")).rsp.timeline)!=null?e:"";return this.timeline=d,d}async callRtmApi(s,d={}){var E,a,b,D,F;let e=(E=this.settings.apiKey)==null?void 0:E.trim(),m=(a=this.settings.sharedSecret)==null?void 0:a.trim();if(!e||!m)throw new Error("API Key/Secret missing");let c={...d,method:s,api_key:e,format:"json",auth_token:(D=(b=this.settings.authToken)==null?void 0:b.trim())!=null?D:""},w=Object.keys(c).sort(),g=m;for(let S of w)g+=S+c[S];c.api_sig=V(g);let p=Object.keys(c).map(S=>{var P;return`${encodeURIComponent(S)}=${encodeURIComponent((P=c[S])!=null?P:"")}`}).join("&"),u=`${Z}?${p}`,x=(await(0,h.requestUrl)({url:u})).json;if(x.rsp.stat!=="ok")throw console.error("API Error:",x),new Error(`API Error: ${(F=x.rsp.err)==null?void 0:F.msg}`);return x}async ensureFolderExists(s){if(!s)return;let d=s.split("/"),e="";for(let m of d)if(m&&(e=e===""?m:`${e}/${m}`,!this.app.vault.getAbstractFileByPath(e)))try{await this.app.vault.adapter.mkdir(e)}catch(c){console.error(`Failed to create folder: ${e}`,c)}}async loadSettings(){this.settings=Object.assign({},ht,await this.loadData())}async saveSettings(){await this.saveData(this.settings)}},z=class extends h.Modal{constructor(k,s,d){super(k),this.tasks=s,this.selected=new Array(s.length).fill(!1),this.onSubmit=d}onOpen(){let{contentEl:k}=this;new h.Setting(k).setName(`Select Tasks (${this.tasks.length})`).setHeading();let s=k.createDiv({cls:"rtm-task-list"});this.tasks.forEach((c,w)=>{var A;let g=s.createDiv({cls:"rtm-task-item"}),p=g.createEl("input",{type:"checkbox"});p.checked=(A=this.selected[w])!=null?A:!1,p.onchange=x=>{this.selected[w]=x.target.checked};let u=g.createSpan({cls:"rtm-task-label"});u.createEl("b",{text:c.name}),c.listName&&u.createEl("small",{text:` [${c.listName}]`,cls:"rtm-task-list-name"}),c.due&&u.createEl("small",{text:` ${c.due}`,cls:"rtm-task-due"}),c.notes&&c.notes.length>0&&u.createEl("small",{text:" \u{1F4DD}"})});let d=k.createDiv({cls:"rtm-btn-row"}),e=d.createEl("button",{text:"Select All"});e.onclick=()=>{let c=this.selected.every(Boolean);this.selected.fill(!c),s.querySelectorAll('input[type="checkbox"]').forEach(g=>g.checked=!c),e.textContent=c?"Select All":"Deselect All"};let m=d.createEl("button",{text:"Process Selected"});m.className="mod-cta",m.onclick=()=>{let c=this.tasks.filter((w,g)=>this.selected[g]);this.onSubmit(c),this.close()}}onClose(){this.contentEl.empty()}},J=class extends h.Modal{constructor(k,s){super(k),this.onSubmit=s}onOpen(){let{contentEl:k}=this;k.createEl("h3",{text:"Filter RTM Tasks"}),new h.Setting(k).setName("Search Query").setDesc("e.g. list:Inbox, due:today").addText(s=>s.setValue("status:incomplete").onChange(d=>{}).inputEl.addEventListener("keydown",d=>{d.key==="Enter"&&(this.onSubmit(s.getValue()),this.close())})),new h.Setting(k).addButton(s=>s.setButtonText("Search").setCta().onClick(()=>{let d=k.querySelector("input");d&&(this.onSubmit(d.value),this.close())}))}onClose(){this.contentEl.empty()}},X=class extends h.PluginSettingTab{constructor(k,s){super(k,s),this.plugin=s}display(){let{containerEl:k}=this;k.empty(),new h.Setting(k).setName("Connection").setHeading(),k.createEl("p",{text:`Version: ${this.plugin.manifest.version} (Build: 6/24/2026, 3:02:09 PM)`,cls:"setting-item-description rtm-version-info"}),new h.Setting(k).setName("API Key").addText(e=>e.setValue(this.plugin.settings.apiKey).onChange(async m=>{this.plugin.settings.apiKey=m,await this.plugin.saveSettings()})),new h.Setting(k).setName("Shared Secret").addText(e=>e.setValue(this.plugin.settings.sharedSecret).onChange(async m=>{this.plugin.settings.sharedSecret=m,await this.plugin.saveSettings()}));let d=new h.Setting(k).setName("Auth");this.plugin.settings.authToken&&d.setDesc("\u2705 Authorized"),d.addButton(e=>e.setButtonText("Start Auth").onClick(async()=>await this.startAuthProcess())),new h.Setting(k).setName("Default Due Date").setDesc("Setting for new tasks created from editor").addDropdown(e=>e.addOption("none","No Due Date").addOption("today","Today").setValue(this.plugin.settings.defaultDueForNewTask).onChange(async m=>{this.plugin.settings.defaultDueForNewTask=m,await this.plugin.saveSettings()})),new h.Setting(k).setName("Note Creation Folder").setDesc("Folder to create notes from RTM tasks.").addDropdown(e=>{e.addOption("","/ (Vault Root)"),this.app.vault.getAllLoadedFiles().filter(c=>c instanceof h.TFolder).forEach(c=>{c.path!=="/"&&e.addOption(c.path,c.path)}),e.setValue(this.plugin.settings.noteCreationFolder),e.onChange(async c=>{this.plugin.settings.noteCreationFolder=c,await this.plugin.saveSettings()})}),new h.Setting(k).setName("Import with Notes and RTM Link").setDesc("When inserting tasks to the editor, also include RTM task notes and links as sub-items.").addToggle(e=>e.setValue(this.plugin.settings.importWithNotesAndLink).onChange(async m=>{this.plugin.settings.importWithNotesAndLink=m,await this.plugin.saveSettings()}))}async startAuthProcess(){var x,E,a;let k=this.plugin,s=(x=k.settings.apiKey)==null?void 0:x.trim(),d=(E=k.settings.sharedSecret)==null?void 0:E.trim();if(!s||!d){new h.Notice("Enter keys first");return}let e={method:"rtm.auth.getFrob",api_key:s,format:"json"},m=d;Object.keys(e).sort().forEach(b=>m+=b+e[b]),e.api_sig=V(m);let c=Object.keys(e).map(b=>{var D;return`${encodeURIComponent(b)}=${encodeURIComponent((D=e[b])!=null?D:"")}`}).join("&"),g=(a=(await(0,h.requestUrl)({url:`${Z}?${c}`})).json.rsp.frob)!=null?a:"",p={api_key:s,perms:"delete",frob:g},u=d;Object.keys(p).sort().forEach(b=>u+=b+p[b]);let A=V(u);window.open(`${ut}?api_key=${s}&perms=delete&frob=${g}&api_sig=${A}`),new Y(this.app,g,k,()=>{this.display()}).open()}},Y=class extends h.Modal{constructor(k,s,d,e){super(k),this.frob=s,this.plugin=d,this.onSuccess=e}onOpen(){let{contentEl:k}=this;new h.Setting(k).addButton(s=>s.setButtonText("Finish Auth").setCta().onClick(async()=>{var w,g;let d=(w=this.plugin.settings.apiKey)==null?void 0:w.trim(),e=(g=this.plugin.settings.sharedSecret)==null?void 0:g.trim(),m={method:"rtm.auth.getToken",api_key:d!=null?d:"",format:"json",frob:this.frob},c=e;Object.keys(m).sort().forEach(p=>c+=p+m[p]),m.api_sig=V(c);try{let p=Object.keys(m).map(x=>{var E;return`${encodeURIComponent(x)}=${encodeURIComponent((E=m[x])!=null?E:"")}`}).join("&"),A=(await(0,h.requestUrl)({url:`${Z}?${p}`})).json;A.rsp.auth?(this.plugin.settings.authToken=A.rsp.auth.token,await this.plugin.saveSettings(),new h.Notice("Success!"),this.onSuccess(),this.close()):new h.Notice("Failed.")}catch(p){new h.Notice("Error.")}}))}onClose(){this.contentEl.empty()}}; diff --git a/src/main.ts b/src/main.ts index 3c20cd5..96e7edf 100644 --- a/src/main.ts +++ b/src/main.ts @@ -31,6 +31,7 @@ interface RtmResponse { stat: string; err?: { msg: string }; lists?: { list: RtmList | RtmList[] }; + list?: RtmTaskList | RtmTaskList[]; tasks?: { list: RtmTaskList | RtmTaskList[] }; timeline?: string; frob?: string; @@ -371,13 +372,40 @@ export default class RtmPlugin extends Plugin { const timeline = await this.getTimeline(); const res = await this.callRtmApi('rtm.tasks.add', { timeline: timeline, name: taskName, parse: '1' }); - const rawList = res.rsp.tasks?.list; + const rawList = res.rsp.list; + if (!rawList) { + console.error("API Response does not contain 'list':", res); + throw new Error("API Response missing 'list'. Check console for details."); + } const list: RtmTaskList = (Array.isArray(rawList) ? rawList[0] : rawList) as RtmTaskList; + if (!list.id) { + console.error("API Response list missing 'id':", res); + throw new Error("API Response list missing 'id'. Check console for details."); + } const listId = list.id; + const seriesRaw = list.taskseries; + if (!seriesRaw) { + console.error("API Response does not contain 'taskseries':", res); + throw new Error("API Response missing 'taskseries'. Check console for details."); + } const series: RtmTaskSeries = (Array.isArray(seriesRaw) ? seriesRaw[0] : seriesRaw) as RtmTaskSeries; + if (!series.id) { + console.error("API Response series missing 'id':", res); + throw new Error("API Response series missing 'id'. Check console for details."); + } const taskSeriesId = series.id; - const taskObj: RtmTask = (Array.isArray(series.task) ? series.task[0] : series.task) as RtmTask; + + const taskRaw = series.task; + if (!taskRaw) { + console.error("API Response does not contain 'task':", res); + throw new Error("API Response missing 'task'. Check console for details."); + } + const taskObj: RtmTask = (Array.isArray(taskRaw) ? taskRaw[0] : taskRaw) as RtmTask; + if (!taskObj.id) { + console.error("API Response taskObj missing 'id':", res); + throw new Error("API Response taskObj missing 'id'. Check console for details."); + } const taskId = taskObj.id; // 現在の設定に従ってデフォルト期日を設定 @@ -458,17 +486,20 @@ export default class RtmPlugin extends Plugin { } async callRtmApi(method: string, params: Record = {}): Promise { - const apiKey = this.settings.apiKey; - const sharedSecret = this.settings.sharedSecret; + const apiKey = this.settings.apiKey?.trim(); + const sharedSecret = this.settings.sharedSecret?.trim(); if(!apiKey || !sharedSecret) throw new Error("API Key/Secret missing"); - const apiParams: Record = { ...params, method, api_key: apiKey, format: 'json', auth_token: this.settings.authToken }; + const apiParams: Record = { ...params, method, api_key: apiKey, format: 'json', auth_token: this.settings.authToken?.trim() ?? "" }; const keys = Object.keys(apiParams).sort(); let sigString = sharedSecret; for (const key of keys) sigString += key + apiParams[key]; apiParams['api_sig'] = md5(sigString); - const url = `${RTM_REST_URL}?${new URLSearchParams(apiParams).toString()}`; + const queryString = Object.keys(apiParams) + .map(k => `${encodeURIComponent(k)}=${encodeURIComponent(apiParams[k] ?? "")}`) + .join('&'); + const url = `${RTM_REST_URL}?${queryString}`; const res = await requestUrl({ url: url }); const json = res.json as RtmResponse; if(json.rsp.stat !== 'ok') { @@ -651,14 +682,17 @@ class RtmSettingTab extends PluginSettingTab { } async startAuthProcess() { const rtm = this.plugin; - const apiKey = rtm.settings.apiKey; - const sharedSecret = rtm.settings.sharedSecret; + const apiKey = rtm.settings.apiKey?.trim(); + const sharedSecret = rtm.settings.sharedSecret?.trim(); if (!apiKey || !sharedSecret) { new Notice('Enter keys first'); return; } const frobParams: Record = { method: 'rtm.auth.getFrob', api_key: apiKey, format: 'json' }; let sigString = sharedSecret; Object.keys(frobParams).sort().forEach(k => sigString += k + frobParams[k]); frobParams['api_sig'] = md5(sigString); - const frobRes = await requestUrl({ url: `${RTM_REST_URL}?${new URLSearchParams(frobParams).toString()}` }); + const frobQueryString = Object.keys(frobParams) + .map(k => `${encodeURIComponent(k)}=${encodeURIComponent(frobParams[k] ?? "")}`) + .join('&'); + const frobRes = await requestUrl({ url: `${RTM_REST_URL}?${frobQueryString}` }); const frob = (frobRes.json as RtmResponse).rsp.frob ?? ''; const authParams: Record = { api_key: apiKey, perms: 'delete', frob }; let authSigString = sharedSecret; @@ -675,14 +709,17 @@ class TokenModal extends Modal { onOpen() { const {contentEl} = this; new Setting(contentEl).addButton(btn => btn.setButtonText('Finish Auth').setCta().onClick(async () => { - const apiKey = this.plugin.settings.apiKey; - const sharedSecret = this.plugin.settings.sharedSecret; - const tokenParams: Record = { method: 'rtm.auth.getToken', api_key: apiKey, format: 'json', frob: this.frob }; + const apiKey = this.plugin.settings.apiKey?.trim(); + const sharedSecret = this.plugin.settings.sharedSecret?.trim(); + const tokenParams: Record = { method: 'rtm.auth.getToken', api_key: apiKey ?? "", format: 'json', frob: this.frob }; let sig = sharedSecret; Object.keys(tokenParams).sort().forEach(k => sig += k + tokenParams[k]); tokenParams['api_sig'] = md5(sig); try { - const res = await requestUrl({ url: `${RTM_REST_URL}?${new URLSearchParams(tokenParams).toString()}` }); + const tokenQueryString = Object.keys(tokenParams) + .map(k => `${encodeURIComponent(k)}=${encodeURIComponent(tokenParams[k] ?? "")}`) + .join('&'); + const res = await requestUrl({ url: `${RTM_REST_URL}?${tokenQueryString}` }); const json = res.json as RtmResponse; if(json.rsp.auth) { this.plugin.settings.authToken = json.rsp.auth.token;