451dcc56b399060d05a759e8a8f10d3233d19a18
[doldaconnect.git] / clients / gaim / gp-dolcon.c
1 /*
2  *  Dolda Connect - Modular multiuser Direct Connect-style client
3  *  Copyright (C) 2005 Fredrik Tolf (fredrik@dolda2000.com)
4  *  
5  *  This program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2 of the License, or
8  *  (at your option) any later version.
9  *  
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *  
15  *  You should have received a copy of the GNU General Public License
16  *  along with this program; if not, write to the Free Software
17  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18 */
19
20 #ifdef HAVE_CONFIG_H
21 #include <config.h>
22 #endif
23 #include <string.h>
24 #include <doldaconnect/uilib.h>
25 #include <doldaconnect/uimisc.h>
26 #include <doldaconnect/utils.h>
27 #include <plugin.h>
28 #include <version.h>
29 #include <accountopt.h>
30 #include <roomlist.h>
31 #include <util.h>
32 #include <errno.h>
33
34 struct conndata {
35     int fd;
36     int readhdl, writehdl;
37     G/PCConnection *gc;
38     G/PCRoomlist *roomlist;
39 };
40
41 static struct conndata *inuse = NULL;
42 static G/PCPlugin *me;
43
44 static void dcfdcb(struct conndata *data, int fd, G/PCInputCondition condition);
45
46 static void updatewrite(struct conndata *data)
47 {
48     if(dc_wantwrite()) {
49         if(data->writehdl == -1)
50             data->writehdl = G/P_input_add(data->fd, G/PU_INPUT_WRITE, (void (*)(void *, int, G/PCInputCondition))dcfdcb, data);
51     } else {
52         if(data->writehdl != -1) {
53             G/P_input_remove(data->writehdl);
54             data->writehdl = -1;
55         }
56     }
57 }
58
59 static void disconnected(struct conndata *data)
60 {
61     if(inuse == data)
62         inuse = NULL;
63     if(data->readhdl != -1) {
64         G/P_input_remove(data->readhdl);
65         data->readhdl = -1;
66     }
67     if(data->writehdl != -1) {
68         G/P_input_remove(data->writehdl);
69         data->writehdl = -1;
70     }
71     data->fd = -1;
72 }
73
74 static int loginconv(int type, wchar_t *text, char **resp, struct conndata *data)
75 {
76     switch(type) {
77     case DC_LOGIN_CONV_NOECHO:
78         if(data->gc->account->password == NULL) {
79             updatewrite(data);
80             return(1);
81         } else {
82             *resp = sstrdup(data->gc->account->password);
83             updatewrite(data);
84             return(0);
85         }
86     default:
87         updatewrite(data);
88         return(1);
89     }
90 }
91
92 static gboolean gi_chatjoincb(G/PCConversation *conv, const char *user, G/PCConvChatBuddyFlags flags, void *uudata)
93 {
94     G/PCConnection *c;
95     
96     if((c = G/P_conversation_get_gc(conv)) == NULL)
97         return(FALSE);
98     if(c->prpl == me)
99         return(TRUE);
100     return(FALSE);
101 }
102
103 static gboolean gi_chatleavecb(G/PCConversation *conv, const char *user, const char *reason, void *uudata)
104 {
105     G/PCConnection *c;
106     
107     if((c = G/P_conversation_get_gc(conv)) == NULL)
108         return(FALSE);
109     if(c->prpl == me)
110         return(TRUE);
111     return(FALSE);
112 }
113
114 static void regsigs(void)
115 {
116     static G/PCPlugin *regged = NULL;
117     
118     if(regged != me) {
119         G/P_signal_connect(G/P_conversations_get_handle(), "chat-buddy-joining", me, G/PU_CALLBACK(gi_chatjoincb), NULL);
120         G/P_signal_connect(G/P_conversations_get_handle(), "chat-buddy-leaving", me, G/PU_CALLBACK(gi_chatleavecb), NULL);
121         regged = me;
122     }
123 }
124
125 static void newpeercb(struct dc_fnetpeer *peer)
126 {
127     struct conndata *data;
128     G/PCConversation *conv;
129     char *buf;
130     
131     data = peer->fn->udata;
132     if((conv = G/P_find_chat(data->gc, peer->fn->id)) != NULL) {
133         buf = sprintf2("%s", icswcstombs(peer->nick, "UTF-8", NULL));
134         G/P_conv_chat_add_user(G/PU_CONV_CHAT(conv), buf, NULL, G/PU_CBFLAGS_NONE, TRUE);
135         free(buf);
136     }
137 }
138
139 static void delpeercb(struct dc_fnetpeer *peer)
140 {
141     struct conndata *data;
142     G/PCConversation *conv;
143     char *buf;
144     
145     data = peer->fn->udata;
146     if((conv = G/P_find_chat(data->gc, peer->fn->id)) != NULL) {
147         buf = sprintf2("%s", icswcstombs(peer->nick, "UTF-8", NULL));
148         G/P_conv_chat_remove_user(G/PU_CONV_CHAT(conv), buf, NULL);
149         free(buf);
150     }
151 }
152
153 static void chpeercb(struct dc_fnetpeer *peer)
154 {
155 }
156
157 static void fillpeerlist(struct dc_fnetnode *fn, int resp, struct conndata *data)
158 {
159 }
160
161 static void getfnlistcb(int resp, struct conndata *data)
162 {
163     struct dc_fnetnode *fn;
164     
165     for(fn = dc_fnetnodes; fn != NULL; fn = fn->next)
166     {
167         dc_getpeerlistasync(fn, (void (*)(struct dc_fnetnode *, int, void *))fillpeerlist, data);
168         fn->udata = data;
169         fn->newpeercb = newpeercb;
170         fn->delpeercb = delpeercb;
171         fn->chpeercb = chpeercb;
172     }
173 }
174
175 static void logincb(int err, wchar_t *reason, struct conndata *data)
176 {
177     if(err != DC_LOGIN_ERR_SUCCESS) {
178         dc_disconnect();
179         disconnected(data);
180         G/P_connection_error(data->gc, "Invalid login");
181         return;
182     }
183     G/P_connection_set_state(data->gc, G/PU_CONNECTED);
184     dc_queuecmd(NULL, NULL, L"notify", L"fn:chat", L"on", L"fn:act", L"on", L"fn:peer", L"on", NULL);
185     dc_getfnlistasync((void (*)(int, void *))getfnlistcb, data);
186 }
187
188 static void dcfdcb(struct conndata *data, int fd, G/PCInputCondition condition)
189 {
190     struct dc_response *resp;
191     struct dc_intresp *ires;
192     struct dc_fnetnode *fn;
193     G/PCConversation *conv;
194     char *peer, *msg;
195     
196     if(((condition & G/PU_INPUT_READ) && dc_handleread()) || ((condition & G/PU_INPUT_WRITE) && dc_handlewrite())) {
197         disconnected(data);
198         G/P_connection_error(data->gc, "Server has disconnected");
199         return;
200     }
201     while((resp = dc_getresp()) != NULL) {
202         if(!wcscmp(resp->cmdname, L".connect")) {
203             if(resp->code != 201) {
204                 dc_disconnect();
205                 disconnected(data);
206                 G/P_connection_error(data->gc, "Server refused connection");
207                 return;
208             } else if(dc_checkprotocol(resp, DC_LATEST)) {
209                 dc_disconnect();
210                 disconnected(data);
211                 G/P_connection_error(data->gc, "Server protocol revision mismatch");
212                 return;
213             } else {
214                 G/P_connection_update_progress(data->gc, "Authenticating", 2, 3);
215                 dc_loginasync(NULL, 1, (int (*)(int, wchar_t *, char **, void *))loginconv, (void (*)(int, wchar_t *, void *))logincb, data);
216             }
217         } else if(!wcscmp(resp->cmdname, L".notify")) {
218             dc_uimisc_handlenotify(resp);
219             switch(resp->code) {
220             case 600:
221                 if((ires = dc_interpret(resp)) != NULL)
222                 {
223                     if((fn = dc_findfnetnode(ires->argv[0].val.num)) != NULL)
224                     {
225                         if(ires->argv[1].val.num)
226                         {
227                             /* XXX: Handle different rooms */
228                             if((conv = G/P_find_chat(data->gc, fn->id)) != NULL)
229                             {
230                                 peer = icwcstombs(ires->argv[3].val.str, "UTF-8");
231                                 msg = g_markup_escape_text(icswcstombs(ires->argv[4].val.str, "UTF-8", NULL), -1);
232                                 serv_got_chat_in(data->gc, G/P_conv_chat_get_id(G/PU_CONV_CHAT(conv)), peer, 0, msg, time(NULL));
233                                 g_free(msg);
234                                 free(peer);
235                             }
236                         } else {
237                             peer = sprintf2("%i:%s", fn->id, icswcstombs(ires->argv[3].val.str, "UTF-8", NULL));
238                             msg = g_markup_escape_text(icswcstombs(ires->argv[4].val.str, "UTF-8", NULL), -1);
239                             if(!G/P_account_get_bool(data->gc->account, "represspm", FALSE) || (G/P_find_conversation_with_account(G/PU_CONV_TYPE_IM, peer, data->gc->account) != NULL))
240                                 serv_got_im(data->gc, peer, msg, 0, time(NULL));
241                             g_free(msg);
242                             free(peer);
243                         }
244                     }
245                     dc_freeires(ires);
246                 }
247                 break;
248             case 601:
249             case 602:
250             case 603:
251                 break;
252             case 604:
253                 if((ires = dc_interpret(resp)) != NULL)
254                 {
255                     if((fn = dc_findfnetnode(ires->argv[0].val.num)) != NULL)
256                     {
257                         fn->udata = data;
258                         fn->newpeercb = newpeercb;
259                         fn->delpeercb = delpeercb;
260                         fn->chpeercb = chpeercb;
261                     }
262                     dc_freeires(ires);
263                 }
264                 break;
265             case 605:
266                 break;
267             }
268         }
269         dc_freeresp(resp);
270     }
271     updatewrite(data);
272 }
273
274 static int gi_sendchat(G/PCConnection *gc, int id, const char *what, G/PCMessageFlags flags)
275 {
276     struct conndata *data;
277     struct dc_fnetnode *fn;
278     wchar_t *wwhat;
279     
280     data = gc->proto_data;
281     if((fn = dc_findfnetnode(id)) == NULL)
282         return(-EINVAL);
283     /* XXX: Handle chat rooms */
284     if((wwhat = icmbstowcs((char *)what, "UTF-8")) == NULL)
285         return(-errno);
286     dc_queuecmd(NULL, NULL, L"sendchat", L"%i", fn->id, L"1", L"", L"%ls", wwhat, NULL);
287     free(wwhat);
288     updatewrite(data);
289     return(0);
290 }
291
292 static int gi_sendim(G/PCConnection *gc, const char *who, const char *what, G/PCMessageFlags flags)
293 {
294     struct conndata *data;
295     struct dc_fnetnode *fn;
296     struct dc_fnetpeer *peer;
297     wchar_t *wwho, *wwhat, *p;
298     int en, id;
299     
300     data = gc->proto_data;
301     if((wwho = icmbstowcs((char *)who, "UTF-8")) == NULL)
302         return(-errno);
303     if((p = wcschr(wwho, L':')) == NULL) {
304         free(wwho);
305         return(-ESRCH);
306     }
307     *(p++) = L'\0';
308     id = wcstol(wwho, NULL, 10);
309     if((fn = dc_findfnetnode(id)) == NULL) {
310         free(wwho);
311         return(-ESRCH);
312     }
313     for(peer = fn->peers; peer != NULL; peer = peer->next) {
314         if(!wcscmp(peer->nick, p))
315             break;
316     }
317     if(peer == NULL) {
318         free(wwho);
319         return(-ESRCH);
320     }
321     if((wwhat = icmbstowcs((char *)what, "UTF-8")) == NULL) {
322         en = errno;
323         free(wwho);
324         return(-en);
325     }
326     dc_queuecmd(NULL, NULL, L"sendchat", L"%i", fn->id, L"0", L"%ls", peer->nick, L"%ls", wwhat, NULL);
327     free(wwho);
328     free(wwhat);
329     updatewrite(data);
330     return(1);
331 }
332
333 static const char *gi_listicon(G/PCAccount *a, G/PCBuddy *b)
334 {
335     return("dolcon");
336 }
337
338 static char *gi_statustext(G/PCBuddy *b)
339 {
340     G/PCPresence *p;
341
342     p = G/P_buddy_get_presence(b);
343     if (G/P_presence_is_online(p) && !G/P_presence_is_available(p))
344         return(g_strdup("Away"));
345     return(NULL);
346 }
347
348 static void gi_tiptext(G/PCBuddy *b, G/PCNotifyUserInfo *inf, gboolean full)
349 {
350     /* Nothing for now */
351 }
352
353 static GList *gi_statustypes(G/PCAccount *act)
354 {
355     GList *ret;
356     
357     ret = NULL;
358     ret = g_list_append(ret, G/P_status_type_new(G/PU_STATUS_AVAILABLE, "avail", NULL, TRUE));
359     ret = g_list_append(ret, G/P_status_type_new(G/PU_STATUS_AWAY, "away", NULL, TRUE)); /* Coming up in ADC */
360     ret = g_list_append(ret, G/P_status_type_new(G/PU_STATUS_OFFLINE, "offline", NULL, TRUE));
361     return(ret);
362 }
363
364 static struct conndata *newconndata(void)
365 {
366     struct conndata *new;
367     
368     new = smalloc(sizeof(*new));
369     memset(new, 0, sizeof(*new));
370     new->fd = -1;
371     new->readhdl = new->writehdl = -1;
372     return(new);
373 }
374
375 static void freeconndata(struct conndata *data)
376 {
377     if(data->roomlist != NULL)
378         G/P_roomlist_unref(data->roomlist);
379     if(inuse == data)
380         inuse = NULL;
381     if(data->readhdl != -1)
382         G/P_input_remove(data->readhdl);
383     if(data->writehdl != -1)
384         G/P_input_remove(data->writehdl);
385     if(data->fd >= 0)
386         dc_disconnect();
387     free(data);
388 }
389
390 static void gi_login(G/PCAccount *act)
391 {
392     G/PCConnection *gc;
393     struct conndata *data;
394     
395     gc = G/P_account_get_connection(act);
396     gc->proto_data = data = newconndata();
397     data->gc = gc;
398     if(inuse != NULL) {
399         G/P_connection_error(gc, "Dolda Connect library already in use");
400         return;
401     }
402     G/P_connection_update_progress(gc, "Connecting", 1, 3);
403     if((data->fd = dc_connect((char *)G/P_account_get_string(act, "server", "localhost"))) < 0)
404     {
405         G/P_connection_error(gc, "Could not connect to server");
406         return;
407     }
408     data->readhdl = G/P_input_add(data->fd, G/PU_INPUT_READ, (void (*)(void *, int, G/PCInputCondition))dcfdcb, data);
409     updatewrite(data);
410     inuse = data;
411 }
412
413 static void gi_close(G/PCConnection *gc)
414 {
415     struct conndata *data;
416     
417     data = gc->proto_data;
418     freeconndata(data);
419 }
420
421 static G/PCRoomlist *gi_getlist(G/PCConnection *gc)
422 {
423     struct conndata *data;
424     GList *fields;
425     G/PCRoomlist *rl;
426     G/PCRoomlistField *f;
427     G/PCRoomlistRoom *r;
428     struct dc_fnetnode *fn;
429     
430     data = gc->proto_data;
431     if(data->roomlist != NULL)
432         G/P_roomlist_unref(data->roomlist);
433     data->roomlist = rl = G/P_roomlist_new(G/P_connection_get_account(gc));
434     fields = NULL;
435     f = G/P_roomlist_field_new(G/PU_ROOMLIST_FIELD_INT, "", "id", TRUE);
436     fields = g_list_append(fields, f);
437     f = G/P_roomlist_field_new(G/PU_ROOMLIST_FIELD_INT, "Users", "users", FALSE);
438     fields = g_list_append(fields, f);
439     G/P_roomlist_set_fields(rl, fields);
440     for(fn = dc_fnetnodes; fn != NULL; fn = fn->next) {
441         if(fn->state != DC_FNN_STATE_EST)
442             continue;
443         r = G/P_roomlist_room_new(G/PU_ROOMLIST_ROOMTYPE_ROOM, icswcstombs(fn->name, "UTF-8", NULL), NULL);
444         G/P_roomlist_room_add_field(rl, r, GINT_TO_POINTER(fn->id));
445         G/P_roomlist_room_add_field(rl, r, GINT_TO_POINTER(fn->numusers));
446         G/P_roomlist_room_add(rl, r);
447     }
448     G/P_roomlist_set_in_progress(rl, FALSE);
449     return(rl);
450 }
451
452 static void gi_cancelgetlist(G/PCRoomlist *rl)
453 {
454     G/PCConnection *gc;
455     struct conndata *data;
456     
457     if((gc = G/P_account_get_connection(rl->account)) == NULL)
458         return;
459     data = gc->proto_data;
460     G/P_roomlist_set_in_progress(rl, FALSE);
461     if(data->roomlist == rl) {
462         data->roomlist = NULL;
463         G/P_roomlist_unref(rl);
464     }
465 }
466
467 static void gi_joinchat(G/PCConnection *gc, GHashTable *chatdata)
468 {
469     struct conndata *data;
470     struct dc_fnetnode *fn;
471     G/PCConversation *conv;
472     struct dc_fnetpeer *peer;
473     char *buf;
474     GList *ul, *fl, *c;
475     
476     regsigs();
477     data = gc->proto_data;
478     if((fn = dc_findfnetnode(GPOINTER_TO_INT(g_hash_table_lookup(chatdata, "id")))) == NULL)
479         return;
480     if(G/P_find_chat(gc, fn->id) != NULL)
481         return;
482     conv = serv_got_joined_chat(data->gc, fn->id, icswcstombs(fn->name, "UTF-8", NULL));
483     ul = fl = NULL;
484     for(peer = fn->peers; peer != NULL; peer = peer->next) {
485         buf = icwcstombs(peer->nick, "UTF-8");
486         ul = g_list_append(ul, buf);
487         fl = g_list_append(fl, GINT_TO_POINTER(0));
488     }
489     G/P_conv_chat_add_users(G/PU_CONV_CHAT(conv), ul, NULL, fl, FALSE);
490     for(c = ul; c != NULL; c = c->next)
491         free(c->data);
492     g_list_free(ul);
493     g_list_free(fl);
494 }
495
496 static char *gi_cbname(G/PCConnection *gc, int id, const char *who)
497 {
498     return(g_strdup_printf("%i:%s", id, who));
499 }
500
501 static G/PCPluginProtocolInfo protinfo = {
502     .options            = OPT_PROTO_PASSWORD_OPTIONAL,
503     .icon_spec          = NO_BUDDY_ICONS,
504     .list_icon          = gi_listicon,
505     .status_text        = gi_statustext,
506     .tooltip_text       = gi_tiptext,
507     .status_types       = gi_statustypes,
508     .login              = gi_login,
509     .close              = gi_close,
510     .roomlist_get_list  = gi_getlist,
511     .roomlist_cancel    = gi_cancelgetlist,
512     .join_chat          = gi_joinchat,
513     .chat_send          = gi_sendchat,
514     .send_im            = gi_sendim,
515     .get_cb_real_name   = gi_cbname,
516 };
517
518 static G/PCPluginInfo info = {
519     .magic              = G/PU_PLUGIN_MAGIC,
520     .major_version      = G/PU_MAJOR_VERSION,
521     .minor_version      = G/PU_MINOR_VERSION,
522     .type               = G/PU_PLUGIN_PROTOCOL,
523     .priority           = G/PU_PRIORITY_DEFAULT,
524     .id                 = "prpl-dolcon",
525     .name               = "Dolda Connect",
526     .version            = VERSION,
527     .summary            = "Dolda Connect chat plugin",
528     .description        = "Allows G/PC to be used as a chat user interface for the Dolda Connect daemon",
529     .author             = "Fredrik Tolf <fredrik@dolda2000.com>",
530     .homepage           = "http://www.dolda2000.com/~fredrik/doldaconnect/",
531     .extra_info         = &protinfo
532 };
533
534 static void init(G/PCPlugin *pl)
535 {
536     G/PCAccountOption *opt;
537     
538     dc_init();
539     opt = G/P_account_option_string_new("Server", "server", "");
540     protinfo.protocol_options = g_list_append(protinfo.protocol_options, opt);
541     opt = G/P_account_option_bool_new("Do not pop up private messages automatically", "represspm", FALSE);
542     protinfo.protocol_options = g_list_append(protinfo.protocol_options, opt);
543     me = pl;
544 }
545
546 G/PU_INIT_PLUGIN(dolcon, init, info);