This commit is contained in:
2026-07-15 19:33:49 +02:00
parent 2338858254
commit bdb6be1702
4 changed files with 95 additions and 6 deletions

View File

@@ -187,9 +187,10 @@ export default async bot => {
// --- LINKED ACCOUNT CHECK --- // --- LINKED ACCOUNT CHECK ---
let websiteUser = null; let websiteUser = null;
if (e.type === 'matrix' || e.type === 'discord') { if (e.type === 'matrix' || e.type === 'discord' || e.type === 'tg') {
// Identify lookup key // Identify lookup key and type
// Matrix: Use unique ID (MXID) // Matrix: Use unique ID (MXID)
// Telegram: Use numeric user ID (most stable, never changes)
// Discord: Use Nick or Username (Legacy) - TODO: Migration to ID recommended later // Discord: Use Nick or Username (Legacy) - TODO: Migration to ID recommended later
let lookupAlias = e.user.nick || e.user.username; let lookupAlias = e.user.nick || e.user.username;
let lookupType = 'discord'; // default check let lookupType = 'discord'; // default check
@@ -197,6 +198,9 @@ export default async bot => {
if (e.type === 'matrix') { if (e.type === 'matrix') {
lookupAlias = e.user.account; // MXID lookupAlias = e.user.account; // MXID
lookupType = 'matrix'; lookupType = 'matrix';
} else if (e.type === 'tg') {
lookupAlias = e.user.account; // numeric Telegram user ID as string
lookupType = 'telegram';
} }
// Check DB // Check DB
@@ -220,7 +224,8 @@ export default async bot => {
// Enforce Link for !w command // Enforce Link for !w command
if (isWCommand && !websiteUser) { if (isWCommand && !websiteUser) {
await e.reply(`You must link your account to use this command. Go to ${cfg.main.url.full}/settings to link your ${e.type} account.`); const linkType = e.type === 'tg' ? 'Telegram' : e.type;
await e.reply(`You must link your account to use this command. Go to ${cfg.main.url.full}/settings to link your ${linkType} account. Then send !link <TOKEN> to this bot.`);
return false; return false;
} }
} }

View File

@@ -0,0 +1,78 @@
import db from "../sql.mjs";
import cfg from "../config.mjs";
export default async self => {
return [{
name: "telegram_link",
call: /^!link(?:\s+(.+))?$/i,
active: true,
clients: ["tg"],
f: async e => {
// Only for Telegram
if (e.type !== 'tg') return false;
let token = (e.args[0] || '').trim().toUpperCase();
// Scenario 1: User typed "!link" only (no token)
if (!token) {
await e.reply(`Please generate a link token on the website at ${cfg.main.url.full}/settings and then send: !link <TOKEN>`);
return true;
}
// Scenario 2: Processing a token
try {
// Find token in DB
const linkData = (await db`
SELECT user_id FROM link_token WHERE token = ${token}
`)[0];
if (!linkData) {
return await e.reply("Invalid or expired token. Please generate a new one at the website settings page.");
}
// Use the Telegram numeric user ID as the alias (most stable identifier)
const alias = e.user.account; // e.g. "123456789"
// Check if already linked to THIS user
const existing = await db`
SELECT 1 FROM user_alias
WHERE userid = ${linkData.user_id}
AND alias = ${alias}
AND type = 'telegram'
`;
if (existing.length > 0) {
return await e.reply("This Telegram account is already linked to your website profile.");
}
// Check if this Telegram account is linked to ANOTHER user (prevent hijacking)
const taken = await db`
SELECT 1 FROM user_alias
WHERE alias = ${alias}
AND type = 'telegram'
`;
if (taken.length > 0) {
return await e.reply("This Telegram account is already linked to a different website profile.");
}
// Perform Link
await db`
INSERT INTO user_alias (userid, alias, type)
VALUES (${linkData.user_id}, ${alias}, 'telegram')
ON CONFLICT DO NOTHING
`;
// Delete used token
await db`DELETE FROM link_token WHERE token = ${token}`;
const tgUsername = e.user.username ? `@${e.user.username}` : e.user.nick;
return await e.reply(`Successfully linked Telegram account ${tgUsername} (ID: ${alias}) to your website profile!`);
} catch (err) {
console.error("[Telegram Link] Error:", err);
return await e.reply("An internal error occurred while linking your account.");
}
}
}];
};

View File

@@ -1227,6 +1227,7 @@ process.on('uncaughtException', err => {
get cleanup_start_date() { return getCleanupStartDate(); }, get cleanup_start_date() { return getCleanupStartDate(); },
get cleanup_end_date() { return getCleanupEndDate(); }, get cleanup_end_date() { return getCleanupEndDate(); },
matrix_enabled: cfg.clients.find(c => c.type === 'matrix')?.enabled || false, matrix_enabled: cfg.clients.find(c => c.type === 'matrix')?.enabled || false,
telegram_enabled: cfg.clients.find(c => c.type === 'tg')?.enabled || false,
ts: Date.now(), ts: Date.now(),
get default_layout() { return getDefaultLayout(); }, get default_layout() { return getDefaultLayout(); },
show_koepfe: !!cfg.websrv.show_koepfe, show_koepfe: !!cfg.websrv.show_koepfe,

View File

@@ -12,7 +12,7 @@
<a href="#export">{{ t('settings.export_data_title') }}</a> <a href="#export">{{ t('settings.export_data_title') }}</a>
@endif @endif
<a href="#account">{{ t('settings.account') }}</a> <a href="#account">{{ t('settings.account') }}</a>
@if(matrix_enabled) @if(matrix_enabled || telegram_enabled)
<a href="#linked">{{ t('settings.linked_accounts') }}</a> <a href="#linked">{{ t('settings.linked_accounts') }}</a>
@endif @endif
@if(enable_user_api_keys) @if(enable_user_api_keys)
@@ -517,7 +517,7 @@
</div> </div>
</div> </div>
</div> </div>
@if(matrix_enabled) @if(matrix_enabled || telegram_enabled)
<!-- ═══════════════════════════════ LINKED ACCOUNTS ═══════════════════════════════ --> <!-- ═══════════════════════════════ LINKED ACCOUNTS ═══════════════════════════════ -->
<h2 id="linked">{{ t('settings.linked_accounts') }}</h2> <h2 id="linked">{{ t('settings.linked_accounts') }}</h2>
<div class="linked-accounts-wrapper"> <div class="linked-accounts-wrapper">
@@ -532,9 +532,14 @@
<div class="link-action-area"> <div class="link-action-area">
<h4 style="margin-top: 20px;">{{ t('settings.add_new_link') }}</h4> <h4 style="margin-top: 20px;">{{ t('settings.add_new_link') }}</h4>
<p style="margin-bottom: 15px; color: var(--text-muted); font-size: 0.9em;"> <p style="margin-bottom: 10px; color: var(--text-muted); font-size: 0.9em;">
{!! t('settings.matrix_instructions') !!} {!! t('settings.matrix_instructions') !!}
</p> </p>
@if(telegram_enabled)
<p style="margin-bottom: 15px; color: var(--text-muted); font-size: 0.9em;">
<strong>Telegram:</strong> Generate a token below, then send <code>!link &lt;TOKEN&gt;</code> to the Telegram bot.
</p>
@endif
<div id="token-display-area" style="display: none; margin-bottom: 15px;"> <div id="token-display-area" style="display: none; margin-bottom: 15px;">
<div class="alert alert-info"> <div class="alert alert-info">