Compare commits

...

6 Commits

Author SHA1 Message Date
Dele Olajide
4f9fc75347
Merge pull request #898 from igniterealtime/xep-0483-online-meetings
Implement XEP: 0483 HTTP Online Meetings
2025-07-21 12:05:52 +01:00
Dele Olajide
364d1a837f
Update plugins/meet/src/main/java/org/jivesoftware/spark/plugin/ofmeet/SparkMeetPlugin.java
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-21 12:03:12 +01:00
Dele Olajide
a0f28184ad
Update plugins/meet/src/main/java/org/jivesoftware/spark/plugin/ofmeet/SparkMeetPlugin.java
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-21 12:02:23 +01:00
Dele Olajide
43a37bf697
Update plugins/meet/src/main/java/org/jivesoftware/spark/plugin/ofmeet/QueryRequest.java
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-21 11:43:29 +01:00
Dele Olajide
c4affe809d update documentation 2025-07-21 11:31:59 +01:00
Dele Olajide
b6771fa03c First attempt at implementing xep-0483. Supports jitsi, galene and ohun 2025-07-20 22:54:39 +01:00
7 changed files with 181 additions and 28 deletions

View File

@ -10,10 +10,10 @@
</parent>
<artifactId>meet</artifactId>
<version>0.0.9</version>
<version>0.2.0</version>
<name>Pade Meetings Plugin</name>
<description>Adds support for Pade Meetings to the Spark IM client.</description>
<name>Online Meetings Plugin</name>
<description>Adds support for XEP-0483: HTTP Online Meetings to the Spark IM client.</description>
<contributors>
<contributor>

View File

@ -1,5 +1,5 @@
This is a plugin for Spark that allows users to join audio and video conferences hosted by [Pade Meetings](https://github.com/igniterealtime/openfire-pade-plugin/releases).
See the documentation for more details. It provides a button from a Multi User Chat (MUC) room or chat window within the Spark client, to open a Chrome window using the URL for the Jitsi Meet web client.
This is a plugin for Spark that allows users to join audio and video conferences supported by [XEP-0483: HTTP Online Meetings](https://xmpp.org/extensions/xep-0483.html)
See the documentation for more details. It provides a button from a Multi User Chat (MUC) room or chat window within the Spark client, to open a Chrome window using the URL provided.
It uses Electron instead of depending on Chrome installed and configured as the default browser.
![](https://user-images.githubusercontent.com/110731/91915397-5d304600-ecb2-11ea-93b1-d822f13f1509.png)

View File

@ -43,19 +43,17 @@ public class ChatRoomDecorator
ofmeetButton = new RolloverButton(SparkRes.getImageIcon("PADE_ICON"));
ofmeetButton.setToolTipText(GraphicUtils.createToolTip(SparkMeetResource.getString("name")));
final String roomId = getNode(room.getBareJid().toString());
final String sessionID = roomId + "-" + System.currentTimeMillis();
final String sessionID = String.valueOf(System.currentTimeMillis());
ofmeetButton.addActionListener(event -> {
String newUrl, newRoomId;
String newUrl, newRoomId = roomId + "-" + sessionID;
if ("groupchat".equals(room.getChatType().toString()))
{
newRoomId = roomId + "-" + sessionID;
newUrl = plugin.url + newRoomId;
plugin.handleClick(newUrl, room, newUrl, Message.Type.groupchat);
} else {
newRoomId = sessionID;
newUrl = plugin.url + newRoomId;
plugin.handleClick(newUrl, room, newUrl, Message.Type.chat);
}

View File

@ -0,0 +1,94 @@
/**
* Copyright (C) 2004-2011 Jive Software. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.spark.plugin.ofmeet;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.XmlEnvironment;
import org.jivesoftware.smack.provider.IQProvider;
import org.jivesoftware.smack.xml.XmlPullParser;
import org.jivesoftware.smack.xml.XmlPullParserException;
import java.io.IOException;
/**
* An IQ packet that's a request for an online meeting URL
* according to XEP-0483.
*/
public class QueryRequest extends IQ
{
public static final String NAMESPACE = "urn:xmpp:http:online-meetings:0";
public static final String ELEMENT_NAME = "query";
private String type;
public String url = null;
public QueryRequest()
{
super( "query", NAMESPACE );
}
public QueryRequest(String type)
{
super( "query", NAMESPACE );
this.type = type;
}
@Override
protected IQChildElementXmlStringBuilder getIQChildElementBuilder( IQChildElementXmlStringBuilder buf )
{
buf.append(" type=\"" + type + "\"");
buf.rightAngleBracket();
return buf;
}
public static class Provider extends IQProvider<QueryRequest>
{
public Provider()
{
super();
}
public QueryRequest parse(XmlPullParser parser, int i, XmlEnvironment xmlEnvironment) throws XmlPullParserException, IOException
{
final QueryRequest queryRequest = new QueryRequest();
boolean done = false;
while ( !done )
{
XmlPullParser.Event eventType = parser.next();
if ( eventType == XmlPullParser.Event.START_ELEMENT )
{
if ( parser.getName().equals( "url" ) )
{
queryRequest.url = parser.nextText();
}
}
else if ( eventType == XmlPullParser.Event.END_ELEMENT )
{
if ( parser.getName().equals( "query" ) )
{
done = true;
}
}
}
return queryRequest;
}
}
}

View File

@ -28,7 +28,14 @@ import java.lang.reflect.*;
import org.jivesoftware.Spark;
import org.jivesoftware.spark.*;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.provider.ProviderManager;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
import org.jivesoftware.smackx.disco.packet.DiscoverInfo;
import org.jivesoftware.spark.plugin.*;
import org.jivesoftware.spark.ui.*;
import org.jivesoftware.spark.util.log.*;
@ -36,7 +43,7 @@ import org.jivesoftware.spark.util.log.*;
import org.jitsi.util.OSUtils;
import de.mxro.process.*;
import org.jxmpp.jid.parts.*;
import org.jxmpp.jid.impl.JidCreate;
public class SparkMeetPlugin implements Plugin, ChatRoomListener, GlobalMessageListener
{
@ -54,6 +61,7 @@ public class SparkMeetPlugin implements Plugin, ChatRoomListener, GlobalMessageL
public void initialize()
{
ProviderManager.addIQProvider("query", QueryRequest.NAMESPACE, new QueryRequest.Provider());
checkNatives();
chatManager = SparkManager.getChatManager();
@ -87,8 +95,67 @@ public class SparkMeetPlugin implements Plugin, ChatRoomListener, GlobalMessageL
chatManager.addGlobalMessageListener(this);
SparkMeetPreference preference = new SparkMeetPreference(this);
SparkManager.getPreferenceManager().addPreference(preference);
SparkManager.getPreferenceManager().addPreference(preference);
ServiceDiscoveryManager discoManager = ServiceDiscoveryManager.getInstanceFor(SparkManager.getConnection());
DiscoverInfo discoverInfo = null;
String serverJid = SparkManager.getSessionManager().getServerAddress().toString();
try {
discoverInfo = discoManager.discoverInfo(JidCreate.domainBareFrom(serverJid));
}
catch (Exception e) {
Log.debug("Unable to disco " + serverJid);
}
boolean jitsiAvailable = false;
boolean galeneAvailable = false;
boolean ohunAvailable = false;
if (discoverInfo != null) {
jitsiAvailable = discoverInfo.containsFeature("urn:xmpp:http:online-meetings#jitsi");
galeneAvailable = discoverInfo.containsFeature("urn:xmpp:http:online-meetings#galene");
ohunAvailable = discoverInfo.containsFeature("urn:xmpp:http:online-meetings#ohun");
}
String sUrl = null;
if (jitsiAvailable) {
sUrl = getServerUrl("jitsi");
}
else
if (galeneAvailable) {
sUrl = getServerUrl("galene");
}
else
if (ohunAvailable) {
sUrl = getServerUrl("ohun");
}
if (sUrl != null) url = sUrl;
}
private String getServerUrl(String app) {
String serverUrl = null;
try {
QueryRequest request = new QueryRequest(app);
request.setTo(JidCreate.fromOrThrowUnchecked(SparkManager.getSessionManager().getServerAddress()));
request.setType(IQ.Type.get);
IQ result = SparkManager.getConnection().createStanzaCollectorAndSend(request).nextResultOrThrow();
QueryRequest response = (QueryRequest) result;
Log.debug("SparkMeet response: url=" + response.url);
if (response.url != null) serverUrl = response.url + "/";
} catch (Exception e) {
Log.warning("Unable to get meet url from server for app type " + app);
}
return serverUrl;
}
public void commit(String url) {
this.url = url;
@ -109,6 +176,7 @@ public class SparkMeetPlugin implements Plugin, ChatRoomListener, GlobalMessageL
{
Log.warning("shutdown");
chatManager.removeChatRoomListener(this);
ProviderManager.removeIQProvider("query", QueryRequest.NAMESPACE);
if (electronThread != null) electronThread.destory();
electronThread = null;
@ -354,7 +422,7 @@ public class SparkMeetPlugin implements Plugin, ChatRoomListener, GlobalMessageL
jarFileSuffix = "-darwin-x64.zip";
}
InputStream inputStream = new URL("https://github.com/electron/electron/releases/download/v10.1.1/electron-v10.1.1" + jarFileSuffix).openStream();
InputStream inputStream = new URL("https://github.com/electron/electron/releases/download/v37.2.3/electron-v37.2.3" + jarFileSuffix).openStream();
ZipInputStream zipIn = new ZipInputStream(inputStream);
ZipEntry entry = zipIn.getNextEntry();
@ -401,19 +469,12 @@ public class SparkMeetPlugin implements Plugin, ChatRoomListener, GlobalMessageL
System.setProperty("java.library.path", newLibPath);
// this will reload the new setting
try {
@SuppressWarnings("JavaReflectionMemberAccess")
Field fieldSysPath = ClassLoader.class.getDeclaredField("sys_paths");
fieldSysPath.setAccessible(true);
fieldSysPath.set(System.class.getClassLoader(), null);
} catch (NoSuchFieldException ignored) {
// Happens on non Oracle JDK but has no influence since there is no cached field that needs a reset
}
Log.warning("Unable to modify 'java.library.path' dynamically. Please ensure the library path includes: " + libPath);
}
}
catch (Exception e)
{
Log.warning("Error during loading of Pade Meetings plugin", e);
Log.warning(e.getMessage(), e);
}
}

View File

@ -28,7 +28,7 @@ public class SparkMeetPreference implements Preference {
public static final String NAMESPACE = "ofmeet";
private SparkMeetPlugin plugin;
private final PadePanel panel = new PadePanel();
private final MeetingPanel panel = new MeetingPanel();
public SparkMeetPreference(SparkMeetPlugin plugin) {
this.plugin = plugin;
@ -95,12 +95,12 @@ public class SparkMeetPreference implements Preference {
}
private static class PadePanel extends JPanel {
private static class MeetingPanel extends JPanel {
private static final long serialVersionUID = -5992704440953686499L;
private final JTextArea txtMessage = new JTextArea();
private JLabel url = new JLabel(SparkMeetResource.getString("preference.url"));
PadePanel() {
MeetingPanel() {
txtMessage.setBorder(UIManager.getLookAndFeelDefaults().getBorder("TextField.border"));
txtMessage.setLineWrap(true);
setLayout(new VerticalFlowLayout());

View File

@ -1,6 +1,6 @@
name= Pade Meetings
name= Online Meetings
preference.sparkmeetEnabled = Enable Pade meeting
preference.url = Jitsi Meet URL
preference.title = Pade Meetings Settings
preference.sparkmeetEnabled = Enable Online Meetings
preference.url = Online Meetings Base URL
preference.title = Online Meetings Settings