mirror of
https://github.com/tmlnv/obsidian-telegram-bridge.git
synced 2026-07-22 06:53:13 +00:00
prepare plugin for obsidian community registry
- rename plugin to telegram-bridge (drop 'obsidian' prefix per guidelines) - promote manifest.json + versions.json to repo root - replace settings h2/h3/h4 with Setting.setHeading() - drop plugin-name prefix from command titles, add reconnect and sign-out - wire SyncEngine polling through plugin.registerInterval - replace console.log with Notice in show-client-id - move inline usage-bar styles to CSS custom properties - gate status bar item on Platform.isDesktopApp - add first-run supabase requirement disclosure - add tag-triggered GitHub release workflow
This commit is contained in:
parent
f7330c8e29
commit
d8509e7111
11 changed files with 182 additions and 33 deletions
59
.github/workflows/release.yml
vendored
Normal file
59
.github/workflows/release.yml
vendored
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
name: Release Obsidian plugin
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "*.*.*"
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "20"
|
||||
cache: "npm"
|
||||
cache-dependency-path: plugin/package-lock.json
|
||||
|
||||
- name: Install plugin dependencies
|
||||
run: npm ci
|
||||
working-directory: plugin
|
||||
|
||||
- name: Build plugin
|
||||
run: npm run build
|
||||
working-directory: plugin
|
||||
|
||||
- name: Verify manifest version matches tag
|
||||
run: |
|
||||
TAG="${GITHUB_REF_NAME}"
|
||||
MANIFEST_VERSION="$(node -p "require('./manifest.json').version")"
|
||||
if [ "$TAG" != "$MANIFEST_VERSION" ]; then
|
||||
echo "Tag $TAG does not match manifest.json version $MANIFEST_VERSION" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Assemble release assets
|
||||
run: |
|
||||
mkdir -p release
|
||||
cp manifest.json release/manifest.json
|
||||
cp plugin/main.js release/main.js
|
||||
cp plugin/styles.css release/styles.css
|
||||
|
||||
- name: Create GitHub release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: ${{ github.ref_name }}
|
||||
name: ${{ github.ref_name }}
|
||||
draft: false
|
||||
prerelease: false
|
||||
files: |
|
||||
release/manifest.json
|
||||
release/main.js
|
||||
release/styles.css
|
||||
16
README.md
16
README.md
|
|
@ -1,4 +1,4 @@
|
|||
# Obsidian Telegram Sync
|
||||
# Telegram Bridge for Obsidian
|
||||
|
||||
Sync messages from a Telegram bot into your Obsidian vault. Messages arrive in real time, are stored in Supabase, and are pulled into your vault as Markdown notes — with support for files, forum topics, message edits, and flexible routing rules.
|
||||
|
||||
|
|
@ -144,12 +144,12 @@ Until this plugin is published to the Obsidian community registry, install it ma
|
|||
```
|
||||
2. Copy the output files into your vault's plugin folder:
|
||||
```
|
||||
<vault>/.obsidian/plugins/obsidian-telegram/
|
||||
├── main.js
|
||||
├── manifest.json
|
||||
└── styles.css (if present)
|
||||
<vault>/.obsidian/plugins/telegram-bridge/
|
||||
├── main.js (from plugin/main.js)
|
||||
├── manifest.json (from repo root)
|
||||
└── styles.css (from plugin/styles.css)
|
||||
```
|
||||
3. In Obsidian → **Settings → Community plugins**, enable **Obsidian Telegram Sync**.
|
||||
3. In Obsidian → **Settings → Community plugins**, enable **Telegram Bridge**.
|
||||
|
||||
### 3.2 Connect to Supabase
|
||||
|
||||
|
|
@ -321,7 +321,11 @@ The Telegram Bot API limits file downloads to **20 MB**. Anything larger cannot
|
|||
|
||||
```
|
||||
obsidian-telegram/
|
||||
├── manifest.json Obsidian plugin manifest (release source of truth)
|
||||
├── versions.json Plugin → min Obsidian app version map
|
||||
├── plugin/ Obsidian plugin (TypeScript + esbuild)
|
||||
│ ├── manifest.json Local-dev mirror of the root manifest
|
||||
│ ├── styles.css Plugin styles
|
||||
│ └── src/
|
||||
│ ├── main.ts Plugin entry point
|
||||
│ ├── sync-engine.ts Polling, cursor management, Realtime
|
||||
|
|
|
|||
10
manifest.json
Normal file
10
manifest.json
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"id": "telegram-bridge",
|
||||
"name": "Telegram Bridge",
|
||||
"version": "0.1.0",
|
||||
"minAppVersion": "1.5.0",
|
||||
"description": "Sync messages from a Telegram bot into your vault through a self-hosted Supabase backend.",
|
||||
"author": "tmlnv",
|
||||
"authorUrl": "https://github.com/tmlnv/obsidian-telegram",
|
||||
"isDesktopOnly": false
|
||||
}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
{
|
||||
"name": "obsidian-telegram",
|
||||
"name": "telegram-bridge",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"description": "Monorepo for the Obsidian Telegram plugin and Supabase backend",
|
||||
"description": "Monorepo for the Telegram Bridge Obsidian plugin and Supabase backend",
|
||||
"scripts": {
|
||||
"verify": "node scripts/verify-local-env.mjs",
|
||||
"bootstrap:supabase": "node scripts/bootstrap-supabase.mjs",
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"id": "obsidian-telegram",
|
||||
"name": "Obsidian Telegram",
|
||||
"id": "telegram-bridge",
|
||||
"name": "Telegram Bridge",
|
||||
"version": "0.1.0",
|
||||
"minAppVersion": "1.5.0",
|
||||
"description": "Sync Telegram messages into Obsidian through a Supabase backend.",
|
||||
"description": "Sync messages from a Telegram bot into your vault through a self-hosted Supabase backend.",
|
||||
"author": "tmlnv",
|
||||
"authorUrl": "https://github.com/tmlnv/obsidian-telegram",
|
||||
"isDesktopOnly": false
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "obsidian-telegram-plugin",
|
||||
"name": "telegram-bridge-plugin",
|
||||
"version": "0.1.0",
|
||||
"description": "Telegram to Obsidian sync plugin backed by Supabase",
|
||||
"description": "Telegram Bridge Obsidian plugin backed by Supabase",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"dev": "node esbuild.config.mjs",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Notice, Plugin } from "obsidian";
|
||||
import { Notice, Platform, Plugin } from "obsidian";
|
||||
import {
|
||||
createDefaultDistributionRule,
|
||||
DEFAULT_SETTINGS,
|
||||
|
|
@ -51,8 +51,10 @@ export default class ObsidianTelegramPlugin extends Plugin {
|
|||
await this.saveSettings();
|
||||
}
|
||||
|
||||
this.statusIndicator = new StatusIndicator(this);
|
||||
this.statusIndicator.setIdle();
|
||||
if (Platform.isDesktopApp) {
|
||||
this.statusIndicator = new StatusIndicator(this);
|
||||
this.statusIndicator.setIdle();
|
||||
}
|
||||
|
||||
this.addSettingTab(new ObsidianTelegramSettingTab(this));
|
||||
|
||||
|
|
@ -62,20 +64,45 @@ export default class ObsidianTelegramPlugin extends Plugin {
|
|||
|
||||
this.addCommand({
|
||||
id: "show-client-id",
|
||||
name: "Show Obsidian Telegram client ID",
|
||||
name: "Show client ID",
|
||||
callback: () => {
|
||||
this.statusIndicator?.setConnected();
|
||||
console.log("Obsidian Telegram client_id:", this.settings.client_id);
|
||||
new Notice(`Client ID: ${this.settings.client_id}`);
|
||||
},
|
||||
});
|
||||
|
||||
this.addCommand({
|
||||
id: "sync-now",
|
||||
name: "Sync Telegram messages now",
|
||||
name: "Sync now",
|
||||
callback: () => {
|
||||
void this.manualSync();
|
||||
},
|
||||
});
|
||||
|
||||
this.addCommand({
|
||||
id: "reconnect",
|
||||
name: "Reconnect",
|
||||
callback: () => {
|
||||
void this.reinitializeSupabase().catch((error) => {
|
||||
new Notice(error instanceof Error ? error.message : String(error));
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
this.addCommand({
|
||||
id: "sign-out",
|
||||
name: "Sign out",
|
||||
checkCallback: (checking) => {
|
||||
if (!this.hasSession()) {
|
||||
return false;
|
||||
}
|
||||
if (!checking) {
|
||||
void this.logout().catch((error) => {
|
||||
new Notice(error instanceof Error ? error.message : String(error));
|
||||
});
|
||||
}
|
||||
return true;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
onunload(): void {
|
||||
|
|
@ -362,7 +389,7 @@ export default class ObsidianTelegramPlugin extends Plugin {
|
|||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
this.statusIndicator?.setError(message);
|
||||
new Notice(`Obsidian Telegram initialization failed: ${message}`);
|
||||
new Notice(`Telegram Bridge initialization failed: ${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -376,12 +403,13 @@ export default class ObsidianTelegramPlugin extends Plugin {
|
|||
vaultFingerprint: this.app.vault.getName(),
|
||||
pollIntervalSeconds: this.settings.poll_interval_seconds,
|
||||
isRealtimeEnabled: this.settings.is_realtime_enabled,
|
||||
registerInterval: (id) => this.registerInterval(id),
|
||||
onMessages: async (messages) => {
|
||||
await this.processMessages(messages);
|
||||
},
|
||||
onError: (error) => {
|
||||
this.statusIndicator?.setError(error.message);
|
||||
console.error("Obsidian Telegram sync failed:", error);
|
||||
console.error("Telegram Bridge sync failed:", error);
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ export class ObsidianTelegramSettingTab extends PluginSettingTab {
|
|||
const { containerEl } = this;
|
||||
containerEl.empty();
|
||||
|
||||
containerEl.createEl("h2", { text: "Obsidian Telegram" });
|
||||
this.renderIntro(containerEl);
|
||||
this.renderConnectionHeader(containerEl);
|
||||
this.renderUsageCard(containerEl);
|
||||
|
||||
|
|
@ -28,6 +28,22 @@ export class ObsidianTelegramSettingTab extends PluginSettingTab {
|
|||
this.renderAdvancedSection(containerEl);
|
||||
}
|
||||
|
||||
private renderIntro(containerEl: HTMLElement): void {
|
||||
if (this.plugin.isSupabaseConfigured() && this.plugin.hasSession()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const intro = containerEl.createDiv({ cls: "obsidian-telegram-intro" });
|
||||
intro.createEl("div", {
|
||||
cls: "obsidian-telegram-intro-title",
|
||||
text: "Requires a self-hosted Supabase project",
|
||||
});
|
||||
intro.createEl("div", {
|
||||
cls: "obsidian-telegram-intro-body",
|
||||
text: "This plugin reads Telegram messages from your own Supabase backend. Follow the README to provision a free Supabase project, deploy the edge functions, and create a Telegram bot before connecting below.",
|
||||
});
|
||||
}
|
||||
|
||||
private createSection(
|
||||
containerEl: HTMLElement,
|
||||
title: string,
|
||||
|
|
@ -305,10 +321,9 @@ export class ObsidianTelegramSettingTab extends PluginSettingTab {
|
|||
|
||||
this.plugin.settings.distribution_rules.forEach((rule, index) => {
|
||||
const isFallbackRule = rule.filter_query.trim() === "{{all}}";
|
||||
const ruleHeader = body.createDiv({ cls: "obsidian-telegram-rule" });
|
||||
ruleHeader.createEl("h4", {
|
||||
text: isFallbackRule ? `Rule ${index + 1} (fallback)` : `Rule ${index + 1}`,
|
||||
});
|
||||
new Setting(body)
|
||||
.setName(isFallbackRule ? `Rule ${index + 1} (fallback)` : `Rule ${index + 1}`)
|
||||
.setHeading();
|
||||
|
||||
new Setting(body)
|
||||
.setName("Filter query")
|
||||
|
|
@ -581,7 +596,7 @@ export class ObsidianTelegramSettingTab extends PluginSettingTab {
|
|||
|
||||
private renderUsageCard(containerEl: HTMLElement): void {
|
||||
const sectionEl = containerEl.createDiv({ cls: "obsidian-telegram-usage-section" });
|
||||
sectionEl.createEl("h3", { text: "Storage usage" });
|
||||
new Setting(sectionEl).setName("Storage usage").setHeading();
|
||||
|
||||
const summaryEl = sectionEl.createDiv({ cls: "obsidian-telegram-usage-card" });
|
||||
this.usageSummaryEl = summaryEl;
|
||||
|
|
@ -706,12 +721,15 @@ export class ObsidianTelegramSettingTab extends PluginSettingTab {
|
|||
scaleEl.createSpan({ text: "0%" });
|
||||
scaleEl.createSpan({ text: "100%" });
|
||||
|
||||
const clampedUsagePercent = Math.min(100, Math.max(0, usagePercent));
|
||||
const clampedThresholdPercent = Math.min(100, Math.max(0, thresholdPercent));
|
||||
|
||||
const trackEl = chartEl.createDiv({ cls: "obsidian-telegram-usage-track" });
|
||||
const fillEl = trackEl.createDiv({ cls: "obsidian-telegram-usage-fill" });
|
||||
fillEl.style.width = `${Math.min(100, Math.max(0, usagePercent))}%`;
|
||||
fillEl.style.setProperty("--telegram-bridge-usage-percent", `${clampedUsagePercent}%`);
|
||||
|
||||
const thresholdEl = trackEl.createDiv({ cls: "obsidian-telegram-usage-threshold" });
|
||||
thresholdEl.style.left = `${Math.min(100, Math.max(0, thresholdPercent))}%`;
|
||||
thresholdEl.style.setProperty("--telegram-bridge-threshold-percent", `${clampedThresholdPercent}%`);
|
||||
thresholdEl.setAttribute("aria-label", `Warning threshold ${thresholdPercent}%`);
|
||||
|
||||
const thresholdLabelEl = chartEl.createDiv({ cls: "obsidian-telegram-usage-threshold-label" });
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ export interface SyncEngineOptions {
|
|||
vaultFingerprint: string;
|
||||
pollIntervalSeconds: number;
|
||||
isRealtimeEnabled: boolean;
|
||||
registerInterval: (id: number) => number;
|
||||
onMessages: (messages: MessageRow[]) => Promise<void>;
|
||||
onError: (error: Error) => void;
|
||||
}
|
||||
|
|
@ -23,6 +24,7 @@ export class SyncEngine {
|
|||
private readonly onError: (error: Error) => void;
|
||||
private readonly pollIntervalSeconds: number;
|
||||
private readonly isRealtimeEnabled: boolean;
|
||||
private readonly registerInterval: (id: number) => number;
|
||||
private pollIntervalId: number | null = null;
|
||||
private realtimeChannel: RealtimeChannel | null = null;
|
||||
private isPolling = false;
|
||||
|
|
@ -38,6 +40,7 @@ export class SyncEngine {
|
|||
this.vaultFingerprint = options.vaultFingerprint;
|
||||
this.pollIntervalSeconds = options.pollIntervalSeconds;
|
||||
this.isRealtimeEnabled = options.isRealtimeEnabled;
|
||||
this.registerInterval = options.registerInterval;
|
||||
this.onMessages = options.onMessages;
|
||||
this.onError = options.onError;
|
||||
}
|
||||
|
|
@ -47,9 +50,11 @@ export class SyncEngine {
|
|||
await this.poll();
|
||||
|
||||
this.stopPolling();
|
||||
this.pollIntervalId = window.setInterval(() => {
|
||||
void this.poll();
|
||||
}, this.pollIntervalSeconds * 1000);
|
||||
this.pollIntervalId = this.registerInterval(
|
||||
window.setInterval(() => {
|
||||
void this.poll();
|
||||
}, this.pollIntervalSeconds * 1000),
|
||||
);
|
||||
|
||||
if (this.isRealtimeEnabled) {
|
||||
this.subscribeRealtime();
|
||||
|
|
|
|||
|
|
@ -4,6 +4,26 @@
|
|||
gap: 6px;
|
||||
}
|
||||
|
||||
.obsidian-telegram-intro {
|
||||
margin: 0 0 16px;
|
||||
padding: 12px 14px;
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
background: color-mix(in srgb, var(--interactive-accent) 10%, var(--background-secondary));
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.obsidian-telegram-intro-title {
|
||||
color: var(--text-normal);
|
||||
font-weight: 600;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.obsidian-telegram-intro-body {
|
||||
color: var(--text-muted);
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.obsidian-telegram-settings-header {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||
|
|
@ -128,6 +148,7 @@
|
|||
.obsidian-telegram-usage-fill {
|
||||
position: absolute;
|
||||
inset: 0 auto 0 0;
|
||||
width: var(--telegram-bridge-usage-percent, 0%);
|
||||
max-width: 100%;
|
||||
border-radius: inherit;
|
||||
background: linear-gradient(90deg, color-mix(in srgb, var(--interactive-accent) 70%, white), var(--interactive-accent));
|
||||
|
|
@ -137,6 +158,7 @@
|
|||
position: absolute;
|
||||
top: -3px;
|
||||
bottom: -3px;
|
||||
left: var(--telegram-bridge-threshold-percent, 0%);
|
||||
width: 2px;
|
||||
margin-left: -1px;
|
||||
background: var(--text-normal);
|
||||
|
|
|
|||
3
versions.json
Normal file
3
versions.json
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"0.1.0": "1.5.0"
|
||||
}
|
||||
Loading…
Reference in a new issue