forked from friendica/friendica-addons
XMPP addon is created
This commit is contained in:
parent
6a342088a5
commit
801c6492c3
8 changed files with 650 additions and 0 deletions
202
xmppchat/README.md
Normal file
202
xmppchat/README.md
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
# XMPP Chat Addon for Friendica
|
||||
|
||||
Embeds a fully-featured XMPP webchat client (Converse.js) into your Friendica instance.
|
||||
|
||||
## Features
|
||||
|
||||
- **Full XMPP chat UI** via Converse.js
|
||||
- **Multi-User Chat (MUC)** support
|
||||
- **Message Archive Management** (XEP-0313) for chat history
|
||||
- **Stream Management** (XEP-0198) for reliable connections
|
||||
- **Optional OMEMO** end-to-end encryption
|
||||
- **WebSocket & BOSH** transport support
|
||||
- Overlayed chat widget (minimizable)
|
||||
- Desktop notifications
|
||||
- File sharing (when XMPP server supports XEP-0363)
|
||||
|
||||
## Requirements
|
||||
|
||||
### XMPP Server
|
||||
|
||||
You need an XMPP server with:
|
||||
|
||||
- **WebSocket** support (recommended) or **BOSH** (HTTP binding)
|
||||
- Enabled modules:
|
||||
- `mod_websocket` or `mod_bosh`
|
||||
- `mod_mam` (Message Archive Management)
|
||||
- `mod_smacks` (Stream Management)
|
||||
- `mod_carbons` (Message Carbons for multi-device sync)
|
||||
- `mod_http_upload` (for file sharing)
|
||||
- Optional: `mod_omemo` (for OMEMO encryption)
|
||||
|
||||
**Example for Prosody** (`/etc/prosody/prosody.cfg.lua`):
|
||||
```lua
|
||||
modules_enabled = {
|
||||
"websocket";
|
||||
"bosh";
|
||||
"mam";
|
||||
"smacks";
|
||||
"carbons";
|
||||
"http_upload";
|
||||
"csi"; -- Client State Indication
|
||||
"vcard4";
|
||||
"bookmarks";
|
||||
}
|
||||
|
||||
-- WebSocket configuration
|
||||
consider_websocket_secure = true
|
||||
cross_domain_websocket = true
|
||||
|
||||
-- HTTP upload limits
|
||||
http_upload_file_size_limit = 10485760 -- 10 MB
|
||||
http_upload_expire_after = 60 * 60 * 24 * 7 -- 1 week
|
||||
```
|
||||
|
||||
Ensure your firewall allows:
|
||||
|
||||
- Port 5281 (WebSocket/BOSH over HTTPS)
|
||||
- Port 5222 (XMPP client connections)
|
||||
|
||||
### DNS/SSL
|
||||
|
||||
For WebSocket over TLS (wss://), you need:
|
||||
|
||||
- Valid SSL certificate for your XMPP domain
|
||||
- DNS records pointing to your XMPP server
|
||||
|
||||
|
||||
## Security Considerations
|
||||
|
||||
### Auto-Login
|
||||
|
||||
The `auto_login` option is **disabled by default** and requires:
|
||||
|
||||
- XMPP usernames matching Friendica usernames
|
||||
- Separate password management (do NOT reuse Friendica passwords!)
|
||||
- Consider using:
|
||||
- **SASL EXTERNAL** with client certificates
|
||||
- **OAuth tokens** via `mod_auth_external`
|
||||
- **Anonymous login** for public chat rooms
|
||||
|
||||
### User Passwords
|
||||
|
||||
**Custom XMPP credentials** are stored per-user in the database:
|
||||
|
||||
- Passwords are base64-encoded (basic obfuscation)
|
||||
- Users should use **separate XMPP passwords**, not their Friendica password
|
||||
|
||||
### OMEMO Encryption
|
||||
|
||||
To enable end-to-end encryption:
|
||||
1. Set `enable_omemo => true` in config (or via Admin UI)
|
||||
2. Ensure Converse.js OMEMO plugin is loaded (CDN version 10.1.6+ includes it)
|
||||
3. Users must verify device fingerprints manually in the chat UI
|
||||
4. First connection with OMEMO may take longer due to key generation
|
||||
5. OMEMO requires:
|
||||
- Modern browser with WebCrypto API support
|
||||
- Server support for PEP (XEP-0163) and device list storage
|
||||
- All chat participants using OMEMO-capable clients
|
||||
|
||||
**OMEMO Features in this addon:**
|
||||
|
||||
- Automatic device key management
|
||||
- Multi-device synchronization
|
||||
- Encrypted message storage (MAM)
|
||||
- Trust-on-first-use (TOFU) model
|
||||
- Manual fingerprint verification UI
|
||||
|
||||
### Content Security Policy
|
||||
|
||||
If you use strict CSP headers, whitelist:
|
||||
|
||||
- `cdn.conversejs.org` (for Converse.js assets)
|
||||
- Your XMPP server domain (for WebSocket/BOSH connections)
|
||||
|
||||
## Usage
|
||||
|
||||
### For Users
|
||||
|
||||
#### Using Your Own XMPP Account
|
||||
|
||||
1. Go to **Settings → Addon Settings → XMPP Chat**
|
||||
2. Enable "Use Custom XMPP Account"
|
||||
3. Enter your XMPP address (JID), e.g., `user@jabber.org` or `myname@conversations.im`
|
||||
4. Enter your XMPP password
|
||||
5. Save settings
|
||||
6. Reload any page - you'll be automatically logged into the chat
|
||||
|
||||
**You can use any XMPP account from any server!** No need to create an account on the Friendica instance's XMPP server.
|
||||
|
||||
#### Using the Chat Widget
|
||||
|
||||
1. Click the chat icon in bottom-right corner
|
||||
2. If you haven't configured custom credentials, enter XMPP username@domain and password
|
||||
3. Chat with contacts or join group rooms (MUCs)
|
||||
4. Chat state persists across page navigation
|
||||
|
||||
### Joining Group Chats
|
||||
|
||||
To join a multi-user chat room:
|
||||
1. Click the "+" icon in Converse.js
|
||||
2. Select "Join a chat room"
|
||||
3. Enter room JID: `room@conference.example.org`
|
||||
4. Choose a nickname
|
||||
|
||||
### Managing Bookmarks
|
||||
|
||||
Converse.js supports XEP-0048 bookmarks:
|
||||
|
||||
- Bookmarked rooms appear in your sidebar
|
||||
- Auto-join on login (optional)
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Chat widget not appearing
|
||||
|
||||
- Check browser console for errors
|
||||
- Verify `enabled => true` in config
|
||||
- Ensure `websocket_url` or `bosh_url` is set
|
||||
- Check Friendica logs: `tail -f friendica.log | grep xmppchat`
|
||||
|
||||
### Cannot connect to XMPP server
|
||||
|
||||
- Test WebSocket endpoint manually:
|
||||
```bash
|
||||
wscat -c wss://xmpp.example.org:5281/xmpp-websocket
|
||||
```
|
||||
- Verify firewall rules allow port 5281
|
||||
- Check Prosody logs: `tail -f /var/log/prosody/prosody.log`
|
||||
- Ensure SSL certificate is valid
|
||||
|
||||
### Messages not loading from history
|
||||
|
||||
- Verify `mod_mam` is enabled on server
|
||||
- Check `enable_mam => true` in Friendica config
|
||||
- MAM requires XMPP server storage backend (SQL recommended)
|
||||
|
||||
### OMEMO not working
|
||||
|
||||
- Ensure all participants have OMEMO-capable clients
|
||||
- Verify device trust (fingerprint verification)
|
||||
- Check for conflicting security plugins
|
||||
|
||||
## Performance Tips
|
||||
|
||||
- Use **WebSocket** (faster than BOSH)
|
||||
- Enable **Stream Management** (`mod_smacks`)
|
||||
- Limit **MAM page size** (default: 50 messages)
|
||||
- Use **Client State Indication** (`mod_csi`) to reduce traffic when inactive
|
||||
|
||||
## Compatibility
|
||||
|
||||
- **XMPP Servers**: Prosody, ejabberd, Openfire
|
||||
- **Browsers**: Modern browsers with WebSocket support
|
||||
|
||||
## License
|
||||
|
||||
Converse.js is licensed under MPL 2.0.
|
||||
|
||||
## Documentation
|
||||
|
||||
- Converse.js Documentation: https://conversejs.org/docs/html/
|
||||
- XMPP Standards: https://xmpp.org/extensions/
|
||||
36
xmppchat/templates/admin.tpl
Normal file
36
xmppchat/templates/admin.tpl
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
{{*
|
||||
* XMPP Chat Admin Configuration Template
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*}}
|
||||
<p>
|
||||
{{$page_intro}}
|
||||
</p>
|
||||
|
||||
<h4>{{$connection_title}}</h4>
|
||||
{{include file="field_input.tpl" field=$websocket_url}}
|
||||
{{include file="field_input.tpl" field=$bosh_url}}
|
||||
{{include file="field_input.tpl" field=$domain}}
|
||||
|
||||
<h4>{{$auth_title}}</h4>
|
||||
{{include file="field_checkbox.tpl" field=$auto_login}}
|
||||
{{include file="field_checkbox.tpl" field=$allow_anonymous}}
|
||||
|
||||
<h4>{{$features_title}}</h4>
|
||||
{{include file="field_checkbox.tpl" field=$enable_mam}}
|
||||
{{include file="field_checkbox.tpl" field=$enable_smacks}}
|
||||
{{include file="field_checkbox.tpl" field=$enable_omemo}}
|
||||
|
||||
<h4>{{$chatrooms_title}}</h4>
|
||||
{{include file="field_input.tpl" field=$default_muc}}
|
||||
|
||||
<p class="help-block">
|
||||
<strong>{{$help_omemo}}:</strong> {{$help_omemo_text}}
|
||||
<br>
|
||||
<strong>{{$help_muc}}:</strong> {{$help_muc_text}}
|
||||
<br>
|
||||
<strong>{{$help_anon}}:</strong> {{$help_anon_text}}
|
||||
</p>
|
||||
|
||||
<div class="submit">
|
||||
<input type="submit" name="page_site" value="{{$submit}}" />
|
||||
</div>
|
||||
34
xmppchat/templates/settings.tpl
Normal file
34
xmppchat/templates/settings.tpl
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
{{*
|
||||
* XMPP Chat User Settings Template
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*}}
|
||||
<p class="info-message">
|
||||
{{$info}}
|
||||
</p>
|
||||
|
||||
{{include file="field_checkbox.tpl" field=$user_enabled}}
|
||||
{{include file="field_checkbox.tpl" field=$use_custom}}
|
||||
|
||||
<div id="xmppchat-custom-fields" {{if !$use_custom.2}}style="display:none;"{{/if}}>
|
||||
{{include file="field_input.tpl" field=$custom_jid}}
|
||||
{{include file="field_password.tpl" field=$custom_password}}
|
||||
{{include file="field_input.tpl" field=$custom_websocket}}
|
||||
{{include file="field_input.tpl" field=$custom_bosh}}
|
||||
|
||||
<p class="settings-help-text">
|
||||
<strong>{{$security_title}}:</strong> {{$security_text}}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var checkbox = document.getElementById('id_use_custom');
|
||||
var customFields = document.getElementById('xmppchat-custom-fields');
|
||||
|
||||
if (checkbox && customFields) {
|
||||
checkbox.addEventListener('change', function() {
|
||||
customFields.style.display = this.checked ? 'block' : 'none';
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
61
xmppchat/templates/xmppchat.tpl
Normal file
61
xmppchat/templates/xmppchat.tpl
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
{{*
|
||||
* XMPP Chat Widget Template (Converse.js)
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*}}
|
||||
<link rel="stylesheet" href="addon/xmppchat/vendor/converse.min.css">
|
||||
<style>
|
||||
.media {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
<div id="conversejs-container"></div>
|
||||
<script src="addon/xmppchat/vendor/converse.min.js"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var converseConfig = {
|
||||
{{if $websocket_url}}
|
||||
websocket_url: '{{$websocket_url}}',
|
||||
{{/if}}
|
||||
{{if $bosh_url}}
|
||||
bosh_service_url: '{{$bosh_url}}',
|
||||
{{/if}}
|
||||
view_mode: 'overlayed',
|
||||
{{if $allow_anonymous}}
|
||||
authentication: 'anonymous',
|
||||
auto_login: false,
|
||||
{{else if $auto_login && $jid}}
|
||||
authentication: 'login',
|
||||
auto_login: true,
|
||||
jid: '{{$jid}}',
|
||||
{{if $password}}
|
||||
password: '{{$password}}',
|
||||
{{/if}}
|
||||
{{else}}
|
||||
authentication: 'login',
|
||||
auto_login: false,
|
||||
{{/if}}
|
||||
show_controlbox_by_default: false,
|
||||
enable_mam: {{$enable_mam}},
|
||||
enable_smacks: {{$enable_smacks}},
|
||||
message_archiving: 'always',
|
||||
muc_respect_autojoin: true,
|
||||
{{if $enable_omemo}}
|
||||
whitelisted_plugins: ['converse-omemo'],
|
||||
trusted: true,
|
||||
allow_message_corrections: 'all',
|
||||
{{/if}}
|
||||
{{if $default_muc}}
|
||||
auto_join_rooms: [
|
||||
{ jid: '{{$default_muc}}', nick: '{{$jid}}' }
|
||||
],
|
||||
{{/if}}
|
||||
theme: 'concord',
|
||||
allow_non_roster_messaging: true,
|
||||
show_desktop_notifications: true,
|
||||
play_sounds: false,
|
||||
notification_icon: '/images/friendica.svg'
|
||||
};
|
||||
|
||||
converse.initialize(converseConfig);
|
||||
});
|
||||
</script>
|
||||
50
xmppchat/vendor/converse.min.css
vendored
Normal file
50
xmppchat/vendor/converse.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
10
xmppchat/vendor/converse.min.js
vendored
Normal file
10
xmppchat/vendor/converse.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
47
xmppchat/xmppchat.config.php
Normal file
47
xmppchat/xmppchat.config.php
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
/**
|
||||
* XMPP Chat Addon Configuration
|
||||
*
|
||||
* Copy this file to config/xmppchat.config.php and adjust the values
|
||||
*/
|
||||
|
||||
return [
|
||||
'xmppchat' => [
|
||||
// Enable/disable the chat widget
|
||||
'enabled' => false,
|
||||
|
||||
// XMPP server WebSocket URL (preferred)
|
||||
// Example: 'wss://xmpp.example.org:5281/xmpp-websocket'
|
||||
'websocket_url' => '',
|
||||
|
||||
// XMPP server BOSH URL (fallback)
|
||||
// Example: 'https://xmpp.example.org/http-bind'
|
||||
'bosh_url' => '',
|
||||
|
||||
// XMPP domain
|
||||
// Example: 'example.org'
|
||||
'domain' => '',
|
||||
|
||||
// Attempt auto-login with Friendica credentials
|
||||
// WARNING: This requires matching XMPP accounts and secure password handling
|
||||
// Consider using SASL EXTERNAL or OAuth tokens instead
|
||||
'auto_login' => false,
|
||||
|
||||
// Allow anonymous login (users can join without XMPP account)
|
||||
// Requires server support for anonymous authentication
|
||||
'allow_anonymous' => false,
|
||||
|
||||
// Enable Message Archive Management (XEP-0313)
|
||||
'enable_mam' => true,
|
||||
|
||||
// Enable Stream Management (XEP-0198) for connection reliability
|
||||
'enable_smacks' => true,
|
||||
|
||||
// Enable OMEMO encryption (XEP-0384) for end-to-end encryption
|
||||
'enable_omemo' => false,
|
||||
|
||||
// Default chat room to auto-join on login
|
||||
// Example: 'lobby@conference.example.org'
|
||||
'default_muc' => '',
|
||||
],
|
||||
];
|
||||
210
xmppchat/xmppchat.php
Normal file
210
xmppchat/xmppchat.php
Normal file
|
|
@ -0,0 +1,210 @@
|
|||
<?php
|
||||
/**
|
||||
* Name: XMPP Chat
|
||||
* Description: Embeds Converse.js XMPP webchat client into Friendica
|
||||
* Version: 1.0
|
||||
* Author: Friendica Community
|
||||
* License: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
use Friendica\Core\Hook;
|
||||
use Friendica\Core\Renderer;
|
||||
use Friendica\DI;
|
||||
use Friendica\Core\Config\Util\ConfigFileManager;
|
||||
|
||||
function xmppchat_install()
|
||||
{
|
||||
Hook::register('load_config', 'addon/xmppchat/xmppchat.php', 'xmppchat_load_config');
|
||||
Hook::register('footer', 'addon/xmppchat/xmppchat.php', 'xmppchat_footer');
|
||||
Hook::register('addon_settings', 'addon/xmppchat/xmppchat.php', 'xmppchat_addon_settings');
|
||||
Hook::register('addon_settings_post', 'addon/xmppchat/xmppchat.php', 'xmppchat_addon_settings_post');
|
||||
DI::logger()->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;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue