social_stream 0.22.1 → 0.23.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. data/base/app/assets/javascripts/activities.js.erb +0 -85
  2. data/base/app/assets/javascripts/social_stream.comments.js +63 -0
  3. data/base/app/assets/javascripts/social_stream.objects.js +23 -0
  4. data/base/app/assets/javascripts/social_stream.timeline.js +37 -10
  5. data/base/app/assets/javascripts/social_stream.wall.js.erb +135 -118
  6. data/base/app/assets/stylesheets/cheesecake.css.scss +6 -3
  7. data/base/app/models/activity_object.rb +18 -0
  8. data/base/app/models/activity_object_property.rb +5 -0
  9. data/base/app/views/activities/_new.html.erb +1 -1
  10. data/base/app/views/activities/_wall.html.erb +1 -1
  11. data/base/app/views/cheesecake/_cheesecake.html.erb +44 -10
  12. data/base/app/views/cheesecake/_index.html.erb +18 -130
  13. data/base/app/views/cheesecake/_sector_form.html.erb +176 -17
  14. data/base/app/views/comments/_new.html.erb +0 -14
  15. data/base/app/views/objects/_show.html.erb +6 -0
  16. data/base/app/views/toolbar/_home.html.erb +1 -1
  17. data/base/app/views/toolbar/_messages.html.erb +1 -1
  18. data/base/app/views/toolbar/_profile.html.erb +1 -1
  19. data/base/config/locales/es.yml +7 -0
  20. data/base/db/migrate/20120302215722_activity_object_properties.rb +24 -0
  21. data/base/lib/social_stream/ability/base.rb +2 -2
  22. data/base/lib/social_stream/base/version.rb +1 -1
  23. data/documents/app/assets/javascripts/social_stream-documents.js +5 -1
  24. data/documents/app/models/document.rb +4 -3
  25. data/documents/app/models/picture.rb +4 -0
  26. data/documents/lib/social_stream/documents/version.rb +1 -1
  27. data/documents/social_stream-documents.gemspec +1 -1
  28. data/events/app/assets/images/poster.png +0 -0
  29. data/events/app/assets/javascripts/social_stream-events.js +7 -0
  30. data/events/app/assets/javascripts/social_stream.events.poster.js +21 -0
  31. data/events/app/assets/stylesheets/events.css.scss +25 -0
  32. data/events/app/helpers/events_helper.rb +9 -0
  33. data/events/app/models/activity_object_property/poster.rb +2 -0
  34. data/events/app/models/event.rb +18 -0
  35. data/events/app/views/events/_event.html.erb +39 -27
  36. data/events/app/views/events/_form_poster.html.erb +8 -0
  37. data/events/app/views/events/edit.js.erb +5 -0
  38. data/events/config/locales/en.yml +2 -0
  39. data/events/config/locales/es.yml +2 -0
  40. data/events/lib/social_stream-events.rb +2 -1
  41. data/events/lib/social_stream/events/engine.rb +6 -0
  42. data/events/lib/social_stream/events/models/document.rb +26 -0
  43. data/events/lib/social_stream/events/version.rb +1 -1
  44. data/events/social_stream-events.gemspec +1 -1
  45. data/lib/social_stream/version.rb +1 -1
  46. data/linkser/lib/social_stream/linkser/version.rb +1 -1
  47. data/linkser/social_stream-linkser.gemspec +1 -1
  48. data/presence/app/assets/images/games/ter/classic_aspa.png +0 -0
  49. data/presence/app/assets/images/games/ter/classic_board.png +0 -0
  50. data/presence/app/assets/images/games/ter/classic_circle.png +0 -0
  51. data/presence/app/assets/images/games/ter/modern_aspa.png +0 -0
  52. data/presence/app/assets/images/games/ter/modern_board.png +0 -0
  53. data/presence/app/assets/images/games/ter/modern_circle.png +0 -0
  54. data/presence/app/assets/javascripts/jquery.ui.chatbox.sstreampresence.js +34 -3
  55. data/presence/app/assets/javascripts/presence.js.erb +4 -0
  56. data/presence/app/assets/javascripts/{xmpp_client_management.js.erb → presence_XmppClient.js.erb} +417 -49
  57. data/presence/app/assets/javascripts/{chat_audio.js.erb → presence_audio.js.erb} +0 -0
  58. data/presence/app/assets/javascripts/presence_game.js.erb +76 -0
  59. data/presence/app/assets/javascripts/presence_game_ter.js.erb +384 -0
  60. data/presence/app/assets/javascripts/presence_notifications.js +191 -0
  61. data/presence/app/assets/javascripts/{chat_parser.js → presence_parser.js} +0 -0
  62. data/presence/app/assets/javascripts/{chat_persistence.js → presence_persistence.js} +84 -70
  63. data/presence/app/assets/javascripts/{store.js → presence_store.js} +0 -0
  64. data/presence/app/assets/javascripts/{chat_interface_manager.js.erb → presence_uiManager.js.erb} +42 -132
  65. data/presence/app/assets/javascripts/{chat_utilities.js → presence_utilities.js} +10 -34
  66. data/presence/app/assets/javascripts/{videochat.js.erb → presence_videochat.js.erb} +4 -24
  67. data/presence/app/assets/javascripts/{chat_window_manager.js → presence_windowManager.js} +275 -217
  68. data/presence/app/assets/javascripts/social_stream-presence.js +0 -2
  69. data/presence/app/assets/stylesheets/chat.css.scss +12 -1
  70. data/presence/app/views/chat/_contacts.html.erb +5 -5
  71. data/presence/app/views/chat/_index.html.erb +10 -2
  72. data/presence/config/locales/en.yml +9 -1
  73. data/presence/config/locales/es.yml +9 -1
  74. data/presence/ejabberd/ejabberd_files.zip +0 -0
  75. data/presence/ejabberd/ejabberd_scripts/emanagement +132 -2
  76. data/presence/ejabberd/installer.sh +1 -0
  77. data/presence/ejabberd/mod_muc_admin/mod_muc_admin.beam +0 -0
  78. data/presence/ejabberd/mod_muc_admin/mod_muc_admin.erl +871 -0
  79. data/presence/ejabberd/mod_sspresence/mod_sspresence.beam +0 -0
  80. data/presence/lib/social_stream/presence/models/buddy_manager.rb +32 -26
  81. data/presence/lib/social_stream/presence/models/group_manager.rb +12 -2
  82. data/presence/lib/social_stream/presence/version.rb +1 -1
  83. data/presence/lib/social_stream/presence/xmpp_server_order.rb +2 -2
  84. data/presence/social_stream-presence.gemspec +1 -1
  85. data/presence/vendor/assets/javascripts/strophe.muc.js +1 -1
  86. data/social_stream.gemspec +5 -5
  87. metadata +63 -41
  88. data/events/app/assets/javascripts/0_init.js +0 -4
@@ -2,8 +2,6 @@
2
2
  //= require strophe
3
3
  //= require strophe.muc
4
4
  //= require jquery.tools.tooltip
5
- //= require jquery.ui.chatbox.sstreampresence
6
- //= require jquery.flexselect.sstreampresence
7
5
  //= require liquidmetal
8
6
  //= require TB.min
9
7
  //= require_tree .
@@ -129,7 +129,7 @@ div.ui-chatbox-notify{
129
129
  background: $fill-color;
130
130
  background-color: $fill-color;
131
131
  color: black;
132
- height: 12px;
132
+ height: auto;
133
133
  width: 236px;
134
134
  text-align: center;
135
135
  border-style:solid;
@@ -180,6 +180,17 @@ p.ui-chatbox-notify-text{
180
180
  padding: 0px 9px 0px 9px;
181
181
  }
182
182
 
183
+ .chat-gamesthick{
184
+ /*display: block; */
185
+ display: none;
186
+ padding: 0px 1px 0px 1px;
187
+ }
188
+
189
+ div.ui-games-icon{
190
+ /*width: 100px;
191
+ height: 100px;*/
192
+ }
193
+
183
194
  /* Presence Partial */
184
195
 
185
196
  #chat_connecting{
@@ -2,11 +2,11 @@
2
2
 
3
3
  <div class="users_connected">
4
4
  <%@all_contacts.each do |contact| %>
5
- <% if contact.user.connected %>
6
- <div class="user_presence" name='<%=contact.name%>' slug='<%=contact.slug%>' connected='true'>
7
- <% else %>
8
- <div class="user_presence" name='<%=contact.name%>' slug='<%=contact.slug%>' connected='false'>
9
- <% end %>
5
+ <% if contact.user.connected %>
6
+ <div class="user_presence" name='<%=contact.name%>' slug='<%=contact.slug%>' connected='true'>
7
+ <% else %>
8
+ <div class="user_presence" name='<%=contact.name%>' slug='<%=contact.slug%>' connected='false'>
9
+ <% end %>
10
10
  <a title='<%=contact.name%>' class="presence_user_link" width="28">
11
11
  <%=image_tag(contact.logo.url, :alt => contact.name , :size => "28x28")%>
12
12
  <% if contact.user.status %>
@@ -80,9 +80,10 @@
80
80
  //Global variables
81
81
  var cookie = "AuthenticationByCookie>>" + "<%=cookies[Rails.application.config.session_options[:key]]%>";
82
82
  var BOSH_SERVICE = '<%= SocialStream::Presence.bosh_service || root_url + "http-bind/" %>';
83
- var user_name = '<%=current_user.name%>';
83
+ var user_name = '<%=current_user.name%>';
84
84
  var user_slug = '<%=current_user.slug%>';
85
85
  var user_jid = '<%=current_user.slug%>'+"@"+domain;
86
+
86
87
 
87
88
 
88
89
  $(document).ready(function () {
@@ -110,7 +111,7 @@
110
111
 
111
112
  </script>
112
113
 
113
- <% if flow %>
114
+ <% if defined?(flow)!=nil and flow %>
114
115
  <script type="text/javascript">
115
116
  $(document).ready(function () {
116
117
  createMainChatBox();
@@ -121,4 +122,11 @@
121
122
  <% end %>
122
123
 
123
124
 
125
+ <% if defined?(group)!=nil and group %>
126
+ <script type="text/javascript">
127
+ var current_group_slug = '<%=group.slug%>';
128
+ </script>
129
+ <% end %>
130
+
131
+
124
132
  <% end %>
@@ -42,4 +42,12 @@ en:
42
42
  guestOffline: "Unable to connect. {{name}} is offline"
43
43
  offline: "Unable to connect. You are offline"
44
44
  cancel: "{{name}} cancel the videocall"
45
- requirements: You don't have the minimum requirements to run videocall application. Please upgrade to the latest version of Flash.
45
+ requirements: You don't have the minimum requirements to run videocall application. Please upgrade to the latest version of Flash.
46
+ muc:
47
+ group: "{{group}} group"
48
+ join: "{{nick}} joined in the room."
49
+ leave: "{{nick}} has left the room."
50
+ occupants: "Occupants"
51
+ joining: "Joining..."
52
+ joinError: "You can't join in the room \n {{errorMsg}}"
53
+ offline: "You are offline"
@@ -42,4 +42,12 @@ es:
42
42
  guestOffline: "Imposible conectar. {{name}} está desconectado"
43
43
  offline: "Imposible conectar. Estás desconectado"
44
44
  cancel: "{{name}} canceló la videollamada"
45
- requirements: No tienes los requisitos minimos para iniciar la aplicación de videoconferencia. Por favor, actualiza a la última versión de Flash.
45
+ requirements: No tienes los requisitos minimos para iniciar la aplicación de videoconferencia. Por favor, actualiza a la última versión de Flash.
46
+ muc:
47
+ group: "Grupo {{group}}"
48
+ join: "{{nick}} ha entrado en la sala."
49
+ leave: "{{nick}} se ha ido de la sala."
50
+ occupants: "Miembros"
51
+ joining: "Accediendo..."
52
+ joinError: "No se pudo acceder a la sala. \n {{errorMsg}}"
53
+ offline: "Estás desconectado"
Binary file
@@ -5,7 +5,7 @@
5
5
  #Ejabberd Management script
6
6
  #New features for management and maintenance ejabberd
7
7
  #@author Aldo Gordillo < agordillos@gmail.com >
8
- #@version 2.0 - 24-2-2012
8
+ #@version 2.1 - 28-2-2012
9
9
  ####################################
10
10
 
11
11
  require 'logger'
@@ -48,6 +48,7 @@ PARAMS_FOR_COMMANDS = {
48
48
  'removeAllRosters' => 0,
49
49
  'getAllUserJidsWithRosterByDomain' => 1,
50
50
  'printAllRostersByDomain' => 1,
51
+ 'printAllRosters' => 0,
51
52
  'printAllBidirecctionalBuddysByDomain' => 1,
52
53
  'checkUserJid' => 1,
53
54
  'checkBidirecctionalBuddys' => 2,
@@ -62,6 +63,16 @@ PARAMS_FOR_COMMANDS = {
62
63
  'getConnectedJidsByDomain' => 1,
63
64
  'getConnectedJids' => 0,
64
65
  'kickUserJid' => 1,
66
+ 'createPersistentRoom' => 2,
67
+ 'createRoom' => 2,
68
+ 'destroyRoom' => 2,
69
+ 'destroyAllRoomsByDomain' => 1,
70
+ 'destroyAllRooms' => 0,
71
+ 'getAllJidsOfRoom' => 2,
72
+ 'getAffiliationsOfRoom' => 2,
73
+ 'setJidAffiliationOfRoom' => 4,
74
+ 'printAllRoomsByDomain' => 1,
75
+ 'printAllRooms' => 0,
65
76
  'help' => 0,
66
77
  }
67
78
 
@@ -76,6 +87,7 @@ SYNTAX_FOR_COMMANDS = {
76
87
  'removeAllRosters' => 'removeAllRosters',
77
88
  'getAllUserJidsWithRosterByDomain' => 'getAllUserJidsWithRosterByDomain domain',
78
89
  'printAllRostersByDomain' => 'printAllRostersByDomain domain',
90
+ 'printAllRosters' => 'printAllRosters',
79
91
  'printAllBidirecctionalBuddysByDomain' => 'printAllBidirecctionalBuddysByDomain domain',
80
92
  'checkUserJid' => 'checkUserJid userJid',
81
93
  'checkBidirecctionalBuddys' => 'checkBidirecctionalBuddys userAJid userBJid',
@@ -90,6 +102,16 @@ SYNTAX_FOR_COMMANDS = {
90
102
  'getConnectedJidsByDomain' => 'getConnectedJidsByDomain domain',
91
103
  'getConnectedJids' => 'getConnectedJids',
92
104
  'kickUserJid' => 'kickUserJid(userJid)',
105
+ 'createPersistentRoom' => 'createPersistentRoom roomName domain',
106
+ 'createRoom' => 'createRoom roomName domain',
107
+ 'destroyRoom' => 'destroyRoom roomName domain',
108
+ 'destroyAllRoomsByDomain' => 'destroyAllRoomsByDomain domain',
109
+ 'destroyAllRooms' => 'destroyAllRooms',
110
+ 'getAllJidsOfRoom' => 'getAllJidsOfRoom roomName domain',
111
+ 'getAffiliationsOfRoom' => 'getAffiliationsOfRoom roomName domain',
112
+ 'setJidAffiliationOfRoom' => 'setJidAffiliationOfRoom roomName domain jid affiliation (affiliation value: owner/admin/member/outcast/none)',
113
+ 'printAllRoomsByDomain' => 'printAllRoomsByDomain domain',
114
+ 'printAllRooms' => 'printAllRooms',
93
115
  'help' => 'help',
94
116
  }
95
117
 
@@ -546,7 +568,115 @@ end
546
568
 
547
569
 
548
570
 
571
+ #########################
572
+ #MUC Methods (Multi-user-chat)
573
+ #########################
574
+
575
+ $muc_host = "conference"
576
+
577
+ def printAllRoomsByDomain(domain)
578
+ if(domain=="all")
579
+ return printAllRooms
580
+ end
581
+ output = executeCommand("ejabberdctl muc_online_rooms " + domain)
582
+ return output;
583
+ end
584
+
585
+ def printAllRooms
586
+ output = executeCommand("ejabberdctl muc_online_rooms global")
587
+ return output;
588
+ end
589
+
590
+ def createPersistentRoom(roomName,domain)
591
+ createRoom(roomName,domain)
592
+ setRoomPersistence(roomName,domain,true)
593
+ return "Done"
594
+ end
595
+
596
+ def createRoom(roomName,domain)
597
+ executeCommand("ejabberdctl create_room " + roomName + " " + $muc_host + "." + domain + " " + domain)
598
+ return "Done"
599
+ end
600
+
601
+ def setRoomPersistence(roomName,domain,persistence)
602
+ if (persistence==true)
603
+ executeCommand("ejabberdctl change_room_option " + roomName + " " + $muc_host + "." + domain + " persistent true")
604
+ else
605
+ executeCommand("ejabberdctl change_room_option " + roomName + " " + $muc_host + "." + domain + " persistent false")
606
+ end
607
+ end
608
+
609
+ def destroyAllRoomsByDomain(domain)
610
+ rooms = getAllRoomsByDomain(domain)
611
+ rooms.each do |room|
612
+ destroyRoom(getNameFromRoom(room),getDomainFromRoom(room))
613
+ end
614
+ return "Done"
615
+ end
616
+
617
+ def destroyAllRooms()
618
+ return destroyAllRoomsByDomain("all")
619
+ end
620
+
621
+ def destroyRoom(roomName,domain)
622
+ executeCommand("ejabberdctl destroy_room " + roomName + " " + $muc_host + "." + domain + " " + domain)
623
+ return "Done"
624
+ end
625
+
626
+ def getAllRoomsByDomain(domain)
627
+ if(domain=="all")
628
+ output = executeCommand("ejabberdctl muc_online_rooms global")
629
+ else
630
+ output = executeCommand("ejabberdctl muc_online_rooms " + domain)
631
+ end
632
+ rooms = output.split("\n")
633
+ return rooms
634
+ end
635
+
636
+ def getAllJidsOfRoomObject(room)
637
+ return getAllJidsOfRoom(getNameFromRoom(room),getDomainFromRoom(room))
638
+ end
639
+
640
+ def getAllJidsOfRoom(roomName,domain)
641
+ jids = []
642
+ output = executeCommand("ejabberdctl get_room_occupants " + roomName + " " + $muc_host + "." + domain)
643
+ lines = output.split("\n")
644
+ lines.each do |line|
645
+ jids << line.split("/")[0]
646
+ end
647
+ return jids
648
+ end
549
649
 
650
+ def getAffiliationsOfRoomObject(room)
651
+ return getAffiliationsOfRoom(getNameFromRoom(room),getDomainFromRoom(room))
652
+ end
653
+
654
+ def getAffiliationsOfRoom(roomName,domain)
655
+ affiliateds = []
656
+ output = executeCommand("ejabberdctl get_room_affiliations " + roomName + " " + $muc_host + "." + domain)
657
+ lines = output.split("\n")
658
+ lines.each do |line|
659
+ split=line.split(" ");
660
+ jid = split[0]+"@"+split[1];
661
+ affiliation=split[2]
662
+ affiliateds << [jid,affiliation]
663
+ end
664
+ return affiliateds
665
+ end
666
+
667
+ #affiliation = Owner/Admin/Member/Outcast/None
668
+ def setJidAffiliationOfRoom(roomName,domain,jid,affiliation)
669
+ output = executeCommand("ejabberdctl set_room_affiliation " + roomName + " " + $muc_host + "." + domain + " " + jid + " " + affiliation)
670
+ return "Done"
671
+ end
672
+
673
+ def getDomainFromRoom(room)
674
+ return room.split(".")[1];
675
+ end
676
+
677
+ def getNameFromRoom(room)
678
+ return room.split("@")[0];
679
+ end
550
680
 
551
681
 
552
682
  #########################
@@ -558,7 +688,7 @@ def executeCommand(command)
558
688
 
559
689
  if $verbose
560
690
  #Logging...
561
- #puts "Executing: " + command
691
+ puts "Executing: " + command
562
692
  ejabberdLog("Executing (#{command})")
563
693
  end
564
694
 
@@ -216,6 +216,7 @@ done
216
216
  msg "Copying Ejabberd modules"
217
217
  cp $installer_folder_path/mod_admin_extra/mod_admin_extra.beam $ejabberd_module_path
218
218
  cp $installer_folder_path/mod_sspresence/mod_sspresence.beam $ejabberd_module_path
219
+ cp $installer_folder_path/mod_muc_admin/mod_muc_admin.beam $ejabberd_module_path
219
220
 
220
221
  msg "Copying scripts"
221
222
  cp -r $installer_folder_path/ejabberd_scripts/* $scripts_path
@@ -0,0 +1,871 @@
1
+ %%%----------------------------------------------------------------------
2
+ %%% File : mod_muc_admin.erl
3
+ %%% Author : Badlop <badlop@ono.com>
4
+ %%% Purpose : Tools for additional MUC administration
5
+ %%% Created : 8 Sep 2007 by Badlop <badlop@ono.com>
6
+ %%% Id : $Id$
7
+ %%%----------------------------------------------------------------------
8
+
9
+ -module(mod_muc_admin).
10
+ -author('badlop@ono.com').
11
+
12
+ -behaviour(gen_mod).
13
+
14
+ -export([
15
+ start/2, stop/1, % gen_mod API
16
+ muc_online_rooms/1,
17
+ muc_unregister_nick/1,
18
+ create_room/3, destroy_room/3,
19
+ create_rooms_file/1, destroy_rooms_file/1,
20
+ rooms_unused_list/2, rooms_unused_destroy/2,
21
+ get_room_occupants/2,
22
+ send_direct_invitation/4,
23
+ change_room_option/4,
24
+ set_room_affiliation/4,
25
+ get_room_affiliations/2,
26
+ web_menu_main/2, web_page_main/2, % Web Admin API
27
+ web_menu_host/3, web_page_host/3
28
+ ]).
29
+
30
+ -include("ejabberd.hrl").
31
+ -include("jlib.hrl").
32
+ -include("mod_muc/mod_muc_room.hrl").
33
+ -include("web/ejabberd_http.hrl").
34
+ -include("web/ejabberd_web_admin.hrl").
35
+ -include("ejabberd_commands.hrl").
36
+
37
+ %% Copied from mod_muc/mod_muc.erl
38
+ -record(muc_online_room, {name_host, pid}).
39
+
40
+ %%----------------------------
41
+ %% gen_mod
42
+ %%----------------------------
43
+
44
+ start(Host, _Opts) ->
45
+ ejabberd_commands:register_commands(commands()),
46
+ ejabberd_hooks:add(webadmin_menu_main, ?MODULE, web_menu_main, 50),
47
+ ejabberd_hooks:add(webadmin_menu_host, Host, ?MODULE, web_menu_host, 50),
48
+ ejabberd_hooks:add(webadmin_page_main, ?MODULE, web_page_main, 50),
49
+ ejabberd_hooks:add(webadmin_page_host, Host, ?MODULE, web_page_host, 50).
50
+
51
+ stop(Host) ->
52
+ ejabberd_commands:unregister_commands(commands()),
53
+ ejabberd_hooks:delete(webadmin_menu_main, ?MODULE, web_menu_main, 50),
54
+ ejabberd_hooks:delete(webadmin_menu_host, Host, ?MODULE, web_menu_host, 50),
55
+ ejabberd_hooks:delete(webadmin_page_main, ?MODULE, web_page_main, 50),
56
+ ejabberd_hooks:delete(webadmin_page_host, Host, ?MODULE, web_page_host, 50),
57
+ ejabberd_hooks:delete(webadmin_user, Host, ?MODULE, web_user, 50).
58
+
59
+ %%%
60
+ %%% Register commands
61
+ %%%
62
+
63
+ commands() ->
64
+ [
65
+ #ejabberd_commands{name = muc_online_rooms, tags = [muc],
66
+ desc = "List existing rooms ('global' to get all vhosts)",
67
+ module = ?MODULE, function = muc_online_rooms,
68
+ args = [{host, string}],
69
+ result = {rooms, {list, {room, string}}}},
70
+ #ejabberd_commands{name = muc_unregister_nick, tags = [muc],
71
+ desc = "Unregister the nick in the MUC service",
72
+ module = ?MODULE, function = muc_unregister_nick,
73
+ args = [{nick, string}],
74
+ result = {res, rescode}},
75
+
76
+ #ejabberd_commands{name = create_room, tags = [muc_room],
77
+ desc = "Create a MUC room name@service in host",
78
+ module = ?MODULE, function = create_room,
79
+ args = [{name, string}, {service, string},
80
+ {host, string}],
81
+ result = {res, rescode}},
82
+ #ejabberd_commands{name = destroy_room, tags = [muc_room],
83
+ desc = "Destroy a MUC room",
84
+ module = ?MODULE, function = destroy_room,
85
+ args = [{name, string}, {service, string},
86
+ {host, string}],
87
+ result = {res, rescode}},
88
+ #ejabberd_commands{name = create_rooms_file, tags = [muc],
89
+ desc = "Create the rooms indicated in file",
90
+ module = ?MODULE, function = create_rooms_file,
91
+ args = [{file, string}],
92
+ result = {res, rescode}},
93
+ #ejabberd_commands{name = destroy_rooms_file, tags = [muc],
94
+ desc = "Destroy the rooms indicated in file",
95
+ module = ?MODULE, function = destroy_rooms_file,
96
+ args = [{file, string}],
97
+ result = {res, rescode}},
98
+ #ejabberd_commands{name = rooms_unused_list, tags = [muc],
99
+ desc = "List the rooms that are unused for many days in host",
100
+ module = ?MODULE, function = rooms_unused_list,
101
+ args = [{host, string}, {days, integer}],
102
+ result = {rooms, {list, {room, string}}}},
103
+ #ejabberd_commands{name = rooms_unused_destroy, tags = [muc],
104
+ desc = "Destroy the rooms that are unused for many days in host",
105
+ module = ?MODULE, function = rooms_unused_destroy,
106
+ args = [{host, string}, {days, integer}],
107
+ result = {rooms, {list, {room, string}}}},
108
+
109
+ #ejabberd_commands{name = get_room_occupants, tags = [muc_room],
110
+ desc = "Get the list of occupants of a MUC room",
111
+ module = ?MODULE, function = get_room_occupants,
112
+ args = [{name, string}, {service, string}],
113
+ result = {occupants, {list,
114
+ {occupant, {tuple,
115
+ [{jid, string},
116
+ {nick, string},
117
+ {role, string}
118
+ ]}}
119
+ }}},
120
+
121
+ #ejabberd_commands{name = send_direct_invitation, tags = [muc_room],
122
+ desc = "Send a direct invitation to several destinations",
123
+ longdesc = "Password and Message can also be: none. Users JIDs are separated with : ",
124
+ module = ?MODULE, function = send_direct_invitation,
125
+ args = [{room, string}, {password, string}, {reason, string}, {users, string}],
126
+ result = {res, rescode}},
127
+
128
+ #ejabberd_commands{name = change_room_option, tags = [muc_room],
129
+ desc = "Change an option in a MUC room",
130
+ module = ?MODULE, function = change_room_option,
131
+ args = [{name, string}, {service, string},
132
+ {option, string}, {value, string}],
133
+ result = {res, rescode}},
134
+
135
+ #ejabberd_commands{name = set_room_affiliation, tags = [muc_room],
136
+ desc = "Change an affiliation in a MUC room",
137
+ module = ?MODULE, function = set_room_affiliation,
138
+ args = [{name, string}, {service, string},
139
+ {jid, string}, {affiliation, string}],
140
+ result = {res, rescode}},
141
+ #ejabberd_commands{name = get_room_affiliations, tags = [muc_room],
142
+ desc = "Get the list of affiliations of a MUC room",
143
+ module = ?MODULE, function = get_room_affiliations,
144
+ args = [{name, string}, {service, string}],
145
+ result = {affiliations, {list,
146
+ {affiliation, {tuple,
147
+ [{username, string},
148
+ {domain, string},
149
+ {affiliation, atom},
150
+ {reason, string}
151
+ ]}}
152
+ }}}
153
+ ].
154
+
155
+
156
+ %%%
157
+ %%% ejabberd commands
158
+ %%%
159
+
160
+ muc_online_rooms(ServerHost) ->
161
+ MUCHost = find_host(ServerHost),
162
+ Rooms = ets:tab2list(muc_online_room),
163
+ lists:foldl(
164
+ fun({_, {Roomname, Host}, _}, Results) ->
165
+ case MUCHost of
166
+ global ->
167
+ [Roomname ++ "@" ++ Host | Results];
168
+ Host ->
169
+ [Roomname ++ "@" ++ Host | Results];
170
+ _ ->
171
+ Results
172
+ end
173
+ end,
174
+ [],
175
+ Rooms).
176
+
177
+ muc_unregister_nick(Nick) ->
178
+ F2 = fun(N) ->
179
+ [{_,Key,_}] = mnesia:index_read(muc_registered, N, 3),
180
+ mnesia:delete({muc_registered, Key})
181
+ end,
182
+ case mnesia:transaction(F2, [Nick], 1) of
183
+ {atomic, ok} ->
184
+ ok;
185
+ {aborted, _Error} ->
186
+ error
187
+ end.
188
+
189
+
190
+ %%----------------------------
191
+ %% Ad-hoc commands
192
+ %%----------------------------
193
+
194
+
195
+ %%----------------------------
196
+ %% Web Admin
197
+ %%----------------------------
198
+
199
+ %%---------------
200
+ %% Web Admin Menu
201
+
202
+ web_menu_main(Acc, Lang) ->
203
+ Acc ++ [{"muc", ?T("Multi-User Chat")}].
204
+
205
+ web_menu_host(Acc, _Host, Lang) ->
206
+ Acc ++ [{"muc", ?T("Multi-User Chat")}].
207
+
208
+
209
+ %%---------------
210
+ %% Web Admin Page
211
+
212
+ -define(TDTD(L, N),
213
+ ?XE("tr", [?XCT("td", L),
214
+ ?XC("td", integer_to_list(N))
215
+ ])).
216
+
217
+ web_page_main(_, #request{path=["muc"], lang = Lang} = _Request) ->
218
+ Res = [?XC("h1", "Multi-User Chat"),
219
+ ?XC("h3", "Statistics"),
220
+ ?XAE("table", [],
221
+ [?XE("tbody", [?TDTD("Total rooms", ets:info(muc_online_room, size)),
222
+ ?TDTD("Permanent rooms", mnesia:table_info(muc_room, size)),
223
+ ?TDTD("Registered nicknames", mnesia:table_info(muc_registered, size))
224
+ ])
225
+ ]),
226
+ ?XE("ul", [?LI([?ACT("rooms", "List of rooms")])])
227
+ ],
228
+ {stop, Res};
229
+
230
+ web_page_main(_, #request{path=["muc", "rooms"], q = Q, lang = Lang} = _Request) ->
231
+ Sort_query = get_sort_query(Q),
232
+ Res = make_rooms_page(global, Lang, Sort_query),
233
+ {stop, Res};
234
+
235
+ web_page_main(Acc, _) -> Acc.
236
+
237
+ web_page_host(_, Host,
238
+ #request{path = ["muc"],
239
+ q = Q,
240
+ lang = Lang} = _Request) ->
241
+ Sort_query = get_sort_query(Q),
242
+ Res = make_rooms_page(find_host(Host), Lang, Sort_query),
243
+ {stop, Res};
244
+ web_page_host(Acc, _, _) -> Acc.
245
+
246
+
247
+ %% Returns: {normal | reverse, Integer}
248
+ get_sort_query(Q) ->
249
+ case catch get_sort_query2(Q) of
250
+ {ok, Res} -> Res;
251
+ _ -> {normal, 1}
252
+ end.
253
+
254
+ get_sort_query2(Q) ->
255
+ {value, {_, String}} = lists:keysearch("sort", 1, Q),
256
+ Integer = list_to_integer(String),
257
+ case Integer >= 0 of
258
+ true -> {ok, {normal, Integer}};
259
+ false -> {ok, {reverse, abs(Integer)}}
260
+ end.
261
+
262
+ make_rooms_page(Host, Lang, {Sort_direction, Sort_column}) ->
263
+ Rooms_names = get_rooms(Host),
264
+ Rooms_infos = build_info_rooms(Rooms_names),
265
+ Rooms_sorted = sort_rooms(Sort_direction, Sort_column, Rooms_infos),
266
+ Rooms_prepared = prepare_rooms_infos(Rooms_sorted),
267
+ TList = lists:map(
268
+ fun(Room) ->
269
+ ?XE("tr", [?XC("td", E) || E <- Room])
270
+ end, Rooms_prepared),
271
+ Titles = ["Jabber ID",
272
+ "# participants",
273
+ "Last message",
274
+ "Public",
275
+ "Persistent",
276
+ "Logging",
277
+ "Just created",
278
+ "Title"],
279
+ {Titles_TR, _} =
280
+ lists:mapfoldl(
281
+ fun(Title, Num_column) ->
282
+ NCS = integer_to_list(Num_column),
283
+ TD = ?XE("td", [?CT(Title),
284
+ ?C(" "),
285
+ ?ACT("?sort="++NCS, "<"),
286
+ ?C(" "),
287
+ ?ACT("?sort=-"++NCS, ">")]),
288
+ {TD, Num_column+1}
289
+ end,
290
+ 1,
291
+ Titles),
292
+ [?XC("h1", "Multi-User Chat"),
293
+ ?XC("h2", "Rooms"),
294
+ ?XE("table",
295
+ [?XE("thead",
296
+ [?XE("tr", Titles_TR)]
297
+ ),
298
+ ?XE("tbody", TList)
299
+ ]
300
+ )
301
+ ].
302
+
303
+ sort_rooms(Direction, Column, Rooms) ->
304
+ Rooms2 = lists:keysort(Column, Rooms),
305
+ case Direction of
306
+ normal -> Rooms2;
307
+ reverse -> lists:reverse(Rooms2)
308
+ end.
309
+
310
+ build_info_rooms(Rooms) ->
311
+ [build_info_room(Room) || Room <- Rooms].
312
+
313
+ build_info_room({Name, Host, Pid}) ->
314
+ C = get_room_config(Pid),
315
+ Title = C#config.title,
316
+ Public = C#config.public,
317
+ Persistent = C#config.persistent,
318
+ Logging = C#config.logging,
319
+
320
+ S = get_room_state(Pid),
321
+ Just_created = S#state.just_created,
322
+ Num_participants = length(dict:fetch_keys(S#state.users)),
323
+
324
+ History = (S#state.history)#lqueue.queue,
325
+ Ts_last_message =
326
+ case queue:is_empty(History) of
327
+ true ->
328
+ "A long time ago";
329
+ false ->
330
+ Last_message1 = queue:last(History),
331
+ {_, _, _, Ts_last, _} = Last_message1,
332
+ jlib:timestamp_to_iso(Ts_last)
333
+ end,
334
+
335
+ {Name++"@"++Host,
336
+ Num_participants,
337
+ Ts_last_message,
338
+ Public,
339
+ Persistent,
340
+ Logging,
341
+ Just_created,
342
+ Title}.
343
+
344
+ prepare_rooms_infos(Rooms) ->
345
+ [prepare_room_info(Room) || Room <- Rooms].
346
+ prepare_room_info(Room_info) ->
347
+ {NameHost,
348
+ Num_participants,
349
+ Ts_last_message,
350
+ Public,
351
+ Persistent,
352
+ Logging,
353
+ Just_created,
354
+ Title} = Room_info,
355
+ [NameHost,
356
+ integer_to_list(Num_participants),
357
+ Ts_last_message,
358
+ atom_to_list(Public),
359
+ atom_to_list(Persistent),
360
+ atom_to_list(Logging),
361
+ atom_to_list(Just_created),
362
+ Title].
363
+
364
+
365
+ %%----------------------------
366
+ %% Create/Delete Room
367
+ %%----------------------------
368
+
369
+ %% @spec (Name::string(), Host::string(), ServerHost::string()) ->
370
+ %% ok | error
371
+ %% @doc Create a room immediately with the default options.
372
+ create_room(Name, Host, ServerHost) ->
373
+
374
+ %% Get the default room options from the muc configuration
375
+ DefRoomOpts = gen_mod:get_module_opt(ServerHost, mod_muc,
376
+ default_room_options, []),
377
+
378
+ %% Store the room on the server, it is not started yet though at this point
379
+ mod_muc:store_room(Host, Name, DefRoomOpts),
380
+
381
+ %% Get all remaining mod_muc parameters that might be utilized
382
+ Access = gen_mod:get_module_opt(ServerHost, mod_muc, access, all),
383
+ AcCreate = gen_mod:get_module_opt(ServerHost, mod_muc, access_create, all),
384
+ AcAdmin = gen_mod:get_module_opt(ServerHost, mod_muc, access_admin, none),
385
+ AcPer = gen_mod:get_module_opt(ServerHost, mod_muc, access_persistent, all),
386
+ HistorySize = gen_mod:get_module_opt(ServerHost, mod_muc, history_size, 20),
387
+ RoomShaper = gen_mod:get_module_opt(ServerHost, mod_muc, room_shaper, none),
388
+
389
+ %% If the room does not exist yet in the muc_online_room
390
+ case mnesia:dirty_read(muc_online_room, {Name, Host}) of
391
+ [] ->
392
+ %% Start the room
393
+ {ok, Pid} = mod_muc_room:start(
394
+ Host,
395
+ ServerHost,
396
+ {Access, AcCreate, AcAdmin, AcPer},
397
+ Name,
398
+ HistorySize,
399
+ RoomShaper,
400
+ DefRoomOpts),
401
+ {atomic, ok} = register_room(Host, Name, Pid),
402
+ ok;
403
+ _ ->
404
+ error
405
+ end.
406
+
407
+ register_room(Host, Name, Pid) ->
408
+ F = fun() ->
409
+ mnesia:write(#muc_online_room{name_host = {Name, Host},
410
+ pid = Pid})
411
+ end,
412
+ mnesia:transaction(F).
413
+
414
+ %% Create the room only in the database.
415
+ %% It is required to restart the MUC service for the room to appear.
416
+ muc_create_room({Name, Host, _}, DefRoomOpts) ->
417
+ io:format("Creating room ~s@~s~n", [Name, Host]),
418
+ mod_muc:store_room(Host, Name, DefRoomOpts).
419
+
420
+ %% @spec (Name::string(), Host::string(), ServerHost::string()) ->
421
+ %% ok | {error, room_not_exists}
422
+ %% @doc Destroy the room immediately.
423
+ %% If the room has participants, they are not notified that the room was destroyed;
424
+ %% they will notice when they try to chat and receive an error that the room doesn't exist.
425
+ destroy_room(Name, Service, _Server) ->
426
+ case mnesia:dirty_read(muc_online_room, {Name, Service}) of
427
+ [R] ->
428
+ Pid = R#muc_online_room.pid,
429
+ gen_fsm:send_all_state_event(Pid, destroy),
430
+ ok;
431
+ [] ->
432
+ error
433
+ end.
434
+
435
+ destroy_room({N, H, SH}) ->
436
+ io:format("Destroying room: ~s@~s - vhost: ~s~n", [N, H, SH]),
437
+ destroy_room(N, H, SH).
438
+
439
+
440
+ %%----------------------------
441
+ %% Destroy Rooms in File
442
+ %%----------------------------
443
+
444
+ %% The format of the file is: one chatroom JID per line
445
+ %% The file encoding must be UTF-8
446
+
447
+ destroy_rooms_file(Filename) ->
448
+ {ok, F} = file:open(Filename, [read]),
449
+ RJID = read_room(F),
450
+ Rooms = read_rooms(F, RJID, []),
451
+ file:close(F),
452
+ [destroy_room(A) || A <- Rooms],
453
+ ok.
454
+
455
+ read_rooms(_F, eof, L) ->
456
+ L;
457
+
458
+ read_rooms(F, RJID, L) ->
459
+ RJID2 = read_room(F),
460
+ read_rooms(F, RJID2, [RJID | L]).
461
+
462
+ read_room(F) ->
463
+ case io:get_line(F, "") of
464
+ eof -> eof;
465
+ String ->
466
+ case io_lib:fread("~s", String) of
467
+ {ok, [RoomJID], _} -> split_roomjid(RoomJID);
468
+ {error, What} ->
469
+ io:format("Parse error: what: ~p~non the line: ~p~n~n", [What, String])
470
+ end
471
+ end.
472
+
473
+ %% This function is quite rudimentary
474
+ %% and may not be accurate
475
+ split_roomjid(RoomJID) ->
476
+ [Name, Host] = string:tokens(RoomJID, "@"),
477
+ [_MUC_service_name | ServerHostList] = string:tokens(Host, "."),
478
+ ServerHost = join(ServerHostList, "."),
479
+ {Name, Host, ServerHost}.
480
+
481
+ %% This function is copied from string:join/2 in Erlang/OTP R12B-1
482
+ %% Note that string:join/2 is not implemented in Erlang/OTP R11B
483
+ join([H|T], Sep) ->
484
+ H ++ lists:concat([Sep ++ X || X <- T]).
485
+
486
+
487
+ %%----------------------------
488
+ %% Create Rooms in File
489
+ %%----------------------------
490
+
491
+ create_rooms_file(Filename) ->
492
+ {ok, F} = file:open(Filename, [read]),
493
+ RJID = read_room(F),
494
+ Rooms = read_rooms(F, RJID, []),
495
+ file:close(F),
496
+ %% Read the default room options defined for the first virtual host
497
+ DefRoomOpts = gen_mod:get_module_opt(?MYNAME, mod_muc,
498
+ default_room_options, []),
499
+ [muc_create_room(A, DefRoomOpts) || A <- Rooms],
500
+ ok.
501
+
502
+
503
+ %%----------------------------
504
+ %% List/Delete Unused Rooms
505
+ %%----------------------------
506
+
507
+ %%---------------
508
+ %% Control
509
+
510
+ rooms_unused_list(Host, Days) ->
511
+ rooms_unused_report(list, Host, Days).
512
+ rooms_unused_destroy(Host, Days) ->
513
+ rooms_unused_report(destroy, Host, Days).
514
+
515
+ rooms_unused_report(Action, Host, Days) ->
516
+ {NA, NP, RP} = muc_unused(Action, Host, Days),
517
+ io:format("Unused rooms: ~p out of ~p~n", [NP, NA]),
518
+ [R ++ "@" ++ H || {R, H, _P} <- RP].
519
+
520
+ muc_unused(Action, ServerHost, Days) ->
521
+ Host = find_host(ServerHost),
522
+ muc_unused2(Action, ServerHost, Host, Days).
523
+
524
+ muc_unused2(Action, ServerHost, Host, Last_allowed) ->
525
+ %% Get all required info about all existing rooms
526
+ Rooms_all = get_rooms(Host),
527
+
528
+ %% Decide which ones pass the requirements
529
+ Rooms_pass = decide_rooms(Rooms_all, Last_allowed),
530
+
531
+ Num_rooms_all = length(Rooms_all),
532
+ Num_rooms_pass = length(Rooms_pass),
533
+
534
+ %% Perform the desired action for matching rooms
535
+ act_on_rooms(Action, Rooms_pass, ServerHost),
536
+
537
+ {Num_rooms_all, Num_rooms_pass, Rooms_pass}.
538
+
539
+ %%---------------
540
+ %% Get info
541
+
542
+ get_rooms(Host) ->
543
+ Get_room_names = fun(Room_reg, Names) ->
544
+ Pid = Room_reg#muc_online_room.pid,
545
+ case {Host, Room_reg#muc_online_room.name_host} of
546
+ {Host, {Name1, Host}} ->
547
+ [{Name1, Host, Pid} | Names];
548
+ {global, {Name1, Host1}} ->
549
+ [{Name1, Host1, Pid} | Names];
550
+ _ ->
551
+ Names
552
+ end
553
+ end,
554
+ ets:foldr(Get_room_names, [], muc_online_room).
555
+
556
+ get_room_config(Room_pid) ->
557
+ {ok, R} = gen_fsm:sync_send_all_state_event(Room_pid, get_config),
558
+ R.
559
+
560
+ get_room_state(Room_pid) ->
561
+ {ok, R} = gen_fsm:sync_send_all_state_event(Room_pid, get_state),
562
+ R.
563
+
564
+ %%---------------
565
+ %% Decide
566
+
567
+ decide_rooms(Rooms, Last_allowed) ->
568
+ Decide = fun(R) -> decide_room(R, Last_allowed) end,
569
+ lists:filter(Decide, Rooms).
570
+
571
+ decide_room({_Room_name, _Host, Room_pid}, Last_allowed) ->
572
+ C = get_room_config(Room_pid),
573
+ Persistent = C#config.persistent,
574
+
575
+ S = get_room_state(Room_pid),
576
+ Just_created = S#state.just_created,
577
+
578
+ Room_users = S#state.users,
579
+ Num_users = length(?DICT:to_list(Room_users)),
580
+
581
+ History = (S#state.history)#lqueue.queue,
582
+ Ts_now = calendar:now_to_universal_time(now()),
583
+ Ts_uptime = uptime_seconds(),
584
+ {Has_hist, Last} = case queue:is_empty(History) of
585
+ true ->
586
+ {false, Ts_uptime};
587
+ false ->
588
+ Last_message = queue:last(History),
589
+ {_, _, _, Ts_last, _} = Last_message,
590
+ Ts_diff =
591
+ calendar:datetime_to_gregorian_seconds(Ts_now)
592
+ - calendar:datetime_to_gregorian_seconds(Ts_last),
593
+ {true, Ts_diff}
594
+ end,
595
+
596
+ case {Persistent, Just_created, Num_users, Has_hist, seconds_to_days(Last)} of
597
+ {_true, false, 0, _, Last_days}
598
+ when Last_days >= Last_allowed ->
599
+ true;
600
+ _ ->
601
+ false
602
+ end.
603
+
604
+ seconds_to_days(S) ->
605
+ S div (60*60*24).
606
+
607
+ %%---------------
608
+ %% Act
609
+
610
+ act_on_rooms(Action, Rooms, ServerHost) ->
611
+ ServerHosts = [ {A, find_host(A)} || A <- ?MYHOSTS ],
612
+ Delete = fun({_N, H, _Pid} = Room) ->
613
+ SH = case ServerHost of
614
+ global -> find_serverhost(H, ServerHosts);
615
+ O -> O
616
+ end,
617
+
618
+ act_on_room(Action, Room, SH)
619
+ end,
620
+ lists:foreach(Delete, Rooms).
621
+
622
+ find_serverhost(Host, ServerHosts) ->
623
+ {value, {ServerHost, Host}} = lists:keysearch(Host, 2, ServerHosts),
624
+ ServerHost.
625
+
626
+ act_on_room(destroy, {N, H, Pid}, SH) ->
627
+ gen_fsm:send_all_state_event(
628
+ Pid, {destroy, "Room destroyed by rooms_unused_destroy."}),
629
+ mod_muc:room_destroyed(H, N, Pid, SH),
630
+ mod_muc:forget_room(H, N);
631
+
632
+ act_on_room(list, _, _) ->
633
+ ok.
634
+
635
+
636
+ %%----------------------------
637
+ %% Change Room Option
638
+ %%----------------------------
639
+
640
+ get_room_occupants(Room, Host) ->
641
+ case get_room_pid(Room, Host) of
642
+ room_not_found -> throw({error, room_not_found});
643
+ Pid -> get_room_occupants(Pid)
644
+ end.
645
+
646
+ get_room_occupants(Pid) ->
647
+ S = get_room_state(Pid),
648
+ lists:map(
649
+ fun({_LJID, Info}) ->
650
+ {jlib:jid_to_string(Info#user.jid),
651
+ Info#user.nick,
652
+ atom_to_list(Info#user.role)}
653
+ end,
654
+ dict:to_list(S#state.users)).
655
+
656
+ %%----------------------------
657
+ %% Send Direct Invitation
658
+ %%----------------------------
659
+ %% http://xmpp.org/extensions/xep-0249.html
660
+
661
+ send_direct_invitation(RoomString, Password, Reason, UsersString) ->
662
+ RoomJid = jlib:string_to_jid(RoomString),
663
+ XmlEl = build_invitation(Password, Reason, RoomString),
664
+ UsersStrings = get_users_to_invite(RoomJid, UsersString),
665
+ [send_direct_invitation(RoomJid, jlib:string_to_jid(UserStrings), XmlEl)
666
+ || UserStrings <- UsersStrings],
667
+ timer:sleep(1000),
668
+ ok.
669
+
670
+ get_users_to_invite(RoomJid, UsersString) ->
671
+ UsersStrings = string:tokens(UsersString, ":"),
672
+ OccupantsTuples = get_room_occupants(RoomJid#jid.luser,
673
+ RoomJid#jid.lserver),
674
+ OccupantsJids = [jlib:string_to_jid(JidString)
675
+ || {JidString, _Nick, _} <- OccupantsTuples],
676
+ lists:filter(
677
+ fun(UserString) ->
678
+ UserJid = jlib:string_to_jid(UserString),
679
+ %% [{"badlop@localhost/work","badlop","moderator"}]
680
+ lists:all(fun(OccupantJid) ->
681
+ UserJid#jid.luser /= OccupantJid#jid.luser
682
+ orelse UserJid#jid.lserver /= OccupantJid#jid.lserver
683
+ end,
684
+ OccupantsJids)
685
+ end,
686
+ UsersStrings).
687
+
688
+ build_invitation(Password, Reason, RoomString) ->
689
+ PasswordAttrList = case Password of
690
+ "none" -> [];
691
+ _ -> [{"password", Password}]
692
+ end,
693
+ ReasonAttrList = case Reason of
694
+ "none" -> [];
695
+ _ -> [{"reason", Reason}]
696
+ end,
697
+ XAttrs = [{"xmlns", ?NS_XCONFERENCE},
698
+ {"jid", RoomString}]
699
+ ++ PasswordAttrList
700
+ ++ ReasonAttrList,
701
+ XEl = {xmlelement, "x", XAttrs, []},
702
+ {xmlelement, "message", [], [XEl]}.
703
+
704
+ send_direct_invitation(FromJid, UserJid, XmlEl) ->
705
+ ejabberd_router:route(FromJid, UserJid, XmlEl).
706
+
707
+ %%----------------------------
708
+ %% Change Room Option
709
+ %%----------------------------
710
+
711
+ %% @spec(Name::string(), Service::string(), Option::string(), Value) -> ok
712
+ %% Value = atom() | integer() | string()
713
+ %% @doc Change an option in an existing room.
714
+ %% Requires the name of the room, the MUC service where it exists,
715
+ %% the option to change (for example title or max_users),
716
+ %% and the value to assign to the new option.
717
+ %% For example:
718
+ %% change_room_option("testroom", "conference.localhost", "title", "Test Room")
719
+ change_room_option(Name, Service, Option, Value) when is_atom(Option) ->
720
+ Pid = get_room_pid(Name, Service),
721
+ {ok, _} = change_room_option(Pid, Option, Value),
722
+ ok;
723
+ change_room_option(Name, Service, OptionString, ValueString) ->
724
+ Option = list_to_atom(OptionString),
725
+ Value = case Option of
726
+ title -> ValueString;
727
+ description -> ValueString;
728
+ password -> ValueString;
729
+ max_users -> list_to_integer(ValueString);
730
+ _ -> list_to_atom(ValueString)
731
+ end,
732
+ change_room_option(Name, Service, Option, Value).
733
+
734
+ change_room_option(Pid, Option, Value) ->
735
+ Config = get_room_config(Pid),
736
+ Config2 = change_option(Option, Value, Config),
737
+ gen_fsm:sync_send_all_state_event(Pid, {change_config, Config2}).
738
+
739
+ %% @doc Get the Pid of an existing MUC room, or 'room_not_found'.
740
+ get_room_pid(Name, Service) ->
741
+ case mnesia:dirty_read(muc_online_room, {Name, Service}) of
742
+ [] ->
743
+ room_not_found;
744
+ [Room] ->
745
+ Room#muc_online_room.pid
746
+ end.
747
+
748
+ %% It is required to put explicitely all the options because
749
+ %% the record elements are replaced at compile time.
750
+ %% So, this can't be parametrized.
751
+ change_option(Option, Value, Config) ->
752
+ case Option of
753
+ allow_change_subj -> Config#config{allow_change_subj = Value};
754
+ allow_private_messages -> Config#config{allow_private_messages = Value};
755
+ allow_query_users -> Config#config{allow_query_users = Value};
756
+ allow_user_invites -> Config#config{allow_user_invites = Value};
757
+ anonymous -> Config#config{anonymous = Value};
758
+ logging -> Config#config{logging = Value};
759
+ max_users -> Config#config{max_users = Value};
760
+ members_by_default -> Config#config{members_by_default = Value};
761
+ members_only -> Config#config{members_only = Value};
762
+ moderated -> Config#config{moderated = Value};
763
+ password -> Config#config{password = Value};
764
+ password_protected -> Config#config{password_protected = Value};
765
+ persistent -> Config#config{persistent = Value};
766
+ public -> Config#config{public = Value};
767
+ public_list -> Config#config{public_list = Value};
768
+ title -> Config#config{title = Value}
769
+ end.
770
+
771
+
772
+ %%----------------------------
773
+ %% Get Room Affiliations
774
+ %%----------------------------
775
+
776
+ %% @spec(Name::string(), Service::string()) ->
777
+ %% [{JID::string(), Domain::string(), Role::string(), Reason::string()}]
778
+ %% @doc Get the affiliations of the room Name@Service.
779
+ get_room_affiliations(Name, Service) ->
780
+ case mnesia:dirty_read(muc_online_room, {Name, Service}) of
781
+ [R] ->
782
+ %% Get the PID of the online room, then request its state
783
+ Pid = R#muc_online_room.pid,
784
+ {ok, StateData} = gen_fsm:sync_send_all_state_event(Pid, get_state),
785
+ Affiliations = ?DICT:to_list(StateData#state.affiliations),
786
+ lists:map(
787
+ fun({{Uname, Domain, _Res}, {Aff, Reason}}) when is_atom(Aff)->
788
+ {Uname, Domain, Aff, Reason};
789
+ ({{Uname, Domain, _Res}, Aff}) when is_atom(Aff)->
790
+ {Uname, Domain, Aff, ""}
791
+ end, Affiliations);
792
+ [] ->
793
+ throw({error, "The room does not exist."})
794
+ end.
795
+
796
+ %%----------------------------
797
+ %% Change Room Affiliation
798
+ %%----------------------------
799
+
800
+ %% @spec(Name, Service, JID, AffiliationString) -> ok | {error, Error}
801
+ %% Name = string()
802
+ %% Service = string()
803
+ %% JID = string()
804
+ %% AffiliationString = "outcast" | "none" | "member" | "admin" | "owner"
805
+ %% @doc Set the affiliation of JID in the room Name@Service.
806
+ %% If the affiliation is 'none', the action is to remove,
807
+ %% In any other case the action will be to create the affiliation.
808
+ set_room_affiliation(Name, Service, JID, AffiliationString) ->
809
+ Affiliation = list_to_atom(AffiliationString),
810
+ case mnesia:dirty_read(muc_online_room, {Name, Service}) of
811
+ [R] ->
812
+ %% Get the PID for the online room so we can get the state of the room
813
+ Pid = R#muc_online_room.pid,
814
+ {ok, StateData} = gen_fsm:sync_send_all_state_event(Pid, get_state),
815
+ SJID = jlib:string_to_jid(JID),
816
+ LJID = jlib:jid_remove_resource(jlib:jid_tolower(SJID)),
817
+ Affiliations = change_affiliation(Affiliation, LJID, StateData#state.affiliations),
818
+ Res = StateData#state{affiliations = Affiliations},
819
+ {ok, _State} = gen_fsm:sync_send_all_state_event(Pid, {change_state, Res}),
820
+ mod_muc:store_room(Res#state.host, Res#state.room, make_opts(Res)),
821
+ ok;
822
+ [] ->
823
+ error
824
+ end.
825
+
826
+ change_affiliation(none, LJID, Affiliations) ->
827
+ ?DICT:erase(LJID, Affiliations);
828
+ change_affiliation(Affiliation, LJID, Affiliations) ->
829
+ ?DICT:store(LJID, Affiliation, Affiliations).
830
+
831
+ -define(MAKE_CONFIG_OPT(Opt), {Opt, Config#config.Opt}).
832
+
833
+ make_opts(StateData) ->
834
+ Config = StateData#state.config,
835
+ [
836
+ ?MAKE_CONFIG_OPT(title),
837
+ ?MAKE_CONFIG_OPT(allow_change_subj),
838
+ ?MAKE_CONFIG_OPT(allow_query_users),
839
+ ?MAKE_CONFIG_OPT(allow_private_messages),
840
+ ?MAKE_CONFIG_OPT(public),
841
+ ?MAKE_CONFIG_OPT(public_list),
842
+ ?MAKE_CONFIG_OPT(persistent),
843
+ ?MAKE_CONFIG_OPT(moderated),
844
+ ?MAKE_CONFIG_OPT(members_by_default),
845
+ ?MAKE_CONFIG_OPT(members_only),
846
+ ?MAKE_CONFIG_OPT(allow_user_invites),
847
+ ?MAKE_CONFIG_OPT(password_protected),
848
+ ?MAKE_CONFIG_OPT(password),
849
+ ?MAKE_CONFIG_OPT(anonymous),
850
+ ?MAKE_CONFIG_OPT(logging),
851
+ ?MAKE_CONFIG_OPT(max_users),
852
+ {affiliations, ?DICT:to_list(StateData#state.affiliations)},
853
+ {subject, StateData#state.subject},
854
+ {subject_author, StateData#state.subject_author}
855
+ ].
856
+
857
+
858
+ %%----------------------------
859
+ %% Utils
860
+ %%----------------------------
861
+
862
+ uptime_seconds() ->
863
+ trunc(element(1, erlang:statistics(wall_clock))/1000).
864
+
865
+ find_host(global) ->
866
+ global;
867
+ find_host("global") ->
868
+ global;
869
+ find_host(ServerHost) ->
870
+ gen_mod:get_module_opt_host(ServerHost, mod_muc, "conference.@HOST@").
871
+