You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

mod_room_metadata_component.lua 7.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. -- This module implements a generic metadata storage system for rooms.
  2. --
  3. -- VirtualHost "jitmeet.example.com"
  4. -- modules_enabled = {
  5. -- "room_metadata"
  6. -- }
  7. -- room_metadata_component = "metadata.jitmeet.example.com"
  8. -- main_muc = "conference.jitmeet.example.com"
  9. --
  10. -- Component "metadata.jitmeet.example.com" "room_metadata_component"
  11. -- muc_component = "conference.jitmeet.example.com"
  12. -- breakout_rooms_component = "breakout.jitmeet.example.com"
  13. local jid_node = require 'util.jid'.node;
  14. local json = require 'cjson.safe';
  15. local st = require 'util.stanza';
  16. local util = module:require 'util';
  17. local is_healthcheck_room = util.is_healthcheck_room;
  18. local get_room_from_jid = util.get_room_from_jid;
  19. local room_jid_match_rewrite = util.room_jid_match_rewrite;
  20. local internal_room_jid_match_rewrite = util.internal_room_jid_match_rewrite;
  21. local process_host_module = util.process_host_module;
  22. local COMPONENT_IDENTITY_TYPE = 'room_metadata';
  23. local FORM_KEY = 'muc#roominfo_jitsimetadata';
  24. local muc_component_host = module:get_option_string('muc_component');
  25. if muc_component_host == nil then
  26. module:log('error', 'No muc_component specified. No muc to operate on!');
  27. return;
  28. end
  29. local muc_domain_base = module:get_option_string('muc_mapper_domain_base');
  30. if not muc_domain_base then
  31. module:log('warn', 'No muc_domain_base option set.');
  32. return;
  33. end
  34. local breakout_rooms_component_host = module:get_option_string('breakout_rooms_component');
  35. module:log("info", "Starting room metadata for %s", muc_component_host);
  36. local main_muc_module;
  37. -- Utility functions
  38. function getMetadataJSON(room)
  39. local res, error = json.encode({
  40. type = COMPONENT_IDENTITY_TYPE,
  41. metadata = room.jitsiMetadata or {}
  42. });
  43. if not res then
  44. module:log('error', 'Error encoding data room:%s', room.jid, error);
  45. end
  46. return res;
  47. end
  48. -- Putting the information on the config form / disco-info allows us to save
  49. -- an extra message to users who join later.
  50. function getFormData(room)
  51. return {
  52. name = FORM_KEY;
  53. type = 'text-multi';
  54. label = 'Room metadata';
  55. value = getMetadataJSON(room);
  56. };
  57. end
  58. function broadcastMetadata(room)
  59. local json_msg = getMetadataJSON(room);
  60. for _, occupant in room:each_occupant() do
  61. send_json_msg(occupant.jid, internal_room_jid_match_rewrite(room.jid), json_msg)
  62. end
  63. end
  64. function send_json_msg(to_jid, room_jid, json_msg)
  65. local stanza = st.message({ from = module.host; to = to_jid; })
  66. :tag('json-message', { xmlns = 'http://jitsi.org/jitmeet', room = room_jid }):text(json_msg):up();
  67. module:send(stanza);
  68. end
  69. -- Handling events
  70. function room_created(event)
  71. local room = event.room;
  72. if is_healthcheck_room(room.jid) then
  73. return ;
  74. end
  75. if not room.jitsiMetadata then
  76. room.jitsiMetadata = {};
  77. end
  78. end
  79. function on_message(event)
  80. local session = event.origin;
  81. -- Check the type of the incoming stanza to avoid loops:
  82. if event.stanza.attr.type == 'error' then
  83. return; -- We do not want to reply to these, so leave.
  84. end
  85. if not session or not session.jitsi_web_query_room then
  86. return false;
  87. end
  88. local message = event.stanza:get_child(COMPONENT_IDENTITY_TYPE, 'http://jitsi.org/jitmeet');
  89. local messageText = message:get_text();
  90. if not message or not messageText then
  91. return false;
  92. end
  93. local roomJid = message.attr.room;
  94. local room = get_room_from_jid(room_jid_match_rewrite(roomJid));
  95. if not room then
  96. module:log('warn', 'No room found found for %s/%s',
  97. session.jitsi_web_query_prefix, session.jitsi_web_query_room);
  98. return false;
  99. end
  100. -- check that the participant requesting is a moderator and is an occupant in the room
  101. local from = event.stanza.attr.from;
  102. local occupant = room:get_occupant_by_real_jid(from);
  103. if not occupant then
  104. module:log('warn', 'No occupant %s found for %s', from, room.jid);
  105. return false;
  106. end
  107. local jsonData, error = json.decode(messageText);
  108. if jsonData == nil then -- invalid JSON
  109. module:log("error", "Invalid JSON message: %s error:%s", messageText, error);
  110. return false;
  111. end
  112. if jsonData.key == nil or jsonData.data == nil then
  113. module:log("error", "Invalid JSON payload, key or data are missing: %s", messageText);
  114. return false;
  115. end
  116. if occupant.role ~= 'moderator' then
  117. -- will return a non nil filtered data to use, if it is nil, it is not allowed
  118. local res = module:context(muc_domain_base):fire_event('jitsi-metadata-allow-moderation',
  119. { room = room; actor = occupant; key = jsonData.key ; data = jsonData.data; session = session; });
  120. if not res then
  121. module:log('warn', 'Occupant %s is not moderator and not allowed this operation for %s', from, room.jid);
  122. return false;
  123. end
  124. jsonData.data = res;
  125. end
  126. room.jitsiMetadata[jsonData.key] = jsonData.data;
  127. broadcastMetadata(room);
  128. -- fire and event for the change
  129. main_muc_module:fire_event('jitsi-metadata-updated', { room = room; actor = occupant; key = jsonData.key; });
  130. return true;
  131. end
  132. -- Module operations
  133. -- handle messages to this component
  134. module:hook("message/host", on_message);
  135. -- operates on already loaded main muc module
  136. function process_main_muc_loaded(main_muc, host_module)
  137. main_muc_module = host_module;
  138. module:log('debug', 'Main muc loaded');
  139. module:log("info", "Hook to muc events on %s", muc_component_host);
  140. host_module:hook("muc-room-created", room_created, -1);
  141. host_module:hook('muc-disco#info', function (event)
  142. local room = event.room;
  143. table.insert(event.form, getFormData(room));
  144. end);
  145. host_module:hook("muc-config-form", function(event)
  146. local room = event.room;
  147. table.insert(event.form, getFormData(room));
  148. end);
  149. -- The room metadata was updated internally (from another module).
  150. host_module:hook("room-metadata-changed", function(event)
  151. broadcastMetadata(event.room);
  152. end);
  153. end
  154. -- process or waits to process the main muc component
  155. process_host_module(muc_component_host, function(host_module, host)
  156. local muc_module = prosody.hosts[host].modules.muc;
  157. if muc_module then
  158. process_main_muc_loaded(muc_module, host_module);
  159. else
  160. module:log('debug', 'Will wait for muc to be available');
  161. prosody.hosts[host].events.add_handler('module-loaded', function(event)
  162. if (event.module == 'muc') then
  163. process_main_muc_loaded(prosody.hosts[host].modules.muc, host_module);
  164. end
  165. end);
  166. end
  167. end);
  168. -- breakout rooms support
  169. function process_breakout_muc_loaded(breakout_muc, host_module)
  170. module:log('debug', 'Breakout rooms muc loaded');
  171. module:log("info", "Hook to muc events on %s", breakout_rooms_component_host);
  172. host_module:hook("muc-room-created", room_created, -1);
  173. host_module:hook('muc-disco#info', function (event)
  174. local room = event.room;
  175. table.insert(event.form, getFormData(room));
  176. end);
  177. host_module:hook("muc-config-form", function(event)
  178. local room = event.room;
  179. table.insert(event.form, getFormData(room));
  180. end);
  181. end
  182. if breakout_rooms_component_host then
  183. process_host_module(breakout_rooms_component_host, function(host_module, host)
  184. local muc_module = prosody.hosts[host].modules.muc;
  185. if muc_module then
  186. process_breakout_muc_loaded(muc_module, host_module);
  187. else
  188. module:log('debug', 'Will wait for muc to be available');
  189. prosody.hosts[host].events.add_handler('module-loaded', function(event)
  190. if (event.module == 'muc') then
  191. process_breakout_muc_loaded(prosody.hosts[host].modules.muc, host_module);
  192. end
  193. end);
  194. end
  195. end);
  196. end