234 lines
19 KiB
JavaScript
234 lines
19 KiB
JavaScript
|
|
module.exports = function(RED) {
|
||
|
|
var https = require('https');
|
||
|
|
var http = require('http');
|
||
|
|
var url = require('url');
|
||
|
|
|
||
|
|
var OPERATIONS = {
|
||
|
|
// --- Room ---
|
||
|
|
'room:list': { method:'GET', path:'/ocs/v2.php/apps/spreed/api/v4/room', query:['noStatusUpdate','includeStatus','modifiedSince','includeLastMessage'] },
|
||
|
|
'room:listed': { method:'GET', path:'/ocs/v2.php/apps/spreed/api/v4/listed-room', query:['searchTerm'] },
|
||
|
|
'room:get': { method:'GET', path:'/ocs/v2.php/apps/spreed/api/v4/room/{token}' },
|
||
|
|
'room:create': { method:'POST', path:'/ocs/v2.php/apps/spreed/api/v4/room', body:['roomType','roomName','objectType','objectId','password','readOnly','listable','description','emoji'] },
|
||
|
|
'room:rename': { method:'PUT', path:'/ocs/v2.php/apps/spreed/api/v4/room/{token}', body:['roomName'] },
|
||
|
|
'room:delete': { method:'DELETE', path:'/ocs/v2.php/apps/spreed/api/v4/room/{token}' },
|
||
|
|
'room:noteToSelf': { method:'GET', path:'/ocs/v2.php/apps/spreed/api/v4/room/note-to-self' },
|
||
|
|
'room:description': { method:'PUT', path:'/ocs/v2.php/apps/spreed/api/v4/room/{token}/description', body:['description'] },
|
||
|
|
'room:password': { method:'PUT', path:'/ocs/v2.php/apps/spreed/api/v4/room/{token}/password', body:['password'] },
|
||
|
|
'room:readOnly': { method:'PUT', path:'/ocs/v2.php/apps/spreed/api/v4/room/{token}/read-only', body:['state'] },
|
||
|
|
'room:listable': { method:'PUT', path:'/ocs/v2.php/apps/spreed/api/v4/room/{token}/listable', body:['scope'] },
|
||
|
|
'room:archive': { method:'POST', path:'/ocs/v2.php/apps/spreed/api/v4/room/{token}/archive' },
|
||
|
|
'room:unarchive': { method:'DELETE', path:'/ocs/v2.php/apps/spreed/api/v4/room/{token}/archive' },
|
||
|
|
'room:important': { method:'POST', path:'/ocs/v2.php/apps/spreed/api/v4/room/{token}/important' },
|
||
|
|
'room:unimportant': { method:'DELETE', path:'/ocs/v2.php/apps/spreed/api/v4/room/{token}/important' },
|
||
|
|
'room:sensitive': { method:'POST', path:'/ocs/v2.php/apps/spreed/api/v4/room/{token}/sensitive' },
|
||
|
|
'room:insensitive': { method:'DELETE', path:'/ocs/v2.php/apps/spreed/api/v4/room/{token}/sensitive' },
|
||
|
|
'room:messageExpiration': { method:'POST', path:'/ocs/v2.php/apps/spreed/api/v4/room/{token}/message-expiration', body:['seconds'] },
|
||
|
|
'room:capabilities': { method:'GET', path:'/ocs/v2.php/apps/spreed/api/v4/room/{token}/capabilities' },
|
||
|
|
'room:scheduleMeeting': { method:'POST', path:'/ocs/v2.php/apps/spreed/api/v4/room/{token}/meeting', body:['calendarUri','start','end','title','description'] },
|
||
|
|
// --- Favorites ---
|
||
|
|
'favorite:add': { method:'POST', path:'/ocs/v2.php/apps/spreed/api/v4/room/{token}/favorite' },
|
||
|
|
'favorite:remove': { method:'DELETE', path:'/ocs/v2.php/apps/spreed/api/v4/room/{token}/favorite' },
|
||
|
|
// --- Notification ---
|
||
|
|
'notify:level': { method:'POST', path:'/ocs/v2.php/apps/spreed/api/v4/room/{token}/notify', body:['level'] },
|
||
|
|
'notify:calls': { method:'POST', path:'/ocs/v2.php/apps/spreed/api/v4/room/{token}/notify-calls', body:['level'] },
|
||
|
|
// --- Public/Private ---
|
||
|
|
'room:public': { method:'POST', path:'/ocs/v2.php/apps/spreed/api/v4/room/{token}/public', body:['password'] },
|
||
|
|
'room:private': { method:'DELETE', path:'/ocs/v2.php/apps/spreed/api/v4/room/{token}/public' },
|
||
|
|
// --- Participants ---
|
||
|
|
'participant:list': { method:'GET', path:'/ocs/v2.php/apps/spreed/api/v4/room/{token}/participants', query:['includeStatus'] },
|
||
|
|
'participant:add': { method:'POST', path:'/ocs/v2.php/apps/spreed/api/v4/room/{token}/participants', body:['newParticipant','source'] },
|
||
|
|
'participant:removeSelf': { method:'DELETE', path:'/ocs/v2.php/apps/spreed/api/v4/room/{token}/participants/self' },
|
||
|
|
'participant:remove': { method:'DELETE', path:'/ocs/v2.php/apps/spreed/api/v4/room/{token}/attendees', query:['attendeeId'] },
|
||
|
|
'participant:join': { method:'POST', path:'/ocs/v2.php/apps/spreed/api/v4/room/{token}/participants/active', body:['password','force'] },
|
||
|
|
'participant:leave': { method:'DELETE', path:'/ocs/v2.php/apps/spreed/api/v4/room/{token}/participants/active' },
|
||
|
|
'participant:sessionState': { method:'PUT', path:'/ocs/v2.php/apps/spreed/api/v4/room/{token}/participants/state', body:['state'] },
|
||
|
|
'participant:promote': { method:'POST', path:'/ocs/v2.php/apps/spreed/api/v4/room/{token}/moderators', body:['attendeeId'] },
|
||
|
|
'participant:demote': { method:'DELETE', path:'/ocs/v2.php/apps/spreed/api/v4/room/{token}/moderators', query:['attendeeId'] },
|
||
|
|
// --- Permissions ---
|
||
|
|
'permissions:default': { method:'PUT', path:'/ocs/v2.php/apps/spreed/api/v4/room/{token}/permissions/default', body:['permissions'] },
|
||
|
|
'permissions:call': { method:'PUT', path:'/ocs/v2.php/apps/spreed/api/v4/room/{token}/permissions/call', body:['permissions'] },
|
||
|
|
'permissions:attendee': { method:'PUT', path:'/ocs/v2.php/apps/spreed/api/v4/room/{token}/attendees/permissions', body:['attendeeId','method','permissions'] },
|
||
|
|
// --- Chat ---
|
||
|
|
'chat:send': { method:'POST', path:'/ocs/v2.php/apps/spreed/api/v1/chat/{token}', body:['message','replyTo','silent','threadTitle','threadId'] },
|
||
|
|
'chat:receive': { method:'GET', path:'/ocs/v2.php/apps/spreed/api/v1/chat/{token}', query:['lookIntoFuture','limit','lastKnownMessageId','timeout','setReadMarker','threadId'] },
|
||
|
|
'chat:delete': { method:'DELETE', path:'/ocs/v2.php/apps/spreed/api/v1/chat/{token}/{messageId}' },
|
||
|
|
'chat:edit': { method:'PUT', path:'/ocs/v2.php/apps/spreed/api/v1/chat/{token}/{messageId}', body:['message'] },
|
||
|
|
'chat:context': { method:'GET', path:'/ocs/v2.php/apps/spreed/api/v1/chat/{token}/{messageId}/context', query:['limit','threadId'] },
|
||
|
|
'chat:reminderSet': { method:'POST', path:'/ocs/v2.php/apps/spreed/api/v1/chat/{token}/{messageId}/reminder', body:['timestamp'] },
|
||
|
|
'chat:reminderGet': { method:'GET', path:'/ocs/v2.php/apps/spreed/api/v1/chat/{token}/{messageId}/reminder' },
|
||
|
|
'chat:reminderDelete': { method:'DELETE', path:'/ocs/v2.php/apps/spreed/api/v1/chat/{token}/{messageId}/reminder' },
|
||
|
|
'chat:readMarker': { method:'POST', path:'/ocs/v2.php/apps/spreed/api/v1/chat/{token}/read', body:['lastReadMessage'] },
|
||
|
|
'chat:unread': { method:'DELETE', path:'/ocs/v2.php/apps/spreed/api/v1/chat/{token}/read' },
|
||
|
|
'chat:pin': { method:'POST', path:'/ocs/v2.php/apps/spreed/api/v1/chat/{token}/{messageId}/pin', body:['pinUntil'] },
|
||
|
|
'chat:unpin': { method:'DELETE', path:'/ocs/v2.php/apps/spreed/api/v1/chat/{token}/{messageId}/pin' },
|
||
|
|
'chat:clearHistory': { method:'DELETE', path:'/ocs/v2.php/apps/spreed/api/v1/chat/{token}' },
|
||
|
|
'chat:share': { method:'POST', path:'/ocs/v2.php/apps/spreed/api/v1/chat/{token}/share', body:['objectType','objectId','metaData'] },
|
||
|
|
'chat:sharedOverview': { method:'GET', path:'/ocs/v2.php/apps/spreed/api/v1/chat/{token}/share/overview', query:['limit'] },
|
||
|
|
'chat:sharedObjects': { method:'GET', path:'/ocs/v2.php/apps/spreed/api/v1/chat/{token}/share', query:['objectType','limit','lastKnownMessageId'] },
|
||
|
|
'chat:mentions': { method:'GET', path:'/ocs/v2.php/apps/spreed/api/v1/chat/{token}/mentions', query:['search','limit','includeStatus'] },
|
||
|
|
'chat:schedule': { method:'POST', path:'/ocs/v2.php/apps/spreed/api/v1/chat/{token}/schedule', body:['message','sendAt','replyTo','silent'] },
|
||
|
|
'chat:scheduledGet': { method:'GET', path:'/ocs/v2.php/apps/spreed/api/v1/chat/{token}/schedule' },
|
||
|
|
'chat:scheduledDelete': { method:'DELETE', path:'/ocs/v2.php/apps/spreed/api/v1/chat/{token}/schedule/{messageId}' },
|
||
|
|
// --- Call ---
|
||
|
|
'call:peers': { method:'GET', path:'/ocs/v2.php/apps/spreed/api/v4/call/{token}' },
|
||
|
|
'call:join': { method:'POST', path:'/ocs/v2.php/apps/spreed/api/v4/call/{token}', body:['flags','silent','recordingConsent'] },
|
||
|
|
'call:updateFlags': { method:'PUT', path:'/ocs/v2.php/apps/spreed/api/v4/call/{token}', body:['flags'] },
|
||
|
|
'call:leave': { method:'DELETE', path:'/ocs/v2.php/apps/spreed/api/v4/call/{token}', query:['all'] },
|
||
|
|
'call:ring': { method:'POST', path:'/ocs/v2.php/apps/spreed/api/v4/call/{token}/ring/{attendeeId}' },
|
||
|
|
// --- Reaction ---
|
||
|
|
'reaction:add': { method:'POST', path:'/ocs/v2.php/apps/spreed/api/v1/reaction/{token}/{messageId}', body:['reaction'] },
|
||
|
|
'reaction:delete': { method:'DELETE', path:'/ocs/v2.php/apps/spreed/api/v1/reaction/{token}/{messageId}', query:['reaction'] },
|
||
|
|
'reaction:list': { method:'GET', path:'/ocs/v2.php/apps/spreed/api/v1/reaction/{token}/{messageId}', query:['reaction'] },
|
||
|
|
// --- Poll ---
|
||
|
|
'poll:create': { method:'POST', path:'/ocs/v2.php/apps/spreed/api/v1/poll/{token}', body:['question','options','resultMode','maxVotes','draft','threadId'] },
|
||
|
|
'poll:get': { method:'GET', path:'/ocs/v2.php/apps/spreed/api/v1/poll/{token}/{pollId}' },
|
||
|
|
'poll:vote': { method:'POST', path:'/ocs/v2.php/apps/spreed/api/v1/poll/{token}/{pollId}', body:['optionIds'] },
|
||
|
|
'poll:close': { method:'DELETE', path:'/ocs/v2.php/apps/spreed/api/v1/poll/{token}/{pollId}' },
|
||
|
|
// --- Ban ---
|
||
|
|
'ban:list': { method:'GET', path:'/ocs/v2.php/apps/spreed/api/v1/ban/{token}' },
|
||
|
|
'ban:add': { method:'POST', path:'/ocs/v2.php/apps/spreed/api/v1/ban/{token}', body:['actorType','actorId','internalNote'] },
|
||
|
|
'ban:remove': { method:'DELETE', path:'/ocs/v2.php/apps/spreed/api/v1/ban/{token}/{banId}' },
|
||
|
|
// --- Avatar ---
|
||
|
|
'avatar:get': { method:'GET', path:'/ocs/v2.php/apps/spreed/api/v1/room/{token}/avatar' },
|
||
|
|
'avatar:emoji': { method:'POST', path:'/ocs/v2.php/apps/spreed/api/v1/room/{token}/avatar/emoji', body:['emoji','color'] },
|
||
|
|
'avatar:delete': { method:'DELETE', path:'/ocs/v2.php/apps/spreed/api/v1/room/{token}/avatar' },
|
||
|
|
// --- Breakout rooms ---
|
||
|
|
'breakout:configure': { method:'POST', path:'/ocs/v2.php/apps/spreed/api/v1/breakout-rooms/{token}', body:['mode','amount','attendeeMap'] },
|
||
|
|
'breakout:start': { method:'POST', path:'/ocs/v2.php/apps/spreed/api/v1/breakout-rooms/{token}/rooms' },
|
||
|
|
'breakout:stop': { method:'DELETE', path:'/ocs/v2.php/apps/spreed/api/v1/breakout-rooms/{token}/rooms' },
|
||
|
|
'breakout:remove': { method:'DELETE', path:'/ocs/v2.php/apps/spreed/api/v1/breakout-rooms/{token}' },
|
||
|
|
'breakout:broadcast': { method:'POST', path:'/ocs/v2.php/apps/spreed/api/v1/breakout-rooms/{token}/broadcast', body:['message'] },
|
||
|
|
'breakout:list': { method:'GET', path:'/ocs/v2.php/apps/spreed/api/v4/room/{token}/breakout-rooms' },
|
||
|
|
// --- Recording ---
|
||
|
|
'recording:start': { method:'POST', path:'/ocs/v2.php/apps/spreed/api/v1/recording/{token}', body:['status'] },
|
||
|
|
'recording:stop': { method:'DELETE', path:'/ocs/v2.php/apps/spreed/api/v1/recording/{token}' },
|
||
|
|
// --- Signaling ---
|
||
|
|
'signaling:settings': { method:'GET', path:'/ocs/v2.php/apps/spreed/api/v3/signaling/settings', query:['token'] },
|
||
|
|
// --- Files integration ---
|
||
|
|
'file:getRoomByFileId': { method:'GET', path:'/ocs/v2.php/apps/spreed/api/v1/file/{fileId}' },
|
||
|
|
'file:getRoomByShare': { method:'GET', path:'/ocs/v2.php/apps/spreed/api/v1/publicshare/{shareToken}' },
|
||
|
|
// --- Thread ---
|
||
|
|
'thread:recent': { method:'GET', path:'/ocs/v2.php/apps/spreed/api/v1/chat/{token}/threads/recent', query:['limit'] },
|
||
|
|
'thread:subscribed': { method:'GET', path:'/ocs/v2.php/apps/spreed/api/v1/chat/subscribed-threads', query:['limit','offset'] },
|
||
|
|
'thread:get': { method:'GET', path:'/ocs/v2.php/apps/spreed/api/v1/chat/{token}/threads/{threadId}' },
|
||
|
|
'thread:rename': { method:'PUT', path:'/ocs/v2.php/apps/spreed/api/v1/chat/{token}/threads/{threadId}', body:['threadTitle'] },
|
||
|
|
'thread:notify': { method:'POST', path:'/ocs/v2.php/apps/spreed/api/v1/chat/{token}/threads/{messageId}/notify', body:['level'] },
|
||
|
|
// --- User settings ---
|
||
|
|
'settings:set': { method:'POST', path:'/ocs/v2.php/apps/spreed/api/v1/settings/user', body:['key','value'] },
|
||
|
|
// --- Calendar integration ---
|
||
|
|
'calendar:dashboard': { method:'GET', path:'/ocs/v2.php/apps/spreed/api/v4/dashboard/events' },
|
||
|
|
'calendar:mutual': { method:'GET', path:'/ocs/v2.php/apps/spreed/api/v4/room/{token}/mutual-events' }
|
||
|
|
};
|
||
|
|
|
||
|
|
function TalkNode(config) {
|
||
|
|
RED.nodes.createNode(this, config);
|
||
|
|
var node = this;
|
||
|
|
this.configNode = RED.nodes.getNode(config.nextcloud);
|
||
|
|
this.operation = config.operation || 'room:list';
|
||
|
|
this.token = config.token; this.messageId = config.messageId;
|
||
|
|
this.attendeeId = config.attendeeId; this.pollId = config.pollId;
|
||
|
|
this.banId = config.banId; this.threadId = config.threadId; this.fileId = config.fileId;
|
||
|
|
this.shareToken = config.shareToken;
|
||
|
|
// Body fields
|
||
|
|
this.bodyMessage = config.bodyMessage; this.bodyRoomName = config.bodyRoomName;
|
||
|
|
this.bodyRoomType = config.bodyRoomType; this.bodyLevel = config.bodyLevel;
|
||
|
|
this.bodyReplyTo = config.bodyReplyTo; this.bodySilent = config.bodySilent;
|
||
|
|
this.bodyDescription = config.bodyDescription; this.bodyQuestion = config.bodyQuestion;
|
||
|
|
this.bodyPassword = config.bodyPassword; this.bodyEmoji = config.bodyEmoji;
|
||
|
|
this.bodyTimestamp = config.bodyTimestamp; this.bodyReaction = config.bodyReaction;
|
||
|
|
this.bodyActorType = config.bodyActorType; this.bodyActorId = config.bodyActorId;
|
||
|
|
this.bodyFlags = config.bodyFlags; this.bodyState = config.bodyState;
|
||
|
|
this.bodyKey = config.bodyKey; this.bodyValue = config.bodyValue;
|
||
|
|
this.bodyCalendarUri = config.bodyCalendarUri; this.bodyStart = config.bodyStart;
|
||
|
|
|
||
|
|
if (!this.configNode) { node.error("No Nextcloud configuration node selected"); return; }
|
||
|
|
|
||
|
|
node.on('input', function(msg) {
|
||
|
|
var op = OPERATIONS[msg.operation || node.operation];
|
||
|
|
if (!op) { node.error("Unknown operation: " + (msg.operation || node.operation), msg); return; }
|
||
|
|
|
||
|
|
var baseUrl = (node.configNode.baseUrl || '').replace(/\/+$/, '');
|
||
|
|
var username = node.configNode.credentials.username;
|
||
|
|
var password = node.configNode.credentials.password;
|
||
|
|
|
||
|
|
var path = op.path
|
||
|
|
.replace(/\{token\}/g, msg.token || node.token || '')
|
||
|
|
.replace(/\{messageId\}/g, msg.messageId || node.messageId || '')
|
||
|
|
.replace(/\{attendeeId\}/g, msg.attendeeId || node.attendeeId || '')
|
||
|
|
.replace(/\{pollId\}/g, msg.pollId || node.pollId || '')
|
||
|
|
.replace(/\{banId\}/g, msg.banId || node.banId || '')
|
||
|
|
.replace(/\{threadId\}/g, msg.threadId || node.threadId || '')
|
||
|
|
.replace(/\{fileId\}/g, msg.fileId || node.fileId || '')
|
||
|
|
.replace(/\{shareToken\}/g, msg.shareToken || node.shareToken || '');
|
||
|
|
|
||
|
|
var queryParts = [];
|
||
|
|
if (op.query) {
|
||
|
|
op.query.forEach(function(q) {
|
||
|
|
var v = msg[q] !== undefined ? msg[q] : getField(node, q);
|
||
|
|
if (v !== undefined && v !== '') queryParts.push(encodeURIComponent(q) + '=' + encodeURIComponent(v));
|
||
|
|
});
|
||
|
|
}
|
||
|
|
var fullUrl = baseUrl + path + (queryParts.length ? '?' + queryParts.join('&') : '');
|
||
|
|
var parsedUrl = url.parse(fullUrl);
|
||
|
|
|
||
|
|
var headers = {
|
||
|
|
'OCS-APIRequest': 'true', 'Accept': 'application/json',
|
||
|
|
'Authorization': 'Basic ' + Buffer.from(username + ':' + password).toString('base64')
|
||
|
|
};
|
||
|
|
|
||
|
|
var bodyObj = {};
|
||
|
|
if (op.body) {
|
||
|
|
op.body.forEach(function(b) {
|
||
|
|
var v = msg[b] !== undefined ? msg[b] : getField(node, b);
|
||
|
|
if (v !== undefined && v !== '') bodyObj[b] = v;
|
||
|
|
});
|
||
|
|
}
|
||
|
|
// Handle special array fields
|
||
|
|
if (msg.options && Array.isArray(msg.options)) bodyObj.options = msg.options;
|
||
|
|
if (msg.optionIds && Array.isArray(msg.optionIds)) bodyObj.optionIds = msg.optionIds;
|
||
|
|
|
||
|
|
var bodyStr = null;
|
||
|
|
if (Object.keys(bodyObj).length > 0) {
|
||
|
|
headers['Content-Type'] = 'application/json';
|
||
|
|
bodyStr = JSON.stringify(bodyObj);
|
||
|
|
}
|
||
|
|
|
||
|
|
var opts = {
|
||
|
|
hostname: parsedUrl.hostname,
|
||
|
|
port: parsedUrl.port || (parsedUrl.protocol === 'https:' ? 443 : 80),
|
||
|
|
path: parsedUrl.path, method: op.method, headers: headers, rejectUnauthorized: false
|
||
|
|
};
|
||
|
|
|
||
|
|
node.log(op.method + ' ' + fullUrl);
|
||
|
|
|
||
|
|
var transport = parsedUrl.protocol === 'https:' ? https : http;
|
||
|
|
var req = transport.request(opts, function(res) {
|
||
|
|
var body = '';
|
||
|
|
res.on('data', function(chunk) { body += chunk; });
|
||
|
|
res.on('end', function() {
|
||
|
|
msg.statusCode = res.statusCode; msg.operation = msg.operation || node.operation;
|
||
|
|
try { msg.payload = JSON.parse(body); } catch(e) { msg.payload = body; }
|
||
|
|
node.send(msg);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
req.on('error', function(err) { msg.error = err.message; node.error(op.method + ' ' + fullUrl + ' — ' + err.message, msg); });
|
||
|
|
if (bodyStr) req.write(bodyStr);
|
||
|
|
req.end();
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function getField(node, name) {
|
||
|
|
var m = { 'message': node.bodyMessage, 'roomName': node.bodyRoomName, 'roomType': node.bodyRoomType,
|
||
|
|
'level': node.bodyLevel, 'replyTo': node.bodyReplyTo, 'silent': node.bodySilent,
|
||
|
|
'description': node.bodyDescription, 'question': node.bodyQuestion, 'password': node.bodyPassword,
|
||
|
|
'emoji': node.bodyEmoji, 'timestamp': node.bodyTimestamp, 'reaction': node.bodyReaction,
|
||
|
|
'actorType': node.bodyActorType, 'actorId': node.bodyActorId, 'flags': node.bodyFlags,
|
||
|
|
'state': node.bodyState, 'key': node.bodyKey, 'value': node.bodyValue,
|
||
|
|
'calendarUri': node.bodyCalendarUri, 'start': node.bodyStart, 'threadTitle': node.bodyThreadTitle,
|
||
|
|
'sendAt': node.bodySendAt, 'pinUntil': node.bodyPinUntil, 'mode': node.bodyMode,
|
||
|
|
'amount': node.bodyAmount, 'status': node.bodyStatus };
|
||
|
|
return m[name];
|
||
|
|
}
|
||
|
|
|
||
|
|
RED.nodes.registerType("talk", TalkNode);
|
||
|
|
};
|