fix: resolve RTM API task addition parsing and signature validation errors

This commit is contained in:
creamhead 2026-06-24 15:11:28 +09:00
parent dd3ed3f216
commit 6ca1380b95
2 changed files with 66 additions and 29 deletions

32
main.js

File diff suppressed because one or more lines are too long

View file

@ -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<string, string> = {}): Promise<RtmResponse> {
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<string, string> = { ...params, method, api_key: apiKey, format: 'json', auth_token: this.settings.authToken };
const apiParams: Record<string, string> = { ...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<string, string> = { 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<string, string> = { 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<string, string> = { 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<string, string> = { 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;