#define _GNU_SOURCE #include #include #include #include #include #include #include "weechat-plugin.h" #include "net.h" WEECHAT_PLUGIN_NAME("tomsg") WEECHAT_PLUGIN_DESCRIPTION("tomsg client plugin") WEECHAT_PLUGIN_AUTHOR("Tom Smeding") WEECHAT_PLUGIN_VERSION("0.1") WEECHAT_PLUGIN_LICENSE("MIT") WEECHAT_PLUGIN_PRIORITY(1000) static const char *errpfx; struct roomdata{ char *name; struct t_gui_buffer *buffer; struct conndata *conn; // do not free }; struct conndata{ int fd; struct t_hook *fd_hook; struct t_gui_buffer *buffer; int nrooms,roomscap; struct roomdata **rooms; char *username,*pending_username; i64 linebuf_sz,linebuf_len; char *linebuf; }; FILE *debugf; static struct t_weechat_plugin *weechat_plugin; static struct t_hashtable *conntable; static void close_room(struct roomdata *room){ fprintf(debugf,"close_room(room=%p)\n",room); free(room->name); if(room->buffer)weechat_buffer_close(room->buffer); free(room); } static void message_net_callback(int fd,struct net_response res,void *payload){ (void)payload; fprintf(debugf,"message_net_callback(fd=%d,res={.type=%d})\n",fd,res.type); struct conndata *conn=weechat_hashtable_get(conntable,&fd); assert(conn); if(res.type==NET_ERROR){ weechat_printf(conn->buffer,"tomsg: send threw error: %s",res.error); } else if(res.type!=NET_OK){ fprintf(debugf,"message_net_callback: res.type=%d\n",res.type); } } static int room_input_cb(const void *room_vp,void *_d,struct t_gui_buffer *buffer,const char *input){ (void)_d; (void)buffer; struct roomdata *room=(struct roomdata*)room_vp; struct conndata *conn=room->conn; const char *tosend; const char *p=strchr(input,'\n'); if(p!=NULL){ fprintf(debugf,"room_input_cb: input contained newline <%s>\n",input); tosend=strdup(input); *strchr(tosend,'\n')='\0'; } else { tosend=input; } net_sendf(conn->fd,message_net_callback,NULL,"send %s %s",room->name,tosend); weechat_printf(room->buffer,"%s\t%s",conn->username,tosend); if(tosend!=input){ free((void*)tosend); } return WEECHAT_RC_OK; } static int room_close_cb(const void *room_vp,void *_d,struct t_gui_buffer *buffer){ (void)_d; (void)buffer; struct roomdata *room=(struct roomdata*)room_vp; room->buffer=NULL; return WEECHAT_RC_OK; } static void push_net_callback(int fd,struct net_response res,void *payload){ (void)payload; fprintf(debugf,"push_net_callback(fd=%d,res={.type=%d})\n",fd,res.type); struct conndata *conn=weechat_hashtable_get(conntable,&fd); assert(conn); if(res.type==NET_MESSAGE){ i64 roomi; for(roomi=0;roominrooms;roomi++){ if(strcmp(conn->rooms[roomi]->name,res.room)==0){ break; } } if(roomi==conn->nrooms){ fprintf(debugf,"push_net_callback: message to unknown room '%s'\n",res.room); return; } struct roomdata *room=conn->rooms[roomi]; if(room->buffer==NULL){ room->buffer=weechat_buffer_new( room->name, room_input_cb,room,NULL, room_close_cb,room,NULL); } weechat_printf_date_tags( room->buffer,res.timestamp/1000000LL,NULL, "%s\t%s",res.username,res.message); } else { fprintf(debugf,"push_net_callback: unknown response type %d\n",res.type); } } static void history_push_net_callback(int fd,struct net_response res,void *payload){ push_net_callback(fd,res,payload); } static void history_net_callback(int fd,struct net_response res,void *payload){ struct roomdata *room=(struct roomdata*)payload; assert(room); fprintf(debugf,"history_net_callback(fd=%d,res={.type=%d})\n",fd,res.type); } static void roomlist_net_callback(int fd,struct net_response res,void *payload){ (void)payload; fprintf(debugf,"roomlist_net_callback(fd=%d,res={.type=%d})\n",fd,res.type); struct conndata *conn=weechat_hashtable_get(conntable,&fd); assert(conn); if(res.type==NET_LIST){ for(i64 i=0;inrooms;i++){ close_room(conn->rooms[i]); } if(conn->roomscaproomscap=res.nitems+1; conn->rooms=realloc(conn->rooms,conn->roomscap*sizeof(struct roomdata*)); assert(conn->rooms); } conn->nrooms=res.nitems; for(i64 i=0;irooms[i]=malloc(sizeof(struct roomdata)); conn->rooms[i]->name=strdup(res.items[i]); conn->rooms[i]->buffer=weechat_buffer_new( conn->rooms[i]->name, room_input_cb,conn->rooms[i],NULL, room_close_cb,conn->rooms[i],NULL); conn->rooms[i]->conn=conn; net_sendf(fd,history_net_callback,conn->rooms[i],"history %s 10",conn->rooms[i]->name); } } else { fprintf(debugf,"roomlist_net_callback: res.type=%d\n",res.type); } } static void login_net_callback(int fd,struct net_response res,void *payload){ (void)payload; fprintf(debugf,"login_net_callback(fd=%d,res={.type=%d})\n",fd,res.type); struct conndata *conn=weechat_hashtable_get(conntable,&fd); assert(conn); if(res.type==NET_OK){ if(conn->username)free(conn->username); conn->username=conn->pending_username; conn->pending_username=NULL; weechat_printf(conn->buffer,"Successfully logged in"); net_sendf(conn->fd,roomlist_net_callback,NULL,"list_rooms"); } else if(res.type==NET_ERROR){ weechat_printf(conn->buffer,"Error logging in: %s",res.error); } else { fprintf(debugf,"login_net_callback: res.type=%d\n",res.type); } } static void conn_destroy(struct conndata *conn){ fprintf(debugf,"conn_destroy(conn=%p (fd=%d))\n",conn,conn->fd); weechat_unhook(conn->fd_hook); if(conntable)weechat_hashtable_remove(conntable,&conn->fd); for(int i=0;inrooms;i++){ close_room(conn->rooms[i]); } if(conn->username)free(conn->username); if(conn->pending_username)free(conn->pending_username); free(conn->rooms); free(conn->linebuf); close(conn->fd); free(conn); } static int fd_hook_callback(const void *conn_vp,void *_d,int fd){ (void)_d; struct conndata *conn=(struct conndata*)conn_vp; fprintf(debugf,"fd_hook_callback(conn=%p (fd=%d))\n",conn,fd); assert(fd==conn->fd); if(conn->linebuf_len>conn->linebuf_sz/2){ conn->linebuf_sz*=2; conn->linebuf=realloc(conn->linebuf,conn->linebuf_sz); assert(conn->linebuf); } i64 nr=recv(fd,conn->linebuf+conn->linebuf_len,conn->linebuf_sz-conn->linebuf_len,0); if(nr<=0){ fprintf(debugf,"fd_hook_callback: recv() <= 0\n"); weechat_printf(NULL,"tomsg: Connection dropped"); weechat_buffer_close(conn->buffer); return WEECHAT_RC_OK; } conn->linebuf_len+=nr; while(true){ char *p=memchr(conn->linebuf,'\n',conn->linebuf_len); if(p==NULL)break; *p='\0'; i64 lenp1=p-conn->linebuf+1; net_handle_recv(conn->fd,conn->linebuf); memmove(conn->linebuf,conn->linebuf+lenp1,conn->linebuf_len-lenp1); conn->linebuf_len-=lenp1; } return WEECHAT_RC_OK; } static int conn_input_cb(const void *conn_vp,void *_d,struct t_gui_buffer *buffer,const char *input){ (void)_d; struct conndata *conn=(struct conndata*)conn_vp; fprintf(debugf,"conn_input_cb(conn=%p,buffer=%p,input=\"%s\")\n",conn,buffer,input); char *input2=strdup(input); assert(input2); char *cursor=input2; char *cmd=strsep(&cursor," "); if(cmd==NULL){ free(input2); return WEECHAT_RC_OK; } if(strcmp(cmd,"login")==0){ char *username=strsep(&cursor," "); char *password=cursor; if(conn->pending_username)free(conn->pending_username); conn->pending_username=strdup(username); net_sendf(conn->fd,login_net_callback,NULL,"login %s %s",username,password); } else { weechat_printf(conn->buffer,"Unknown command '%s'",cmd); } return WEECHAT_RC_OK; } static int conn_close_cb(const void *conn_vp,void *_d,struct t_gui_buffer *buffer){ (void)_d; (void)buffer; struct conndata *conn=(struct conndata*)conn_vp; fprintf(debugf,"conn_close_cb(conn=%p,buffer=%p) fd=%d\n",conn,buffer,conn->fd); conn_destroy(conn); return WEECHAT_RC_OK; } static int connect_cb(const void *_p,void *hostname,int status,int _g,int fd,const char *err,const char *_i){ (void)_p; (void)_g; (void)_i; switch(status){ case WEECHAT_HOOK_CONNECT_OK: { struct conndata *conn=malloc(sizeof(struct conndata)); assert(conn); conn->fd=fd; conn->fd_hook=weechat_hook_fd(fd,1,0,0,fd_hook_callback,conn,NULL); conn->buffer=weechat_buffer_new((char*)hostname,conn_input_cb,conn,NULL,conn_close_cb,conn,NULL); conn->nrooms=0; conn->roomscap=2; conn->rooms=malloc(conn->roomscap*sizeof(struct roomdata)); assert(conn->rooms); conn->username=NULL; conn->pending_username=NULL; conn->linebuf_sz=512; conn->linebuf_len=0; conn->linebuf=malloc(conn->linebuf_sz); assert(conn->linebuf); weechat_printf(conn->buffer,"Connected!"); weechat_hashtable_set(conntable,&fd,conn); return WEECHAT_RC_OK; } default: weechat_printf(NULL,"%stomsg: Could not connect to %s: %s",errpfx,(char*)hostname,err); return WEECHAT_RC_ERROR; } } static int cmd_tomsg_cb(const void *_p,void *_d,struct t_gui_buffer *buffer,int argc,char **argv,char **_a){ (void)_p; (void)_d; (void)_a; if(argc<2){ weechat_printf(buffer,"%stomsg: Invalid number of arguments to /tomsg",errpfx); return WEECHAT_RC_ERROR; } if(strcmp(argv[1],"connect")==0){ if(argc<3||argc>4){ weechat_printf(buffer,"%stomsg: Invalid number of arguments to /tomsg connect",errpfx); return WEECHAT_RC_ERROR; } char *hostname=argv[2]; char *endp; int port; if(argc==4){ port=strtol(argv[3],&endp,10); if(argv[3][0]=='\0'||*endp!='\0'||port<=0||port>=65536){ weechat_printf(buffer,"%sInvalid port number",errpfx); return WEECHAT_RC_ERROR; } } else { port=29536; } if(strlen(hostname)==0){ weechat_printf(buffer,"%stomsg: Invalid hostname",errpfx); return WEECHAT_RC_ERROR; } fprintf(debugf,"Connecting to %s:%d\n",hostname,port); char *hostname_copy=strdup(hostname); weechat_hook_connect( NULL, hostname,port,1,0, NULL,NULL,0,NULL, NULL, connect_cb,NULL,hostname_copy); } else { weechat_printf(buffer,"%stomsg: Unknown command \"%s\" to /tomsg",errpfx,argv[1]); return WEECHAT_RC_ERROR; } return WEECHAT_RC_OK; } int weechat_plugin_init(struct t_weechat_plugin *plugin,int argc,char **argv){ (void)argc; (void)argv; weechat_plugin=plugin; char *fname; asprintf(&fname,"%s/Desktop/debugf.txt",getenv("HOME")); debugf=fopen(fname,"w"); free(fname); setvbuf(debugf,NULL,_IONBF,0); fprintf(debugf,"------\n"); errpfx=weechat_prefix("error"); weechat_hook_command( "tomsg", "Execute commands related to tomsg.", "connect [port]", " connect: Connect to a tomsg server", NULL, cmd_tomsg_cb,NULL,NULL); net_set_push_callback(push_net_callback); net_set_history_callback(history_push_net_callback); conntable=weechat_hashtable_new( 16,WEECHAT_HASHTABLE_INTEGER,WEECHAT_HASHTABLE_POINTER,NULL,NULL); return WEECHAT_RC_OK; } int weechat_plugin_end(struct t_weechat_plugin *plugin){ (void)plugin; weechat_hashtable_free(conntable); conntable=NULL; fclose(debugf); return WEECHAT_RC_OK; }