feat(indbakke): en adresse, andre programmer kan skubbe en opgave til
Der var to veje ind i forvejen, og ingen af dem passer til "gem den her, mens jeg står med telefonen i hånden". API-tokenet giver adgang til alt og skal have en JSON-krop skrevet rigtigt; mailadressen virker først, når nogen har sat en mailserver op, og går gennem en indbakke, der kan være minutter om det. Det, der manglede, var en URL, man kan smide en linje tekst efter — fra en genvej på telefonen, et script, en tjeneste, der kan kalde en URL. Kroppen læses på tre måder, fordi de tre er dem, man møder: JSON fra en tjeneste, et formularfelt fra en webhook-formular, og ren tekst fra en genvej, der bare sender det, man har markeret. Første linje bliver til opgaven, resten til noten under den. Linjen læses med quick-add-parseren, så "Ring til Anders i morgen p1 #Firma" betyder det samme her som i feltet øverst i fladen — dét er hele pointen med at have én parser: den vej, teksten kom ind ad, ændrer ikke, hvad den betyder. Sit eget token frem for at låne mailens. De to er den samme slags hemmelighed, men de slipper ud hver for sig: en URL, der har ligget i en genvej på en telefon, der blev væk, skal kunne skiftes uden at mailadressen — som står i andres adressebøger — skifter med. Og ikke et API-token. Et token herfra kan én ting: lave en opgave i den persons indbakke. Det er hele forskellen på en hemmelighed, man kan lægge i et script på en anden maskine, og en, man ikke kan. Kun POST. En GET ville være nemmere at kalde, og det er præcis problemet: forudindlæsning i en browser, en linkkontrol i en chat og en historik, der bliver besøgt igen, ville alle lave opgaver, ingen har bedt om. Sin egen kvote frem for postens, så en travl integration ikke kan lukke for den indgående post. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
0764fbd566
commit
d063425cae
@@ -418,6 +418,11 @@ export const api = {
|
||||
getMailAddress: () => get('/mail-address'),
|
||||
rotateMailAddress: () => post('/mail-address/rotate'),
|
||||
|
||||
// Krogen ind i indbakken. Selve skubbet sker ikke herfra — det er et andet
|
||||
// program, der gør det — så der er kun adressen at hente og at skifte.
|
||||
getHookURL: () => get('/hook-url'),
|
||||
rotateHookURL: () => post('/hook-url/rotate'),
|
||||
|
||||
getGmail: () => get('/gmail'),
|
||||
setGmail: (data) => put('/gmail', data),
|
||||
disconnectGmail: () => del('/gmail'),
|
||||
|
||||
@@ -746,6 +746,17 @@ export const da = {
|
||||
'Der er ingen mailserver sat op på denne server, så adressen herunder kan ikke modtage noget endnu. Sæt VERDANDE_SMTP_HOST og de øvrige SMTP-indstillinger, og få mailserveren til at aflevere post til todo+* videre til /inbound/mail.',
|
||||
'int.yourAddress': 'Din adresse',
|
||||
'int.newAddress': 'Ny adresse',
|
||||
// Krogen ind i indbakken: en URL, et andet program kan skubbe en opgave til.
|
||||
'int.hookToTask': 'Skub fra andre programmer',
|
||||
'int.hookHint':
|
||||
'En adresse, andre programmer kan sende en opgave til — en genvej på telefonen, et script, en tjeneste, der kan kalde en URL. Linjen læses som i feltet øverst i fladen, så “i morgen”, “p1” og “#Projekt” betyder det samme her.',
|
||||
'int.yourHook': 'Din adresse',
|
||||
'int.hookHow':
|
||||
'Send en POST med teksten som krop — ren tekst, JSON med et {text}-felt, eller et formularfelt. Første linje bliver opgaven, resten står under den. Adressen er en nøgle: den, der har den, kan lægge opgaver i din indbakke og ikke andet.',
|
||||
'int.hookExample': 'Sådan ser det ud',
|
||||
'int.newHook': 'Ny adresse',
|
||||
'int.newHookQuestion':
|
||||
'Vil du have en ny adresse? Den gamle holder op med at virke med det samme, og alt, der bruger den, skal rettes.',
|
||||
'int.caldav': 'CalDAV',
|
||||
'int.caldavHint':
|
||||
'Tovejs, i modsætning til feedet ovenfor: Apple Påmindelser og Thunderbird kan også skrive tilbage.',
|
||||
|
||||
@@ -743,6 +743,17 @@ export const en = {
|
||||
'No mail server is configured on this server, so the address below cannot receive anything yet. Set VERDANDE_SMTP_HOST and the other SMTP settings, and have the mail server deliver post for todo+* to /inbound/mail.',
|
||||
'int.yourAddress': 'Your address',
|
||||
'int.newAddress': 'New address',
|
||||
// The hook into the inbox: a URL another program can push a task to.
|
||||
'int.hookToTask': 'Push from other programs',
|
||||
'int.hookHint':
|
||||
'An address other programs can send a task to — a shortcut on your phone, a script, any service that can call a URL. The line is read the way the box at the top of the app reads it, so “tomorrow”, “p1” and “#Project” mean the same here.',
|
||||
'int.yourHook': 'Your address',
|
||||
'int.hookHow':
|
||||
'Send a POST with the text as the body — plain text, JSON with a {text} field, or a form field. The first line becomes the task and the rest sits underneath it. The address is a key: whoever has it can put tasks in your inbox, and nothing else.',
|
||||
'int.hookExample': 'What it looks like',
|
||||
'int.newHook': 'New address',
|
||||
'int.newHookQuestion':
|
||||
'Do you want a new address? The old one stops working immediately, and anything using it has to be changed.',
|
||||
'int.caldav': 'CalDAV',
|
||||
'int.caldavHint':
|
||||
'Two-way, unlike the feed above: Apple Reminders and Thunderbird can write back as well.',
|
||||
|
||||
@@ -289,6 +289,9 @@
|
||||
|
||||
let feedURL = $state('');
|
||||
let mailAddress = $state('');
|
||||
// Krogen ind i indbakken: den URL, en genvej på telefonen eller et script kan
|
||||
// skubbe en linje tekst til.
|
||||
let hookURL = $state('');
|
||||
/** Null until loaded, so the warning does not flash before the answer is in. */
|
||||
let mailConfigured = $state(null);
|
||||
|
||||
@@ -301,6 +304,10 @@
|
||||
mailConfigured = r.configured;
|
||||
})
|
||||
.catch(() => {});
|
||||
api
|
||||
.getHookURL()
|
||||
.then((r) => (hookURL = r.url))
|
||||
.catch(() => {});
|
||||
});
|
||||
|
||||
async function rotateFeed() {
|
||||
@@ -323,6 +330,15 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function rotateHook() {
|
||||
if (!confirm(t('int.newHookQuestion'))) return;
|
||||
try {
|
||||
hookURL = (await api.rotateHookURL()).url;
|
||||
} catch (e) {
|
||||
app.toast(humanMessage(e));
|
||||
}
|
||||
}
|
||||
|
||||
async function copy(text) {
|
||||
try {
|
||||
await navigator.clipboard.writeText(text);
|
||||
@@ -674,6 +690,38 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="panel">
|
||||
<header>
|
||||
<h2>{t('int.hookToTask')}</h2>
|
||||
<p class="hint">
|
||||
{t('int.hookHint')}
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<div class="field">
|
||||
<label for="hook">{t('int.yourHook')}</label>
|
||||
<input id="hook" class="mono" value={hookURL} readonly />
|
||||
<p class="hint">
|
||||
{t('int.hookHow')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Vist som den kommando, den er. Den, der skal sætte det op, skal kunne
|
||||
prøve det af ét sted, før de bygger genvejen på telefonen — og se, at
|
||||
kroppen bare er den linje, opgaven skal hedde. -->
|
||||
<div class="field">
|
||||
<label for="hookcurl">{t('int.hookExample')}</label>
|
||||
<textarea id="hookcurl" class="mono" rows="3" readonly
|
||||
>{`curl -X POST ${hookURL} \\\n -H "Content-Type: text/plain" \\\n -d "Ring til Anders i morgen p1 #Firma"`}</textarea
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<button class="secondary" onclick={() => copy(hookURL)}>{t('int.copy')}</button>
|
||||
<button class="danger" onclick={rotateHook}>{t('int.newHook')}</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="panel">
|
||||
<header>
|
||||
<h2>{t('int.caldav')}</h2>
|
||||
|
||||
Reference in New Issue
Block a user