mirror of
https://github.com/maclean-d/obsidian-limitless-lifelogs.git
synced 2026-07-22 12:20:25 +00:00
Compare commits
17 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fe357a5f08 | ||
|
|
d5fa51277c | ||
|
|
8be0273474 | ||
|
|
8a31323212 | ||
|
|
9c90890099 | ||
|
|
46f4e31f38 | ||
|
|
c2f724a629 | ||
|
|
bc3d17e8a1 | ||
|
|
e0d07875df | ||
|
|
d0ca8c2557 | ||
|
|
cdd792fa0b | ||
|
|
687c8a6e5f | ||
|
|
9cd4f3adae | ||
|
|
0cd1328fed | ||
|
|
dc0875395b | ||
|
|
fcf0dcf53c | ||
|
|
578064afbe |
12 changed files with 2480 additions and 48 deletions
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
|
|
@ -32,4 +32,4 @@ jobs:
|
|||
gh release create "$tag" \
|
||||
--title="$tag" \
|
||||
--draft \
|
||||
main.js manifest.json styles.css
|
||||
main.js manifest.json
|
||||
|
|
|
|||
2
LICENSE
2
LICENSE
|
|
@ -1,5 +1,3 @@
|
|||
Copyright (C) 2020-2025 by Dynalist Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
10
README.md
10
README.md
|
|
@ -78,10 +78,16 @@ If you encounter any issues:
|
|||
|
||||
## Star History
|
||||
|
||||
[](https://star-history.com/#Maclean-D/obsidian-limitless-lifelogs&Date)
|
||||
<a href="https://www.star-history.com/?repos=Maclean-D%2Fobsidian-limitless-lifelogs&type=date&legend=top-left">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/chart?repos=Maclean-D/obsidian-limitless-lifelogs&type=date&theme=dark&legend=top-left&sealed_token=n0qnDUpZGvp6y_j0NpoDcTvohZJ0tocsgfpskhjAPGqYY0mSleeunkuQ8dm8HcZi5aCR2D_OHiD8q1Bz4pnUHiXo48HcWKIHPHFUgls95HtHhPnjnuwr5lsOOBjrenFeB0jwJjXP3aNhafiFlspD5WjXA1VVf1XqIO6Yktr2CBHPrSLgqq0YJZqhctEe" />
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/chart?repos=Maclean-D/obsidian-limitless-lifelogs&type=date&legend=top-left&sealed_token=n0qnDUpZGvp6y_j0NpoDcTvohZJ0tocsgfpskhjAPGqYY0mSleeunkuQ8dm8HcZi5aCR2D_OHiD8q1Bz4pnUHiXo48HcWKIHPHFUgls95HtHhPnjnuwr5lsOOBjrenFeB0jwJjXP3aNhafiFlspD5WjXA1VVf1XqIO6Yktr2CBHPrSLgqq0YJZqhctEe" />
|
||||
<img alt="Star History Chart" src="https://api.star-history.com/chart?repos=Maclean-D/obsidian-limitless-lifelogs&type=date&legend=top-left&sealed_token=n0qnDUpZGvp6y_j0NpoDcTvohZJ0tocsgfpskhjAPGqYY0mSleeunkuQ8dm8HcZi5aCR2D_OHiD8q1Bz4pnUHiXo48HcWKIHPHFUgls95HtHhPnjnuwr5lsOOBjrenFeB0jwJjXP3aNhafiFlspD5WjXA1VVf1XqIO6Yktr2CBHPrSLgqq0YJZqhctEe" />
|
||||
</picture>
|
||||
</a>
|
||||
|
||||
## Contributors
|
||||
|
||||
<a href="https://github.com/Maclean-D/obsidian-limitless-lifelogs/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=Maclean-D/obsidian-limitless-lifelogs" />
|
||||
</a>
|
||||
</a>
|
||||
|
|
|
|||
0
buildaplugin.md
Normal file
0
buildaplugin.md
Normal file
Binary file not shown.
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 46 KiB |
91
main.ts
91
main.ts
|
|
@ -33,7 +33,7 @@ export default class LimitlessLifelogsPlugin extends Plugin {
|
|||
// Add command for syncing
|
||||
this.addCommand({
|
||||
id: 'sync-limitless-lifelogs',
|
||||
name: 'Sync Limitless Lifelogs',
|
||||
name: 'Sync',
|
||||
callback: async () => {
|
||||
await this.syncLifelogs();
|
||||
}
|
||||
|
|
@ -96,8 +96,8 @@ export default class LimitlessLifelogsPlugin extends Plugin {
|
|||
}
|
||||
|
||||
private async ensureFolderExists(path: string) {
|
||||
const folderExists = await this.app.vault.adapter.exists(path);
|
||||
if (!folderExists) {
|
||||
const folder = this.app.vault.getFolderByPath(path);
|
||||
if (!folder) {
|
||||
await this.app.vault.createFolder(path);
|
||||
}
|
||||
}
|
||||
|
|
@ -105,14 +105,15 @@ export default class LimitlessLifelogsPlugin extends Plugin {
|
|||
private async getLastSyncedDate(): Promise<Date | null> {
|
||||
const folderPath = normalizePath(this.settings.folderPath);
|
||||
try {
|
||||
const files = await this.app.vault.adapter.list(folderPath);
|
||||
const dates = files.files
|
||||
.map(file => file.split('/').pop()?.replace('.md', ''))
|
||||
.filter(date => date && /^\d{4}-\d{2}-\d{2}$/.test(date))
|
||||
.map(date => new Date(date as string))
|
||||
const files = this.app.vault.getFiles()
|
||||
.filter(file => file.path.startsWith(folderPath + '/'))
|
||||
.filter(file => file.path.endsWith('.md'))
|
||||
.map(file => file.basename)
|
||||
.filter(basename => /^\d{4}-\d{2}-\d{2}$/.test(basename))
|
||||
.map(basename => new Date(basename))
|
||||
.sort((a, b) => b.getTime() - a.getTime());
|
||||
|
||||
return dates.length > 0 ? dates[0] : null;
|
||||
return files.length > 0 ? files[0] : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -120,7 +121,9 @@ export default class LimitlessLifelogsPlugin extends Plugin {
|
|||
|
||||
private formatLifelogMarkdown(lifelog: any): string {
|
||||
if (lifelog.markdown) {
|
||||
return lifelog.markdown;
|
||||
// Reformat Markdown
|
||||
const reformattedMarkdown = lifelog.markdown.replaceAll('\n\n', '\n');
|
||||
return reformattedMarkdown;
|
||||
}
|
||||
|
||||
const content: string[] = [];
|
||||
|
|
@ -192,7 +195,7 @@ class LimitlessLifelogsSettingTab extends PluginSettingTab {
|
|||
containerEl.empty();
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('API Key')
|
||||
.setName('API key')
|
||||
.setDesc('Your Limitless AI API key')
|
||||
.addText(text => text
|
||||
.setPlaceholder('Enter your API key')
|
||||
|
|
@ -203,7 +206,7 @@ class LimitlessLifelogsSettingTab extends PluginSettingTab {
|
|||
}));
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Folder Path')
|
||||
.setName('Folder path')
|
||||
.setDesc('Where to store the lifelog entries')
|
||||
.addText(text => text
|
||||
.setPlaceholder('Folder path')
|
||||
|
|
@ -214,7 +217,7 @@ class LimitlessLifelogsSettingTab extends PluginSettingTab {
|
|||
}));
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Start Date')
|
||||
.setName('Start date')
|
||||
.setDesc('Default start date for initial sync (YYYY-MM-DD)')
|
||||
.addText(text => text
|
||||
.setPlaceholder('YYYY-MM-DD')
|
||||
|
|
@ -230,6 +233,8 @@ class LimitlessAPI {
|
|||
private apiKey: string;
|
||||
private baseUrl = 'https://api.limitless.ai';
|
||||
private batchSize = 10;
|
||||
private maxRetries = 5;
|
||||
private retryDelay = 1000; // 1 second
|
||||
|
||||
constructor(apiKey: string) {
|
||||
this.apiKey = apiKey;
|
||||
|
|
@ -258,20 +263,7 @@ class LimitlessAPI {
|
|||
}
|
||||
|
||||
try {
|
||||
const response = await requestUrl({
|
||||
url: `${this.baseUrl}/v1/lifelogs?${params.toString()}`,
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'X-API-Key': this.apiKey,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.json) {
|
||||
throw new Error('Invalid response format');
|
||||
}
|
||||
|
||||
const data = response.json;
|
||||
const data = await this.makeRequest(`${this.baseUrl}/v1/lifelogs`, params);
|
||||
const lifelogs = data.data?.lifelogs || [];
|
||||
allLifelogs.push(...lifelogs);
|
||||
|
||||
|
|
@ -284,4 +276,49 @@ class LimitlessAPI {
|
|||
|
||||
return allLifelogs;
|
||||
}
|
||||
|
||||
private async makeRequest(url: string, params: URLSearchParams): Promise<any> {
|
||||
let retries = 0;
|
||||
while (true) {
|
||||
try {
|
||||
const response = await requestUrl({
|
||||
url: `${url}?${params.toString()}`,
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'X-API-Key': this.apiKey,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.json) {
|
||||
throw new Error('Invalid response format');
|
||||
}
|
||||
|
||||
return response.json;
|
||||
} catch (error) {
|
||||
if (error.status === 429 && retries < this.maxRetries) {
|
||||
let delay = this.retryDelay * Math.pow(2, retries);
|
||||
const retryAfter = error.headers?.['retry-after'];
|
||||
|
||||
if (retryAfter) {
|
||||
const retryAfterSeconds = parseInt(retryAfter, 10);
|
||||
if (!isNaN(retryAfterSeconds)) {
|
||||
delay = retryAfterSeconds * 1000;
|
||||
} else {
|
||||
const retryAfterDate = new Date(retryAfter);
|
||||
const now = new Date();
|
||||
delay = retryAfterDate.getTime() - now.getTime();
|
||||
}
|
||||
}
|
||||
|
||||
new Notice(`Rate limit exceeded. Retrying in ${Math.round(delay / 1000)} seconds...`);
|
||||
await new Promise(resolve => setTimeout(resolve, delay));
|
||||
retries++;
|
||||
} else {
|
||||
console.error('Error making request:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
{
|
||||
"id": "limitless-lifelogs",
|
||||
"name": "Limitless Lifelogs",
|
||||
"version": "1.0.2",
|
||||
"version": "1.0.6",
|
||||
"minAppVersion": "0.15.0",
|
||||
"description": "Download and sync your Limitless AI lifelog entries into Obsidian markdown files.",
|
||||
"author": "Maclean Dunstan",
|
||||
"description": "Sync your Limitless AI lifelog entries",
|
||||
"author": "Maclean Dunkin",
|
||||
"authorUrl": "https://github.com/Maclean-D",
|
||||
"isDesktopOnly": false
|
||||
}
|
||||
}
|
||||
2397
package-lock.json
generated
Normal file
2397
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "obsidian-limitless-lifelogs",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.6",
|
||||
"description": "Download and sync your Limitless AI lifelog entries into Obsidian markdown files.",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
|
|
@ -9,7 +9,7 @@
|
|||
"version": "node version-bump.mjs && git add manifest.json versions.json"
|
||||
},
|
||||
"keywords": ["obsidian", "plugin", "limitless", "lifelogs"],
|
||||
"author": "Your Name",
|
||||
"author": "Maclean Dunkin",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/node": "^16.11.6",
|
||||
|
|
|
|||
0
releaseplugin.md
Normal file
0
releaseplugin.md
Normal file
|
|
@ -1,8 +0,0 @@
|
|||
/*
|
||||
|
||||
This CSS file will be included with your plugin, and
|
||||
available in the app when your plugin is enabled.
|
||||
|
||||
If your plugin does not need CSS, delete this file.
|
||||
|
||||
*/
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
{
|
||||
"1.0.0": "0.15.0"
|
||||
}
|
||||
"1.0.0": "0.15.0",
|
||||
"1.0.5": "0.15.0",
|
||||
"1.0.6": "0.15.0"
|
||||
}
|
||||
Loading…
Reference in a new issue