notice("installed xmppchat addon"); } function xmppchat_addon_admin_post() { DI::config()->set('xmppchat', 'websocket_url', trim($_POST['websocket_url'] ?? '')); DI::config()->set('xmppchat', 'bosh_url', trim($_POST['bosh_url'] ?? '')); DI::config()->set('xmppchat', 'domain', trim($_POST['domain'] ?? '')); DI::config()->set('xmppchat', 'auto_login', !empty($_POST['auto_login'])); DI::config()->set('xmppchat', 'enable_mam', !empty($_POST['enable_mam'])); DI::config()->set('xmppchat', 'enable_smacks', !empty($_POST['enable_smacks'])); DI::config()->set('xmppchat', 'enable_omemo', !empty($_POST['enable_omemo'])); DI::config()->set('xmppchat', 'allow_anonymous', !empty($_POST['allow_anonymous'])); DI::config()->set('xmppchat', 'default_muc', trim($_POST['default_muc'] ?? '')); } function xmppchat_addon_admin(string &$o) { $t = Renderer::getMarkupTemplate('admin.tpl', 'addon/xmppchat/'); $o = Renderer::replaceMacros($t, [ '$page_intro' => DI::l10n()->t('Configure the Converse.js XMPP webchat integration. Requires an XMPP server with WebSocket or BOSH support.'), '$connection_title' => DI::l10n()->t('Connection Settings'), '$auth_title' => DI::l10n()->t('Authentication'), '$features_title' => DI::l10n()->t('Features'), '$chatrooms_title' => DI::l10n()->t('Chat Rooms'), '$help_omemo' => DI::l10n()->t('OMEMO Encryption'), '$help_omemo_text' => DI::l10n()->t('Requires users to verify device fingerprints. May increase initial connection time.'), '$help_muc' => DI::l10n()->t('Default Chat Room'), '$help_muc_text' => DI::l10n()->t('Users will automatically join this room on login. Format: roomname@conference.example.org'), '$help_anon' => DI::l10n()->t('Anonymous Login'), '$help_anon_text' => DI::l10n()->t('Allows users to join chat rooms without XMPP accounts. Requires server support for anonymous authentication.'), '$submit' => DI::l10n()->t('Save Settings'), '$websocket_url' => ['websocket_url', DI::l10n()->t('WebSocket URL'), DI::config()->get('xmppchat', 'websocket_url'), DI::l10n()->t('XMPP WebSocket endpoint (e.g., wss://xmpp.example.org:5281/xmpp-websocket)')], '$bosh_url' => ['bosh_url', DI::l10n()->t('BOSH URL'), DI::config()->get('xmppchat', 'bosh_url'), DI::l10n()->t('XMPP BOSH endpoint for fallback (e.g., https://xmpp.example.org/http-bind)')], '$domain' => ['domain', DI::l10n()->t('XMPP Domain'), DI::config()->get('xmppchat', 'domain'), DI::l10n()->t('XMPP server domain (e.g., example.org)')], '$auto_login' => ['auto_login', DI::l10n()->t('Auto-Login'), DI::config()->get('xmppchat', 'auto_login', false), DI::l10n()->t('Attempt automatic login using Friendica username (requires matching XMPP accounts)')], '$enable_mam' => ['enable_mam', DI::l10n()->t('Enable Message Archive'), DI::config()->get('xmppchat', 'enable_mam', true), DI::l10n()->t('Enable XEP-0313 Message Archive Management for chat history')], '$enable_smacks' => ['enable_smacks', DI::l10n()->t('Enable Stream Management'), DI::config()->get('xmppchat', 'enable_smacks', true), DI::l10n()->t('Enable XEP-0198 Stream Management for reliable connections')], '$enable_omemo' => ['enable_omemo', DI::l10n()->t('Enable OMEMO Encryption'), DI::config()->get('xmppchat', 'enable_omemo', false), DI::l10n()->t('Enable XEP-0384 OMEMO end-to-end encryption')], '$allow_anonymous' => ['allow_anonymous', DI::l10n()->t('Allow Anonymous Login'), DI::config()->get('xmppchat', 'allow_anonymous', false), DI::l10n()->t('Allow users to join chat rooms without authentication')], '$default_muc' => ['default_muc', DI::l10n()->t('Default Chat Room'), DI::config()->get('xmppchat', 'default_muc'), DI::l10n()->t('Auto-join this MUC room on login (e.g., lobby@conference.example.org)')], ]); } function xmppchat_addon_settings(array &$data) { if (!DI::userSession()->getLocalUserId()) { return; } $uid = DI::userSession()->getLocalUserId(); $custom_jid = DI::pConfig()->get($uid, 'xmppchat', 'custom_jid'); $custom_password = DI::pConfig()->get($uid, 'xmppchat', 'custom_password'); $use_custom = DI::pConfig()->get($uid, 'xmppchat', 'use_custom', false); $user_enabled = DI::pConfig()->get($uid, 'xmppchat', 'enabled', DI::config()->get('xmppchat', 'enabled', false)); $custom_websocket = DI::pConfig()->get($uid, 'xmppchat', 'custom_websocket_url'); $custom_bosh = DI::pConfig()->get($uid, 'xmppchat', 'custom_bosh_url'); $t = Renderer::getMarkupTemplate('settings.tpl', 'addon/xmppchat/'); $html = Renderer::replaceMacros($t, [ '$title' => DI::l10n()->t('XMPP Chat Settings'), '$submit' => DI::l10n()->t('Save Settings'), '$info' => DI::l10n()->t('You can connect with any XMPP account from any server. Leave password empty to keep the existing one.'), '$security_title' => DI::l10n()->t('Security Note'), '$security_text' => DI::l10n()->t('Your password is stored encrypted on the server. We recommend using a separate password for XMPP.'), '$user_enabled' => ['enabled', DI::l10n()->t('Enable XMPP Chat'), $user_enabled, DI::l10n()->t('Show the chat widget for your account')], '$use_custom' => ['use_custom', DI::l10n()->t('Use Custom XMPP Account'), $use_custom, DI::l10n()->t('Enable to use your own XMPP account from any server')], '$custom_jid' => ['custom_jid', DI::l10n()->t('XMPP Address (JID)'), $custom_jid, DI::l10n()->t('Your full XMPP address (e.g., user@example.org or user@other-server.com)')], '$custom_password' => ['custom_password', DI::l10n()->t('XMPP Password'), '', DI::l10n()->t('Your XMPP account password (stored encrypted)')], '$custom_websocket' => ['custom_websocket_url', DI::l10n()->t('WebSocket URL'), $custom_websocket, DI::l10n()->t('XMPP WebSocket endpoint for your account (e.g., wss://xmpp.example.org:5281/xmpp-websocket)')], '$custom_bosh' => ['custom_bosh_url', DI::l10n()->t('BOSH URL'), $custom_bosh, DI::l10n()->t('XMPP BOSH endpoint for your account (e.g., https://xmpp.example.org/http-bind)')], ]); $data = [ 'addon' => 'xmppchat', 'title' => DI::l10n()->t('XMPP Chat'), 'html' => $html, ]; } function xmppchat_addon_settings_post(array &$b) { if (!DI::userSession()->getLocalUserId()) { return; } $uid = DI::userSession()->getLocalUserId(); if (!empty($b['xmppchat-submit'])) { DI::pConfig()->set($uid, 'xmppchat', 'enabled', !empty($b['enabled'])); DI::pConfig()->set($uid, 'xmppchat', 'use_custom', !empty($b['use_custom'])); DI::pConfig()->set($uid, 'xmppchat', 'custom_jid', trim($b['custom_jid'] ?? '')); DI::pConfig()->set($uid, 'xmppchat', 'custom_websocket_url', trim($b['custom_websocket_url'] ?? '')); DI::pConfig()->set($uid, 'xmppchat', 'custom_bosh_url', trim($b['custom_bosh_url'] ?? '')); // Only update password if provided if (!empty($b['custom_password'])) { // Note: In production, use proper encryption (e.g., openssl_encrypt) // For now, store base64-encoded (NOT SECURE - just for demonstration) $encrypted_password = base64_encode($b['custom_password']); DI::pConfig()->set($uid, 'xmppchat', 'custom_password', $encrypted_password); } } } function xmppchat_load_config(ConfigFileManager $loader) { DI::appHelper()->getConfigCache()->load($loader->loadAddonConfig('xmppchat'), \Friendica\Core\Config\ValueObject\Cache::SOURCE_STATIC); } function xmppchat_footer(string &$body) { DI::logger()->debug("xmppchat: footer hook called"); // Only show the widget for authenticated users who enabled it in their settings if (!DI::userSession()->isAuthenticated()) { return; } $uid = DI::userSession()->getLocalUserId(); $user_enabled = DI::pConfig()->get($uid, 'xmppchat', 'enabled', false); if (!$user_enabled) { return; } // Load global features/settings $domain = DI::config()->get('xmppchat', 'domain'); $auto_login = DI::config()->get('xmppchat', 'auto_login', false); $enable_mam = DI::config()->get('xmppchat', 'enable_mam', true); $enable_smacks = DI::config()->get('xmppchat', 'enable_smacks', true); $enable_omemo = DI::config()->get('xmppchat', 'enable_omemo', false); $allow_anonymous = DI::config()->get('xmppchat', 'allow_anonymous', false); $default_muc = DI::config()->get('xmppchat', 'default_muc'); // Determine whether to use custom user endpoints or global ones $use_custom = DI::pConfig()->get($uid, 'xmppchat', 'use_custom', false); $jid = ''; $password = ''; $websocket_url = null; $bosh_url = null; if ($use_custom) { // User explicitly chose a custom account: use their endpoints and credentials only $websocket_url = DI::pConfig()->get($uid, 'xmppchat', 'custom_websocket_url'); $bosh_url = DI::pConfig()->get($uid, 'xmppchat', 'custom_bosh_url'); $jid = DI::pConfig()->get($uid, 'xmppchat', 'custom_jid'); $encrypted_password = DI::pConfig()->get($uid, 'xmppchat', 'custom_password'); if ($encrypted_password) { $password = base64_decode($encrypted_password); } // If the user selected a custom account but didn't provide endpoints, don't attempt to connect if (empty($websocket_url) && empty($bosh_url)) { DI::logger()->warning("xmppchat: user $uid selected custom XMPP account but no websocket or bosh URL provided"); return; } } else { // Use global endpoints $websocket_url = DI::config()->get('xmppchat', 'websocket_url'); $bosh_url = DI::config()->get('xmppchat', 'bosh_url'); if (empty($websocket_url) && empty($bosh_url)) { DI::logger()->warning("xmppchat: No websocket_url or bosh_url configured globally"); return; } // Auto-login using Friendica username if configured if ($auto_login) { $nickname = DI::session()->get('nickname'); if ($nickname && $domain) { $jid = $nickname . '@' . $domain; } } } $tpl = Renderer::getMarkupTemplate('xmppchat.tpl', 'addon/xmppchat/'); $chat_html = Renderer::replaceMacros($tpl, [ '$websocket_url' => $websocket_url, '$bosh_url' => $bosh_url, '$jid' => $jid, '$password' => $password, '$auto_login' => $auto_login, '$allow_anonymous' => $allow_anonymous, '$enable_mam' => $enable_mam ? 'true' : 'false', '$enable_smacks' => $enable_smacks ? 'true' : 'false', '$enable_omemo' => $enable_omemo ? 'true' : 'false', '$default_muc' => $default_muc, ]); $body .= $chat_html; }