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> </parent>
<artifactId>meet</artifactId> <artifactId>meet</artifactId>
<version>0.0.9</version> <version>0.2.0</version>
<name>Pade Meetings Plugin</name> <name>Online Meetings Plugin</name>
<description>Adds support for Pade Meetings to the Spark IM client.</description> <description>Adds support for XEP-0483: HTTP Online Meetings to the Spark IM client.</description>
<contributors> <contributors>
<contributor> <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). 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 for the Jitsi Meet web client. 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. 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) ![](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 = new RolloverButton(SparkRes.getImageIcon("PADE_ICON"));
ofmeetButton.setToolTipText(GraphicUtils.createToolTip(SparkMeetResource.getString("name"))); ofmeetButton.setToolTipText(GraphicUtils.createToolTip(SparkMeetResource.getString("name")));
final String roomId = getNode(room.getBareJid().toString()); final String roomId = getNode(room.getBareJid().toString());
final String sessionID = roomId + "-" + System.currentTimeMillis(); final String sessionID = String.valueOf(System.currentTimeMillis());
ofmeetButton.addActionListener(event -> { ofmeetButton.addActionListener(event -> {
String newUrl, newRoomId; String newUrl, newRoomId = roomId + "-" + sessionID;
if ("groupchat".equals(room.getChatType().toString())) if ("groupchat".equals(room.getChatType().toString()))
{ {
newRoomId = roomId + "-" + sessionID;
newUrl = plugin.url + newRoomId; newUrl = plugin.url + newRoomId;
plugin.handleClick(newUrl, room, newUrl, Message.Type.groupchat); plugin.handleClick(newUrl, room, newUrl, Message.Type.groupchat);
} else { } else {
newRoomId = sessionID;
newUrl = plugin.url + newRoomId; newUrl = plugin.url + newRoomId;
plugin.handleClick(newUrl, room, newUrl, Message.Type.chat); 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.spark.*; import org.jivesoftware.spark.*;
import org.jivesoftware.smack.packet.Message; 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.plugin.*;
import org.jivesoftware.spark.ui.*; import org.jivesoftware.spark.ui.*;
import org.jivesoftware.spark.util.log.*; import org.jivesoftware.spark.util.log.*;
@ -36,7 +43,7 @@ import org.jivesoftware.spark.util.log.*;
import org.jitsi.util.OSUtils; import org.jitsi.util.OSUtils;
import de.mxro.process.*; import de.mxro.process.*;
import org.jxmpp.jid.parts.*; import org.jxmpp.jid.parts.*;
import org.jxmpp.jid.impl.JidCreate;
public class SparkMeetPlugin implements Plugin, ChatRoomListener, GlobalMessageListener public class SparkMeetPlugin implements Plugin, ChatRoomListener, GlobalMessageListener
{ {
@ -54,6 +61,7 @@ public class SparkMeetPlugin implements Plugin, ChatRoomListener, GlobalMessageL
public void initialize() public void initialize()
{ {
ProviderManager.addIQProvider("query", QueryRequest.NAMESPACE, new QueryRequest.Provider());
checkNatives(); checkNatives();
chatManager = SparkManager.getChatManager(); chatManager = SparkManager.getChatManager();
@ -87,8 +95,67 @@ public class SparkMeetPlugin implements Plugin, ChatRoomListener, GlobalMessageL
chatManager.addGlobalMessageListener(this); chatManager.addGlobalMessageListener(this);
SparkMeetPreference preference = new SparkMeetPreference(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) { public void commit(String url) {
this.url = url; this.url = url;
@ -109,6 +176,7 @@ public class SparkMeetPlugin implements Plugin, ChatRoomListener, GlobalMessageL
{ {
Log.warning("shutdown"); Log.warning("shutdown");
chatManager.removeChatRoomListener(this); chatManager.removeChatRoomListener(this);
ProviderManager.removeIQProvider("query", QueryRequest.NAMESPACE);
if (electronThread != null) electronThread.destory(); if (electronThread != null) electronThread.destory();
electronThread = null; electronThread = null;
@ -354,7 +422,7 @@ public class SparkMeetPlugin implements Plugin, ChatRoomListener, GlobalMessageL
jarFileSuffix = "-darwin-x64.zip"; 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); ZipInputStream zipIn = new ZipInputStream(inputStream);
ZipEntry entry = zipIn.getNextEntry(); ZipEntry entry = zipIn.getNextEntry();
@ -401,19 +469,12 @@ public class SparkMeetPlugin implements Plugin, ChatRoomListener, GlobalMessageL
System.setProperty("java.library.path", newLibPath); System.setProperty("java.library.path", newLibPath);
// this will reload the new setting // this will reload the new setting
try { Log.warning("Unable to modify 'java.library.path' dynamically. Please ensure the library path includes: " + libPath);
@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
}
} }
} }
catch (Exception e) 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"; public static final String NAMESPACE = "ofmeet";
private SparkMeetPlugin plugin; private SparkMeetPlugin plugin;
private final PadePanel panel = new PadePanel(); private final MeetingPanel panel = new MeetingPanel();
public SparkMeetPreference(SparkMeetPlugin plugin) { public SparkMeetPreference(SparkMeetPlugin plugin) {
this.plugin = 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 static final long serialVersionUID = -5992704440953686499L;
private final JTextArea txtMessage = new JTextArea(); private final JTextArea txtMessage = new JTextArea();
private JLabel url = new JLabel(SparkMeetResource.getString("preference.url")); private JLabel url = new JLabel(SparkMeetResource.getString("preference.url"));
PadePanel() { MeetingPanel() {
txtMessage.setBorder(UIManager.getLookAndFeelDefaults().getBorder("TextField.border")); txtMessage.setBorder(UIManager.getLookAndFeelDefaults().getBorder("TextField.border"));
txtMessage.setLineWrap(true); txtMessage.setLineWrap(true);
setLayout(new VerticalFlowLayout()); setLayout(new VerticalFlowLayout());

View File

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