1 /*
2 channel-rejoin.c : rejoin to channel if it's "temporarily unavailable"
3 this has nothing to do with autorejoin if kicked
4
5 Copyright (C) 2000 Timo Sirainen
6
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
11
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License along
18 with this program; if not, write to the Free Software Foundation, Inc.,
19 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 */
21
22 #include "module.h"
23 #include "signals.h"
24 #include "settings.h"
25 #include "misc.h"
26
27 #include "irc-servers.h"
28 #include "irc-channels.h"
29 #include "irc-commands.h"
30 #include "channel-rejoin.h"
31
32 #define REJOIN_TIMEOUT (1000*60*5) /* try to rejoin every 5 minutes */
33
34 static int rejoin_tag;
35
rejoin_destroy(IRC_SERVER_REC * server,REJOIN_REC * rec)36 static void rejoin_destroy(IRC_SERVER_REC *server, REJOIN_REC *rec)
37 {
38 g_return_if_fail(IS_IRC_SERVER(server));
39 g_return_if_fail(rec != NULL);
40
41 server->rejoin_channels =
42 g_slist_remove(server->rejoin_channels, rec);
43
44 signal_emit("channel rejoin remove", 2, server, rec);
45
46 g_free(rec->channel);
47 g_free_not_null(rec->key);
48 g_free(rec);
49 }
50
rejoin_find(IRC_SERVER_REC * server,const char * channel)51 static REJOIN_REC *rejoin_find(IRC_SERVER_REC *server, const char *channel)
52 {
53 GSList *tmp;
54
55 g_return_val_if_fail(IS_IRC_SERVER(server), NULL);
56 g_return_val_if_fail(channel != NULL, NULL);
57
58 for (tmp = server->rejoin_channels; tmp != NULL; tmp = tmp->next) {
59 REJOIN_REC *rec = tmp->data;
60
61 if (g_ascii_strcasecmp(rec->channel, channel) == 0)
62 return rec;
63 }
64
65 return NULL;
66 }
67
68 #define channel_have_key(chan) \
69 ((chan) != NULL && (chan)->key != NULL && (chan)->key[0] != '\0')
70
channel_rejoin(IRC_SERVER_REC * server,const char * channel)71 static int channel_rejoin(IRC_SERVER_REC *server, const char *channel)
72 {
73 IRC_CHANNEL_REC *chanrec;
74 REJOIN_REC *rec;
75
76 g_return_val_if_fail(IS_IRC_SERVER(server), 0);
77 g_return_val_if_fail(channel != NULL, 0);
78
79 chanrec = irc_channel_find(server, channel);
80 if (chanrec == NULL || chanrec->joined) return 0;
81
82 if (!settings_get_bool("channels_rejoin_unavailable")) {
83 chanrec->left = TRUE;
84 channel_destroy(CHANNEL(chanrec));
85 return 0;
86 }
87
88 rec = rejoin_find(server, channel);
89 if (rec != NULL) {
90 /* already exists */
91 rec->joining = FALSE;
92
93 /* update channel key */
94 g_free_and_null(rec->key);
95 if (channel_have_key(chanrec))
96 rec->key = g_strdup(chanrec->key);
97 } else {
98 /* new rejoin */
99 rec = g_new0(REJOIN_REC, 1);
100 rec->channel = g_strdup(channel);
101 if (channel_have_key(chanrec))
102 rec->key = g_strdup(chanrec->key);
103
104 server->rejoin_channels =
105 g_slist_append(server->rejoin_channels, rec);
106 signal_emit("channel rejoin new", 2, server, rec);
107 }
108
109 chanrec->left = TRUE;
110 channel_destroy(CHANNEL(chanrec));
111 return 1;
112 }
113
event_duplicate_channel(IRC_SERVER_REC * server,const char * data)114 static void event_duplicate_channel(IRC_SERVER_REC *server, const char *data)
115 {
116 CHANNEL_REC *chanrec;
117 char *params, *channel, *p;
118
119 g_return_if_fail(data != NULL);
120
121 params = event_get_params(data, 3, NULL, NULL, &channel);
122 p = strchr(channel, ' ');
123 if (p != NULL) *p = '\0';
124
125 if (channel[0] == '!' && channel[1] != '!') {
126 chanrec = channel_find(SERVER(server), channel);
127 if (chanrec != NULL && !chanrec->names_got) {
128 /* duplicate channel - this should only happen when
129 there's some sync problem with servers, rejoining
130 after a while should help.
131
132 note that this same 407 is sent when trying to
133 create !!channel that already exists so we don't
134 want to try rejoining then. */
135 if (channel_rejoin(server, channel)) {
136 signal_stop();
137 }
138 }
139 }
140
141 g_free(params);
142 }
143
event_target_unavailable(IRC_SERVER_REC * server,const char * data)144 static void event_target_unavailable(IRC_SERVER_REC *server, const char *data)
145 {
146 char *params, *channel;
147 IRC_CHANNEL_REC *chanrec;
148
149 g_return_if_fail(data != NULL);
150
151 params = event_get_params(data, 2, NULL, &channel);
152 if (server_ischannel(SERVER(server), channel)) {
153 chanrec = irc_channel_find(server, channel);
154 if (chanrec != NULL && chanrec->joined) {
155 /* dalnet event - can't change nick while
156 banned in channel */
157 } else {
158 /* channel is unavailable - try to join again
159 a bit later */
160 if (channel_rejoin(server, channel)) {
161 signal_stop();
162 }
163 }
164 }
165
166 g_free(params);
167 }
168
169 /* join ok/failed - remove from rejoins list. this happens always after join
170 except if the "target unavailable" error happens again */
sig_remove_rejoin(IRC_CHANNEL_REC * channel)171 static void sig_remove_rejoin(IRC_CHANNEL_REC *channel)
172 {
173 REJOIN_REC *rec;
174
175 if (!IS_IRC_CHANNEL(channel))
176 return;
177
178 rec = rejoin_find(channel->server, channel->name);
179 if (rec != NULL && rec->joining) {
180 /* join failed, remove the rejoin */
181 rejoin_destroy(channel->server, rec);
182 }
183 }
184
sig_disconnected(IRC_SERVER_REC * server)185 static void sig_disconnected(IRC_SERVER_REC *server)
186 {
187 if (!IS_IRC_SERVER(server))
188 return;
189
190 while (server->rejoin_channels != NULL)
191 rejoin_destroy(server, server->rejoin_channels->data);
192 }
193
server_rejoin_channels(IRC_SERVER_REC * server)194 static void server_rejoin_channels(IRC_SERVER_REC *server)
195 {
196 GSList *tmp, *next;
197 GString *channels, *keys;
198 int use_keys;
199
200 g_return_if_fail(IS_IRC_SERVER(server));
201
202 channels = g_string_new(NULL);
203 keys = g_string_new(NULL);
204
205 use_keys = FALSE;
206 for (tmp = server->rejoin_channels; tmp != NULL; tmp = next) {
207 REJOIN_REC *rec = tmp->data;
208 next = tmp->next;
209
210 if (rec->joining) {
211 /* we missed the join (failed) message,
212 remove from rejoins.. */
213 rejoin_destroy(server, rec);
214 continue;
215 }
216
217 rec->joining = TRUE;
218 g_string_append_printf(channels, "%s,", rec->channel);
219 if (rec->key == NULL)
220 g_string_append(keys, "x,");
221 else {
222 g_string_append_printf(keys, "%s,", rec->key);
223 use_keys = TRUE;
224 }
225 }
226
227 if (channels->len > 0) {
228 g_string_truncate(channels, channels->len-1);
229 g_string_truncate(keys, keys->len-1);
230
231 if (use_keys) g_string_append_printf(channels, " %s", keys->str);
232 server->channels_join(SERVER(server), channels->str, TRUE);
233 }
234
235 g_string_free(channels, TRUE);
236 g_string_free(keys, TRUE);
237 }
238
sig_rejoin(void)239 static int sig_rejoin(void)
240 {
241 GSList *tmp;
242
243 for (tmp = servers; tmp != NULL; tmp = tmp->next) {
244 IRC_SERVER_REC *rec = tmp->data;
245
246 if (IS_IRC_SERVER(rec) && rec->rejoin_channels != NULL)
247 server_rejoin_channels(rec);
248 }
249
250 return TRUE;
251 }
252
cmd_rmrejoins(const char * data,IRC_SERVER_REC * server)253 static void cmd_rmrejoins(const char *data, IRC_SERVER_REC *server)
254 {
255 CMD_IRC_SERVER(server);
256
257 while (server->rejoin_channels != NULL)
258 rejoin_destroy(server, server->rejoin_channels->data);
259 }
260
channel_rejoin_init(void)261 void channel_rejoin_init(void)
262 {
263 settings_add_bool("servers", "channels_rejoin_unavailable", TRUE);
264
265 rejoin_tag = g_timeout_add(REJOIN_TIMEOUT,
266 (GSourceFunc) sig_rejoin, NULL);
267
268 command_bind_irc("rmrejoins", NULL, (SIGNAL_FUNC) cmd_rmrejoins);
269 signal_add_first("event 407", (SIGNAL_FUNC) event_duplicate_channel);
270 signal_add_first("event 437", (SIGNAL_FUNC) event_target_unavailable);
271 signal_add_first("channel joined", (SIGNAL_FUNC) sig_remove_rejoin);
272 signal_add_first("channel destroyed", (SIGNAL_FUNC) sig_remove_rejoin);
273 signal_add("server disconnected", (SIGNAL_FUNC) sig_disconnected);
274 }
275
channel_rejoin_deinit(void)276 void channel_rejoin_deinit(void)
277 {
278 g_source_remove(rejoin_tag);
279
280 command_unbind("rmrejoins", (SIGNAL_FUNC) cmd_rmrejoins);
281 signal_remove("event 407", (SIGNAL_FUNC) event_duplicate_channel);
282 signal_remove("event 437", (SIGNAL_FUNC) event_target_unavailable);
283 signal_remove("channel joined", (SIGNAL_FUNC) sig_remove_rejoin);
284 signal_remove("channel destroyed", (SIGNAL_FUNC) sig_remove_rejoin);
285 signal_remove("server disconnected", (SIGNAL_FUNC) sig_disconnected);
286 }
287