#define _GNU_SOURCE #include #include #include #include #include #include #include #include #include "weechat-plugin.h" #include "net.h" #include "debug.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,*netpfx; #define PROTOCOL_VERSION 3 #define NICK_COLOR "default" #define NICK_AWAY_COLOR "weechat.color.nicklist_away" #define ALWAYS_PRIVATE true struct roomdata{ char *name; struct t_gui_buffer *buffer; struct t_gui_nick_group *buffer_nickgroup; int nmembers; int next_pending_id; struct conndata *conn; // do not free }; struct conndata{ int fd; struct t_hook *fd_hook; struct t_gui_buffer *buffer; bool negotiation_complete; int nrooms,roomscap; struct roomdata **rooms; char *username,*pending_username; i64 linebuf_sz,linebuf_len; char *linebuf; }; static struct t_weechat_plugin *weechat_plugin; static struct t_hashtable *conntable; // fd -> conndata static struct t_hashtable *roomtable; // roomname -> roomdata static i64 gettimestamp(void) { struct timeval tv; gettimeofday(&tv,NULL); return (i64)tv.tv_sec*1000000+tv.tv_usec; } static void display_message( struct roomdata *room, int64_t timestamp, const char *username, const char *message, int64_t msgid, bool is_reply, bool is_history, bool is_self_msg ) { char tags[128]; snprintf(tags, sizeof tags, "tomsgid_%" PRIi64 ",nick_%s%s%s", msgid, username, is_history ? ",notify_none,no_log" : is_self_msg ? ",notify_none,log1" : ALWAYS_PRIVATE || room->nmembers == 2 ? ",notify_private,log1" : ",notify_message,log1", is_reply ? ",tomsg_reply" : ""); weechat_printf_date_tags( room->buffer, timestamp / 1000000LL, tags, "%s\t%s", username, message ); } static struct t_gui_line_data* find_hdata_line_data_ptr(struct roomdata *room, int64_t msgid, bool must_be_reply) { struct t_hdata *buffer_h = weechat_hdata_get("buffer"); struct t_hdata *lines_h = weechat_hdata_get("lines"); struct t_hdata *line_h = weechat_hdata_get("line"); struct t_hdata *line_data_h = weechat_hdata_get("line_data"); struct t_gui_lines *lines_ptr = weechat_hdata_pointer(buffer_h, room->buffer, "lines"); if (!lines_ptr) { debugf("ERROR: Cannot get lines_ptr!"); return NULL; } struct t_gui_line *line_ptr = weechat_hdata_pointer(lines_h, lines_ptr, "last_line"); int lines_count = weechat_hdata_integer(lines_h, lines_ptr, "lines_count"); struct t_gui_line_data *line_data_ptr = NULL; while (lines_count --> 0) { line_data_ptr = weechat_hdata_pointer(line_h, line_ptr, "data"); const int tags_count = weechat_hdata_integer(line_data_h, line_data_ptr, "tags_count"); bool have_reply_tag = false; bool have_msgid_tag = false; for (int i = 0; i < tags_count; i++) { char key[32]; snprintf(key, sizeof key, "%d|tags_array", i); const char *tag = weechat_hdata_string(line_data_h, line_data_ptr, key); if (strcmp(tag, "tomsg_reply") == 0) { have_reply_tag = true; } else if (memcmp(tag, "tomsgid_", 8) == 0) { int64_t this_id = strtoll(tag + 8, NULL, 10); if (this_id == msgid) have_msgid_tag = true; } } if ((have_reply_tag || !must_be_reply) && have_msgid_tag) break; line_data_ptr = NULL; line_ptr = weechat_hdata_pointer(line_h, line_ptr, "prev_line"); } if (line_data_ptr) return line_data_ptr; else return NULL; } static char *collect_line_data_tags(struct t_gui_line_data *line_data_ptr) { struct t_hdata *line_data_h = weechat_hdata_get("line_data"); const int tags_count = weechat_hdata_integer(line_data_h, line_data_ptr, "tags_count"); size_t buffer_cap = 256, buffer_len = 0; char *buffer = malloc(buffer_cap); assert(buffer); for (int i = 0; i < tags_count; i++) { char key[32]; snprintf(key, sizeof key, "%d|tags_array", i); const char *tag = weechat_hdata_string(line_data_h, line_data_ptr, key); const size_t len = strlen(tag); // "+ 1" for the comma, "- 1" for the final null byte if (buffer_len + 1 + len >= buffer_cap - 1) { do buffer_cap *= 2; while (buffer_len + 1 + len >= buffer_cap - 1); buffer = realloc(buffer, buffer_cap); assert(buffer); } if (i > 0) buffer[buffer_len++] = ','; memcpy(buffer + buffer_len, tag, len); buffer_len += len; } buffer[buffer_len] = '\0'; return buffer; } static void edit_reply_message( struct roomdata *room, int64_t msgid, const char *new_message ) { struct t_hdata *line_data_h = weechat_hdata_get("line_data"); struct t_gui_line_data *line_data_ptr = find_hdata_line_data_ptr(room, msgid, true); if (line_data_ptr) { debugf("edit_reply_message: found line\n"); struct t_hashtable *hashtable = weechat_hashtable_new( 8, WEECHAT_HASHTABLE_STRING, WEECHAT_HASHTABLE_STRING, NULL, NULL); if (hashtable) { weechat_hashtable_set(hashtable, "message", new_message); weechat_hdata_update(line_data_h, line_data_ptr, hashtable); weechat_hashtable_free(hashtable); } } else { debugf("edit_reply_message: not found!\n"); } } static void edit_message_msgid(struct roomdata *room, int64_t pending_id, int64_t new_msgid) { debugf("edit_message_msgid: looking for %" PRIi64 ", replacing with %" PRIi64 "\n", pending_id, new_msgid); struct t_hdata *const line_data_h = weechat_hdata_get("line_data"); struct t_gui_line_data *const line_data_ptr = find_hdata_line_data_ptr(room, pending_id, false); if (!line_data_ptr) { debugf("edit_message_msgid: not found!\n"); return; } debugf("edit_message_msgid: found line\n"); char *const tags_str = collect_line_data_tags(line_data_ptr); const size_t tags_len = strlen(tags_str); size_t cursor = 0; char *endptr; size_t taglen = 0; while (cursor < tags_len) { endptr = strchr(tags_str + cursor, ','); taglen = endptr == NULL ? tags_len - cursor : (size_t)(endptr - (tags_str + cursor)); if (memcmp(tags_str + cursor, "tomsgid_", 8) == 0) { int64_t this_id = strtoll(tags_str + cursor + 8, NULL, 10); if (this_id == pending_id) break; } cursor += taglen + 1; } if (cursor >= tags_len) { debugf("edit_message_msgid: msgid not found in tags?\n"); return; } // Splice the pending tag out tags_str[cursor] = '\0'; char *tags_before = tags_str; char *tags_after = endptr; // Add the new tag at the end char *new_tags_str = NULL; asprintf(&new_tags_str, "%stomsgid_%" PRIi64 "%s", tags_before, new_msgid, tags_after); assert(new_tags_str); free(tags_str); // Persist the new tags array struct t_hashtable *hashtable = weechat_hashtable_new( 8, WEECHAT_HASHTABLE_STRING, WEECHAT_HASHTABLE_STRING, NULL, NULL); if (hashtable) { weechat_hashtable_set(hashtable, "tags_array", new_tags_str); weechat_hdata_update(line_data_h, line_data_ptr, hashtable); weechat_hashtable_free(hashtable); } free(new_tags_str); } static void room_update_attributes(struct roomdata *room){ bool private=ALWAYS_PRIVATE||room->nmembers==2; weechat_buffer_set(room->buffer,"localvar_set_type",private?"private":"channel"); weechat_buffer_set(room->buffer,"notify",private?"3":"2"); debugf("room_update_attributes: set private for room %s to %d\n",room->name,private); if(room->conn->username){ weechat_buffer_set(room->buffer,"localvar_set_nick",room->conn->username); } else { weechat_buffer_set(room->buffer,"localvar_del_nick",""); } } static void close_room(struct roomdata *room){ debugf("close_room(room=%p)\n",room); if(room->buffer)weechat_buffer_close(room->buffer); if(roomtable)weechat_hashtable_remove(roomtable,room->name); free(room->name); free(room); } struct room_and_msgid { struct roomdata *room; int64_t msgid; }; static void reply_get_message_net_callback(int fd, struct net_response res, void *payload) { (void)fd; struct room_and_msgid *data = (struct room_and_msgid*)payload; debugf("Got reply from get_message for msgid=%" PRIi64 "\n", res.msgid); const char *green = weechat_color("green"); size_t prefixlen = strlen(green) + 3 + strlen(res.username) + 2; char *buffer = malloc(prefixlen + strlen(res.message) + 1); sprintf(buffer, "%s> <%s> %s", weechat_color("green"), res.username, res.message); edit_reply_message(data->room, data->msgid, buffer); free(buffer); free(data); } struct sent_message_data { struct roomdata *room; int num; // must be <=2 i64 ids[2]; i64 replyid; // -1 if not a reply }; static void message_net_callback(int fd,struct net_response res,void *payload){ struct sent_message_data *sent_message_data = (struct sent_message_data*)payload; debugf("message_net_callback(fd=%d,res={.type=%d})\n", fd, res.type); if (res.type == NET_ERROR) { struct conndata *conn = weechat_hashtable_get(conntable, &fd); assert(conn); weechat_printf(conn->buffer, "tomsg: send threw error: %s", res.error); } else if (res.type == NET_NUMBER) { for (int i = 0; i < sent_message_data->num; i++) { edit_message_msgid(sent_message_data->room, -sent_message_data->ids[i], res.number); } if (sent_message_data->replyid != -1) { struct room_and_msgid *get_payload = malloc(sizeof(struct room_and_msgid)); assert(get_payload); get_payload->room = sent_message_data->room; get_payload->msgid = res.number; net_sendf(fd, reply_get_message_net_callback, get_payload, "get_message %" PRIi64, sent_message_data->replyid); } } else { debugf("message_net_callback: res.type=%d\n", res.type); } free(sent_message_data); } 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'); bool skipfirst=input[0]=='/'&&input[1]=='/'; bool free_tosend=false; if(p!=NULL){ debugf("room_input_cb: input contained newline <%s>\n",input); tosend=strdup(input+skipfirst); *strchr(tosend,'\n')='\0'; free_tosend=true; } else { tosend=input+skipfirst; } struct sent_message_data *payload=malloc(sizeof(struct sent_message_data)); assert(payload); payload->room=room; payload->num=1; payload->ids[0]=room->next_pending_id++; payload->replyid=-1; net_sendf(conn->fd,message_net_callback,payload,"send %s -1 %s",room->name,tosend); display_message(room,gettimestamp(),conn->username,tosend,-payload->ids[0],false,false,true); if(free_tosend){ 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 create_room_buffer(struct roomdata *room){ if(room->buffer!=NULL){ weechat_buffer_close(room->buffer); } weechat_hashtable_set(roomtable,room->name,room); room->buffer= weechat_buffer_new(room->name, room_input_cb,room,NULL, room_close_cb,room,NULL); weechat_buffer_set(room->buffer,"nicklist","1"); weechat_buffer_set(room->buffer,"nicklist_case_sensitive","1"); weechat_buffer_set(room->buffer,"nicklist_display_groups","0"); room->buffer_nickgroup=NULL; room_update_attributes(room); } static void history_net_callback(int fd,struct net_response res,void *payload){ struct roomdata *room=(struct roomdata*)payload; assert(room); debugf("history_net_callback(fd=%d,res={.type=%d})\n",fd,res.type); } struct room_and_name{ struct roomdata *room; char *name; }; static void isonline_net_callback(int fd,struct net_response res,void *payload){ (void)fd; debugf("isonline_net_callback(fd=%d,res={.type=%d,.number=%" PRIi64 "})\n", fd,res.type,res.number); const char *color= res.type!=NET_NUMBER ? "red" : res.number >= 1 ? NICK_COLOR: NICK_AWAY_COLOR; struct room_and_name *rn=(struct room_and_name*)payload; struct roomdata *room=rn->room; char *name=rn->name; free(rn); struct t_gui_nick *nickp=weechat_nicklist_search_nick(room->buffer,room->buffer_nickgroup,name); free(name); if(nickp==NULL)return; weechat_nicklist_nick_set(room->buffer,nickp,"color",color); } static void members_net_callback(int fd,struct net_response res,void *payload){ struct roomdata *room=(struct roomdata*)payload; assert(room); debugf("members_net_callback(fd=%d,res={.type=%d})\n",fd,res.type); if(res.type!=NET_LIST){ debugf("members_net_callback: res.type=%d\n",res.type); return; } if(room->buffer_nickgroup!=NULL){ weechat_nicklist_remove_all(room->buffer); } room->buffer_nickgroup= weechat_nicklist_add_group(room->buffer,NULL,"members","weechat.color.nicklist_group",1); for(int i=0;ibuffer,room->buffer_nickgroup, res.items[i],NICK_COLOR, "",NICK_COLOR, 1); struct room_and_name *payload=malloc(sizeof(struct room_and_name)); assert(payload); payload->room=room; payload->name=strdup(res.items[i]); net_sendf(fd,isonline_net_callback,payload,"is_online %s",res.items[i]); } room->nmembers=res.nitems; room_update_attributes(room); } static void push_net_callback(int fd,struct net_response res,void *payload){ (void)payload; 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||res.type==NET_HISTORY||res.type==NET_JOIN|| res.type==NET_INVITE||res.type==NET_LEAVE){ i64 roomi; for(roomi=0;roominrooms;roomi++){ if(strcmp(conn->rooms[roomi]->name,res.room)==0){ break; } } if(roomi==conn->nrooms){ debugf("push_net_callback: message to unknown room '%s'\n",res.room); return; } struct roomdata *room=conn->rooms[roomi]; if(room->buffer==NULL&&res.type!=NET_LEAVE){ create_room_buffer(room); } if(res.type==NET_MESSAGE||res.type==NET_HISTORY){ if(res.replyid!=-1){ debugf("Found reply msgid=%" PRIi64 " replyid=%" PRIi64 "\n",res.msgid,res.replyid); char str[128]; snprintf(str,sizeof str,"%s> ...",weechat_color("green")); display_message(room,res.timestamp,res.username,str,res.msgid,true,res.type==NET_HISTORY,false); display_message(room,res.timestamp,"",res.message,res.msgid,false,res.type==NET_HISTORY,false); struct room_and_msgid *payload=malloc(sizeof(struct room_and_msgid)); assert(payload); payload->room=room; payload->msgid=res.msgid; net_sendf(fd,reply_get_message_net_callback,payload,"get_message %" PRIi64,res.replyid); } else { display_message(room,res.timestamp,res.username,res.message,res.msgid,false,res.type==NET_HISTORY,false); } } else if(res.type==NET_JOIN){ weechat_printf(room->buffer,"%sUser %s joined this room",netpfx,res.username); if(room->buffer_nickgroup){ weechat_nicklist_add_nick( room->buffer,room->buffer_nickgroup, res.username,NICK_COLOR, "",NICK_COLOR, 1); } room->nmembers++; room_update_attributes(room); } else if(res.type==NET_INVITE){ if(conn->username!=NULL&&strcmp(res.username,conn->username)==0){ weechat_printf(room->buffer,"%sYou created this room in another session",netpfx); } else { weechat_printf(room->buffer,"%sYou were invited into this room",netpfx); } net_sendf(fd,history_net_callback,room,"history %s 10",room->name); } else if(res.type==NET_LEAVE){ if(conn->username!=NULL&&strcmp(res.username,conn->username)==0){ close_room(room); if(roominrooms-1)conn->rooms[roomi]=conn->rooms[conn->nrooms-1]; conn->nrooms--; } else { weechat_printf(room->buffer,"%sUser %s left the room",netpfx,res.username); net_sendf(fd,members_net_callback,room,"list_members %s",room->name); } } else { assert(false); } } else if(res.type==NET_PONG){ // ok } else if(res.type==NET_ONLINE){ debugf(" NET_ONLINE with username='%s' num='%" PRIi64 "'\n",res.online.username,res.online.num); const char *color=res.online.num>0 ? NICK_COLOR : NICK_AWAY_COLOR; for(i64 i=0;inrooms;i++){ if(!conn->rooms[i]->buffer)continue; struct t_gui_nick *nickp=weechat_nicklist_search_nick( conn->rooms[i]->buffer,conn->rooms[i]->buffer_nickgroup,res.online.username); if(nickp!=NULL){ weechat_nicklist_nick_set(conn->rooms[i]->buffer,nickp,"color",color); } } } else { 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){ debugf("history_ -> "); push_net_callback(fd,res,payload); } static void roomlist_net_callback(int fd,struct net_response res,void *payload){ (void)payload; 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)); assert(room); room->name=strdup(res.items[i]); room->conn=conn; room->buffer=NULL; room->buffer_nickgroup=NULL; room->nmembers=0; room->next_pending_id=1; create_room_buffer(room); net_sendf(fd,history_net_callback,room,"history %s 10",room->name); net_sendf(fd,members_net_callback,room,"list_members %s",room->name); } } else { 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; 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 { debugf("login_net_callback: res.type=%d\n",res.type); } } static void pong_net_callback(int fd,struct net_response res,void *payload){ (void)payload; debugf("pong_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_PONG){ weechat_printf(conn->buffer,"pong"); } else { debugf("pong_net_callback: res.type=%d\n",res.type); } } static void conn_destroy(struct conndata *conn){ 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; 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; while(true){ nr=recv(fd,conn->linebuf+conn->linebuf_len,conn->linebuf_sz-conn->linebuf_len,0); if(nr<=0){ if(nr<0){ if(errno==EINTR)continue; if(errno==EAGAIN)return WEECHAT_RC_OK; // next time around maybe? debugf("fd_hook_callback: recv() < 0: %s\n",strerror(errno)); } else { debugf("fd_hook_callback: recv() == 0 (EOF)\n"); } weechat_printf(NULL,"tomsg: Connection dropped"); weechat_buffer_close(conn->buffer); return WEECHAT_RC_OK; } break; } 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; debugf("conn_input_cb(conn=%p,buffer=%p,input=\"%s\")\n",conn,buffer,input); if(!conn->negotiation_complete){ weechat_printf(conn->buffer,"Server protocol version not yet negotiated, please wait..."); return WEECHAT_RC_OK; } 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 if(strcmp(cmd,"ping")==0){ net_sendf(conn->fd,pong_net_callback,NULL,"ping"); } else { weechat_printf(conn->buffer,"Unknown command '%s'",cmd); } return WEECHAT_RC_OK; } static bool password_hide_modifier__found; static void password_hide_modifier_hmapper(void *data,struct t_hashtable *ht,const void *key,const void *value){ (void)ht; (void)key; const struct conndata *conn=(const struct conndata*)value; if(conn->buffer==(struct t_gui_buffer*)data){ password_hide_modifier__found=true; } } static char* password_hide_modifier(const void *_p,void *_d,const char *modifier,const char *bufstr,const char *str){ (void)_p; (void)_d; (void)modifier; struct t_gui_buffer *buffer=(struct t_gui_buffer*)strtoll(bufstr,NULL,16); if(buffer==NULL){ debugf("password_hide_modifier: bufstr is NULL: '%s'\n",bufstr); return NULL; } password_hide_modifier__found=false; weechat_hashtable_map(conntable,password_hide_modifier_hmapper,buffer); if(!password_hide_modifier__found)return NULL; bool cursor_in_login=false; for(int i=0,j=0;i<6;i++,j++){ if(str[j]==0x19){ j+=3; cursor_in_login=true; } if(str[j]!="login "[i]){ return NULL; } } const char *p=strchr(str+6+3*cursor_in_login,' '); if(p==NULL)return NULL; char *newstr=malloc(p-str+8); assert(newstr); memcpy(newstr,str,p-str); if(memchr(newstr,0x19,p-str)!=NULL){ memcpy(newstr+(p-str)," ***",5); // count includes '\0' } else { memcpy(newstr+(p-str)," ***\x19" "b#",8); // count includes '\0' } return newstr; } 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; if(conn){ debugf("conn_close_cb(conn=%p,buffer=%p) fd=%d\n",conn,buffer,conn->fd); conn_destroy(conn); } return WEECHAT_RC_OK; } static void version_net_callback(int fd,struct net_response res,void *payload){ (void)payload; debugf("version_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){ conn->negotiation_complete=true; weechat_printf(conn->buffer,"Version negotiation complete."); } else { conn_destroy(conn); weechat_printf(NULL,"tomsg: Server has incompatible protocol version (we want %d)!",PROTOCOL_VERSION); } } 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->negotiation_complete=false; 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_buffer_set(conn->buffer,"localvar_set_type","server"); weechat_printf(conn->buffer,"Connected!"); weechat_hashtable_set(conntable,&fd,conn); net_sendf(fd,version_net_callback,NULL,"version %d", PROTOCOL_VERSION); 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 **argv_eol){ (void)_p; (void)_d; 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; } 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 if(strcmp(argv[1],"reply")==0){ if(argc<4){ weechat_printf(buffer,"%stomsg: Invalid number of arguments to /tomsg reply",errpfx); return WEECHAT_RC_ERROR; } char *endp; int64_t msgid=strtol(argv[2],&endp,10); if(*argv[2]=='\0'||*endp!='\0'){ weechat_printf(NULL,"%stomsg: Invalid msgid argument to /tomsg reply",errpfx); return WEECHAT_RC_ERROR; } const char *buffername=weechat_buffer_get_string(buffer,"full_name"); debugf("tomsg reply: buffername=<%s>\n",buffername); const char *prefix="tomsg."; size_t prefixlen=strlen(prefix); if(strlen(buffername)>prefixlen&& memcmp(buffername,prefix,prefixlen)==0){ struct roomdata *room= weechat_hashtable_get(roomtable,buffername+prefixlen); if(!room){ weechat_printf(NULL,"%stomsg: Cannot locate internal room data!",errpfx); return WEECHAT_RC_ERROR; } const char *message_body=argv_eol[3]; struct sent_message_data *msg_payload=malloc(sizeof(struct sent_message_data)); assert(msg_payload); msg_payload->room=room; msg_payload->num=2; msg_payload->ids[0]=room->next_pending_id++; msg_payload->ids[1]=room->next_pending_id++; msg_payload->replyid=msgid; int fd=room->conn->fd; net_sendf(fd,message_net_callback,msg_payload,"send %s %" PRIi64 " %s",room->name,msgid,message_body); char str[128]; snprintf(str,sizeof str,"%s> ...",weechat_color("green")); i64 timestamp=gettimestamp(); display_message(room,timestamp,room->conn->username,str,-msg_payload->ids[0],true,false,true); display_message(room,timestamp,"",message_body,-msg_payload->ids[1],false,false,true); // TODO: do get_payload in message_net_callback // struct room_and_msgid *get_payload=malloc(sizeof(struct room_and_msgid)); // assert(get_payload); // get_payload->room=room; // get_payload->msgid=msgid; // net_sendf(fd,reply_get_message_net_callback,get_payload,"get_message %" PRIi64,msgid); } else { weechat_printf(NULL,"%stomsg: /tomsg reply on a non-tomsg buffer",errpfx); return WEECHAT_RC_ERROR; } } else { weechat_printf(buffer,"%stomsg: Unknown command \"%s\" to /tomsg",errpfx,argv[1]); return WEECHAT_RC_ERROR; } return WEECHAT_RC_OK; } static void bind_key_easy(const char *context, const char *key, const char *value) { struct t_hashtable *ht = weechat_hashtable_new( 1, WEECHAT_HASHTABLE_STRING, WEECHAT_HASHTABLE_STRING, NULL, NULL); weechat_hashtable_set(ht, key, value); weechat_key_bind(context, ht); weechat_hashtable_free(ht); } static int cursor_reply_signal_cb( const void *pointer_, void *data_, const char *signal, struct t_hashtable *hashtable ) { (void)pointer_; (void)data_; (void)signal; const char *tags = weechat_hashtable_get(hashtable, "_chat_line_tags"); weechat_command(NULL, "/cursor stop"); int64_t msgid = -1; while (*tags) { const char *ptr = strchr(tags, ','); if (ptr - tags > (ptrdiff_t)strlen("tomsgid_")) { char *endp; msgid = strtoll(tags + 8, &endp, 10); if ((ptr == NULL && *endp == '\0') || endp == ptr) { break; } msgid = -1; } } if (msgid == -1) return WEECHAT_RC_OK; const char *buffer_name = weechat_hashtable_get(hashtable, "_buffer_full_name"); struct t_gui_buffer *bufptr = weechat_buffer_search("==", buffer_name); const char *current_input = weechat_buffer_get_string(bufptr, "input"); int input_pos = weechat_buffer_get_integer(bufptr, "input_pos"); size_t new_input_capacity = 64 + strlen(current_input) + 1; char *new_input = malloc(new_input_capacity); assert(new_input); int total_length = snprintf( new_input, new_input_capacity, "/tomsg reply %" PRIi64 " %s", msgid, current_input); int prefix_length = total_length - strlen(current_input); weechat_buffer_set(bufptr, "input", new_input); free(new_input); char posbuf[64]; snprintf(posbuf, sizeof posbuf, "%d", prefix_length + input_pos); weechat_buffer_set(bufptr, "input_pos", posbuf); return WEECHAT_RC_OK; } int weechat_plugin_init(struct t_weechat_plugin *plugin,int argc,char **argv){ (void)argc; (void)argv; weechat_plugin = plugin; debug_init(); errpfx=weechat_prefix("error"); netpfx=weechat_prefix("network"); weechat_hook_command( "tomsg", "Execute commands related to tomsg.", "connect [port] | " "reply ", " connect: Connect to a tomsg server\n" " reply: Reply to a message in a tomsg buffer. Tip: use middleclick-R.", NULL, cmd_tomsg_cb,NULL,NULL); weechat_hook_modifier("input_text_display_with_cursor",password_hide_modifier,NULL,NULL); weechat_hook_hsignal("tomsg_cursor_reply", cursor_reply_signal_cb, NULL, NULL); bind_key_easy("cursor","@chat(tomsg.*):r","hsignal:tomsg_cursor_reply"); 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); roomtable=weechat_hashtable_new( 16,WEECHAT_HASHTABLE_STRING,WEECHAT_HASHTABLE_POINTER,NULL,NULL); return WEECHAT_RC_OK; } int weechat_plugin_end(struct t_weechat_plugin *plugin){ (void)plugin; weechat_hashtable_free(conntable); weechat_hashtable_free(roomtable); conntable=NULL; roomtable=NULL; debug_deinit(); return WEECHAT_RC_OK; }