diff --git a/docs/Telegram Sync Insider Features.md b/docs/Telegram Sync Insider Features.md
index 4b92eed..615dc5b 100644
--- a/docs/Telegram Sync Insider Features.md
+++ b/docs/Telegram Sync Insider Features.md
@@ -5,9 +5,7 @@
✅ processing messages older than 24 hours if Obsidian wasn't running
✅ easy installing the latest published beta versions
✅ plugin updates in the channel instead of informational messages in your bot
-✅ processing messages older than 24 hours if Obsidian wasn't running
-❌ sending notes from Telegram to Obsidian (_not implemented_)
-❌ getting messages from other bots in connected group chats (_not implemented_)
-❌ posting messages in selected chats (_not implemented_)
+❌ processing messages from other bots in connected group chats (_not implemented_)
+❌ posting notes as messages in selected Telegram chats (_not implemented_)
**⚠ For all of these features, you must connect both your Telegram user and bot.**
diff --git a/release-notes.mjs b/release-notes.mjs
index 0346333..cd3745f 100644
--- a/release-notes.mjs
+++ b/release-notes.mjs
@@ -7,12 +7,12 @@
// TODO NEXT: check reconnecting
// TODO NEXT: bur in reconnecting on MacBook https://t.me/sm1rnov_id
import { compareVersions } from "compare-versions";
-export const releaseVersion = "3.3.0";
+export const releaseVersion = "4.0.0";
export const showNewFeatures = true;
-export let showBreakingChanges = false;
+export let showBreakingChanges = true;
-const newFeatures = `In this release, security has been enhanced by encrypting the bot token with a PIN code`;
-export const breakingChanges = `⚠️ Breaking changes!\n\nThe user's connection to the plugin may need to be reestablished due to the GramJs library update.\n\nGrant your bot admin rights if you want to use bot reactions in groups and channels. ⚠️`;
+const newFeatures = `In this release, security has been enhanced by encrypting the bot token`;
+export const breakingChanges = `⚠️ Breaking changes!\n\nBot token may need to be re-entered. ⚠️`;
export const telegramChannelLink = "https://t.me/obsidian_telegram_sync";
export const insiderFeaturesLink =
"https://github.com/soberhacker/obsidian-telegram-sync/blob/main/docs/Telegram%20Sync%20Insider%20Features.md";
@@ -20,7 +20,7 @@ const telegramChannelAHref = `channel`;
const insiderFeaturesAHref = `insider features`;
const telegramChannelIntroduction = `Subscribe for free to the plugin's ${telegramChannelAHref} and enjoy access to ${insiderFeaturesAHref} and the latest beta versions, several months ahead of public release.`;
const telegramChatLink = "chat";
-const telegramChatIntroduction = `Join the plugin's ${telegramChatLink} - your space to seek advice, ask questions, and share knowledge (access via the @tribute bot).`;
+const telegramChatIntroduction = `Join the plugin's ${telegramChatLink} - your space to seek advice, ask questions, and share knowledge (access via the tribute bot).`;
const donation = `If you appreciate this plugin and would like to support its continued development, please consider donating through the buttons below or via Telegram Stars in the ${telegramChannelAHref}!`;
const bestRegards = "Best regards,\nYour soberhacker🍃🧘💻\n⌞";
diff --git a/src/main.ts b/src/main.ts
index 2535228..5ada436 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -32,7 +32,7 @@ import {
} from "./settings/messageDistribution";
import os from "os";
import { clearCachedUnprocessedMessages, forwardUnprocessedMessages } from "./telegram/user/sync";
-import { decrypt } from "./utils/crypto256";
+import { decrypt, encrypt } from "./utils/crypto256";
import { PinCodeModal } from "./settings/modals/PinCode";
// TODO LOW: add "connecting"
@@ -61,7 +61,7 @@ export default class TelegramSyncPlugin extends Plugin {
status: PluginStatus = "loading";
time4processOldMessages = false;
processOldMessagesIntervalId?: NodeJS.Timer;
- pinCode? = "";
+ pinCode?: string = undefined;
async initTelegram(initType?: Client.SessionType) {
this.lastPollingErrors = [];
@@ -253,6 +253,11 @@ export default class TelegramSyncPlugin extends Plugin {
});
}
+ if (!this.settings.botTokenEncrypted) {
+ this.botTokenEncrypt();
+ needToSaveSettings = true;
+ }
+
needToSaveSettings && (await this.saveSettings());
}
@@ -277,15 +282,26 @@ export default class TelegramSyncPlugin extends Plugin {
else displayAndLogError(this, error, StatusMessages.BOT_DISCONNECTED, checkConnectionMessage, undefined, 0);
}
- getBotToken(): string {
- if (!this.settings.botTokenEncryption) return this.settings.botToken;
- if (!this.pinCode) {
- const pinCodeModal = new PinCodeModal(this, true);
- pinCodeModal.onClose = async () => {
- if (!this.pinCode) displayAndLog(this, "Plugin Telegram Sync stopped. No pin code entered.");
- };
- pinCodeModal.open();
+ async getBotToken(): Promise {
+ if (!this.settings.botTokenEncrypted) return this.settings.botToken;
+
+ if (this.settings.encryptionByPinCode && !this.pinCode) {
+ await new Promise((resolve) => {
+ const pinCodeModal = new PinCodeModal(this, true);
+ pinCodeModal.onClose = async () => {
+ if (!this.pinCode) displayAndLog(this, "Plugin Telegram Sync stopped. No pin code entered.");
+ resolve(undefined);
+ };
+ pinCodeModal.open();
+ });
}
return decrypt(this.settings.botToken, this.pinCode);
}
+
+ botTokenEncrypt(saveSettings = false) {
+ this.settings.botToken = encrypt(this.settings.botToken, this.pinCode);
+ this.settings.botTokenEncrypted = true;
+ saveSettings && this.saveSettings();
+ displayAndLog(this, "Bot token encrypted", 0);
+ }
}
diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts
index 6ceef01..04d54a4 100644
--- a/src/settings/Settings.ts
+++ b/src/settings/Settings.ts
@@ -50,7 +50,8 @@ export interface RefreshValues {
export interface TelegramSyncSettings {
botToken: string;
- botTokenEncryption: boolean;
+ encryptionByPinCode: boolean;
+ botTokenEncrypted: boolean;
deleteMessagesFromTelegram: boolean;
allowedChats: string[];
mainDeviceId: string;
@@ -73,7 +74,8 @@ export interface TelegramSyncSettings {
export const DEFAULT_SETTINGS: TelegramSyncSettings = {
botToken: "",
- botTokenEncryption: false,
+ encryptionByPinCode: false,
+ botTokenEncrypted: false,
deleteMessagesFromTelegram: false,
allowedChats: [""],
mainDeviceId: "",
diff --git a/src/settings/modals/BotSettings.ts b/src/settings/modals/BotSettings.ts
index 5826593..966bc06 100644
--- a/src/settings/modals/BotSettings.ts
+++ b/src/settings/modals/BotSettings.ts
@@ -1,6 +1,5 @@
import { Modal, Setting } from "obsidian";
import TelegramSyncPlugin from "src/main";
-import { encrypt } from "src/utils/crypto256";
import { _5sec, displayAndLog } from "src/utils/logUtils";
import { PinCodeModal } from "./PinCode";
@@ -18,7 +17,7 @@ export class BotSettingsModal extends Modal {
this.addBotToken();
this.addAllowedChatsSetting();
this.addDeviceId();
- this.addBotTokenEncryption();
+ this.addEncryptionByPinCode();
this.addFooterButtons();
}
@@ -49,9 +48,9 @@ export class BotSettingsModal extends Modal {
new Setting(this.botSettingsDiv)
.setName("Bot token (required)")
.setDesc("Enter your Telegram bot token.")
- .addText((text) => {
+ .addText(async (text) => {
text.setPlaceholder("example: 6123456784:AAX9mXnFE2q9WahQ")
- .setValue(this.plugin.settings.botToken)
+ .setValue(await this.plugin.getBotToken())
.onChange(async (value: string) => {
if (!value) {
text.inputEl.style.borderColor = "red";
@@ -59,6 +58,7 @@ export class BotSettingsModal extends Modal {
text.inputEl.style.borderStyle = "solid";
}
this.plugin.settings.botToken = value;
+ this.plugin.settings.botTokenEncrypted = false;
});
});
}
@@ -129,24 +129,28 @@ export class BotSettingsModal extends Modal {
});
}
- addBotTokenEncryption() {
+ addEncryptionByPinCode() {
const botTokenSetting = new Setting(this.botSettingsDiv)
.setName("Bot token encryption using a PIN code")
.setDesc(
"Encrypt the bot token for enhanced security. When enabled, a PIN code is required at each Obsidian launch. ",
)
.addToggle((toggle) => {
- toggle.setValue(this.plugin.settings.botTokenEncryption);
+ toggle.setValue(this.plugin.settings.encryptionByPinCode);
toggle.onChange(async (value) => {
- this.plugin.settings.botTokenEncryption = value;
- await this.plugin.saveSettings();
- if (!value) return;
+ if (this.plugin.settings.botTokenEncrypted) {
+ this.plugin.settings.botToken = await this.plugin.getBotToken();
+ this.plugin.settings.botTokenEncrypted = false;
+ }
+ this.plugin.settings.encryptionByPinCode = value;
+ if (!value) {
+ this.plugin.pinCode = undefined;
+ return;
+ }
const pinCodeModal = new PinCodeModal(this.plugin, false);
pinCodeModal.onClose = async () => {
- if (pinCodeModal.saved && this.plugin.pinCode) {
- this.plugin.settings.botToken = encrypt(this.plugin.settings.botToken, this.plugin.pinCode);
- } else this.plugin.settings.botTokenEncryption = false;
- await this.plugin.saveSettings();
+ if (pinCodeModal.saved && this.plugin.pinCode) return;
+ this.plugin.settings.encryptionByPinCode = false;
};
pinCodeModal.open();
});
@@ -164,6 +168,7 @@ export class BotSettingsModal extends Modal {
b.setTooltip("Connect")
.setIcon("checkmark")
.onClick(async () => {
+ if (!this.plugin.settings.botTokenEncrypted) this.plugin.botTokenEncrypt();
await this.plugin.saveSettings();
this.saved = true;
this.close();
diff --git a/src/settings/modals/PinCode.ts b/src/settings/modals/PinCode.ts
index 554b58d..11466bd 100644
--- a/src/settings/modals/PinCode.ts
+++ b/src/settings/modals/PinCode.ts
@@ -18,10 +18,15 @@ export class PinCodeModal extends Modal {
this.addFooterButtons();
}
+ success = async () => {
+ this.saved = true;
+ this.close();
+ };
+
addHeader() {
this.contentEl.empty();
this.pinCodeDiv = this.contentEl.createDiv();
- this.titleEl.setText((this.decrypt ? "Decrypting" : "Encrypting") + " bot token");
+ this.titleEl.setText("Telegram Sync: " + (this.decrypt ? "Decrypting" : "Encrypting") + " bot token");
}
addPinCode() {
@@ -37,6 +42,10 @@ export class PinCodeModal extends Modal {
}
this.plugin.pinCode = value;
});
+ text.inputEl.addEventListener("keydown", (event: KeyboardEvent) => {
+ if (!(event.key === "Enter")) return;
+ this.success.call(this);
+ });
});
}
@@ -44,12 +53,7 @@ export class PinCodeModal extends Modal {
this.pinCodeDiv.createEl("br");
const footerButtons = new Setting(this.contentEl.createDiv());
footerButtons.addButton((b) => {
- b.setTooltip("Connect")
- .setIcon("checkmark")
- .onClick(async () => {
- this.saved = true;
- this.close();
- });
+ b.setTooltip("Connect").setIcon("checkmark").onClick(this.success);
return b;
});
footerButtons.addExtraButton((b) => {
@@ -57,7 +61,7 @@ export class PinCodeModal extends Modal {
.setTooltip("Cancel")
.onClick(async () => {
this.saved = false;
- this.plugin.pinCode = "";
+ this.plugin.pinCode = undefined;
this.close();
});
return b;
diff --git a/src/telegram/bot/bot.ts b/src/telegram/bot/bot.ts
index ea12b28..063b783 100644
--- a/src/telegram/bot/bot.ts
+++ b/src/telegram/bot/bot.ts
@@ -3,7 +3,7 @@ import TelegramSyncPlugin from "src/main";
import { _1sec, displayAndLog } from "src/utils/logUtils";
import { handleMessage } from "./message/handlers";
import { reconnect } from "../user/user";
-import { enqueueByCondition } from "src/utils/queues";
+import { enqueue, enqueueByCondition } from "src/utils/queues";
// Initialize the Telegram bot and set up message handling
export async function connect(plugin: TelegramSyncPlugin) {
@@ -18,7 +18,7 @@ export async function connect(plugin: TelegramSyncPlugin) {
return;
}
// Create a new bot instance and start polling
- plugin.bot = new TelegramBot(plugin.getBotToken());
+ plugin.bot = new TelegramBot(await enqueue(plugin, plugin.getBotToken));
const bot = plugin.bot;
// Set connected flag to false and log errors when a polling error occurs
bot.on("polling_error", async (error: unknown) => {
diff --git a/src/telegram/bot/message/handlers.ts b/src/telegram/bot/message/handlers.ts
index 2fc77a2..194d41b 100644
--- a/src/telegram/bot/message/handlers.ts
+++ b/src/telegram/bot/message/handlers.ts
@@ -55,6 +55,8 @@ export async function handleMessage(plugin: TelegramSyncPlugin, msg: TelegramBot
const { fileObject, fileType } = getFileObject(msg);
// skip system messages
+ !isChannelPost && (await enqueue(ifNewReleaseThenShowChanges, plugin, msg));
+
if (!msg.text && !fileObject) {
displayAndLog(plugin, `System message skipped`, 0);
return;
@@ -143,7 +145,6 @@ export async function handleMessage(plugin: TelegramSyncPlugin, msg: TelegramBot
try {
if (!msg.text && distributionRule.filePathTemplate) await handleFiles(plugin, msg, distributionRule);
else await handleMessageText(plugin, msg, distributionRule);
- !isChannelPost && (await enqueue(ifNewReleaseThenShowChanges, plugin, msg));
} catch (error) {
await displayAndLogError(plugin, error, "", "", msg, _15sec);
} finally {
@@ -225,6 +226,7 @@ export async function handleFiles(
const chatId = msg.chat.id < 0 ? msg.chat.id.toString().slice(4) : msg.chat.id.toString();
telegramFileName =
telegramFileName || fileLink?.split("/").pop()?.replace(/file/, `${fileType}_${chatId}`) || "";
+ // TODO add bot file size limits to error "...file is too big..." (https://t.me/c/1536715535/1266)
const fileStream = plugin.bot.getFileStream(fileId);
const fileChunks: Uint8Array[] = [];
@@ -410,7 +412,6 @@ export async function ifNewReleaseThenShowChanges(plugin: TelegramSyncPlugin, ms
parse_mode: "HTML",
reply_markup: { inline_keyboard: donationInlineKeyboard },
};
-
await plugin.bot?.sendMessage(msg.chat.id, release.notes, options);
}
diff --git a/src/telegram/user/user.ts b/src/telegram/user/user.ts
index 248c9d3..bd9ef4a 100644
--- a/src/telegram/user/user.ts
+++ b/src/telegram/user/user.ts
@@ -1,6 +1,7 @@
import TelegramSyncPlugin from "src/main";
import * as Client from "./client";
import { StatusMessages, displayAndLogError } from "src/utils/logUtils";
+import { enqueue } from "src/utils/queues";
export async function connect(
plugin: TelegramSyncPlugin,
@@ -31,7 +32,7 @@ export async function connect(
plugin.userConnected = await Client.isAuthorizedAsUser();
if (sessionType == "bot" || !plugin.userConnected) {
- await Client.signInAsBot(plugin.getBotToken());
+ await Client.signInAsBot(await enqueue(plugin, plugin.getBotToken));
}
if (sessionType == "user" && !plugin.userConnected) {
diff --git a/src/utils/crypto256.ts b/src/utils/crypto256.ts
index 5a4a697..bf07913 100644
--- a/src/utils/crypto256.ts
+++ b/src/utils/crypto256.ts
@@ -1,19 +1,36 @@
import crypto from "crypto";
+import { base64ToString } from "src/utils/fsUtils";
+
+const id1 = "c29iZXJoYWNrZXI=";
+const id2 = "S2V5";
+const id3 = "SVY=";
const algorithm = "aes-256-cbc";
-const defaultKey = "soberhackerKey";
-const defaultIV = "soberhackerIV";
+const defaultKey = base64ToString(id1) + base64ToString(id2);
+const defaultIV = base64ToString(id1) + base64ToString(id3);
export function encrypt(text: string, key = defaultKey, iv = defaultIV): string {
- const cipher = crypto.createCipheriv(algorithm, key, iv);
+ const cipher = crypto.createCipheriv(
+ algorithm,
+ Buffer.from(padOrTrim(key, 32), "utf8"),
+ Buffer.from(padOrTrim(iv, 16), "utf8"),
+ );
let encrypted = cipher.update(text, "utf8", "hex");
encrypted += cipher.final("hex");
return encrypted;
}
export function decrypt(encryptedText: string, key = defaultKey, iv = defaultIV): string {
- const decipher = crypto.createDecipheriv(algorithm, key, iv);
+ const decipher = crypto.createDecipheriv(
+ algorithm,
+ Buffer.from(padOrTrim(key, 32), "utf8"),
+ Buffer.from(padOrTrim(iv, 16), "utf8"),
+ );
let decrypted = decipher.update(encryptedText, "hex", "utf8");
decrypted += decipher.final("utf8");
return decrypted;
}
+
+export function padOrTrim(input: string, length: number) {
+ return input.length > length ? input.slice(0, length) : input.padEnd(length, "0");
+}