Files

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);
};