#include #include #include #include #include #include #include #include #include #include "delta-connection.h" #include "libdelta.h" #define IMEX_RECEIVED_MESSAGE "Setup message received. To apply it, reply with:\nIMEX: %d nnnn-nnnn-nnnn-nnnn-nnnn-nnnn-nnnn-nnnn-nnnn\nNo whitespace in the setup-code!" void delta_recv_im(DeltaConnectionData *conn, dc_msg_t *msg); void _transpose_config(dc_context_t *mailbox, PurpleAccount *acct) { const char *addr = acct->username; const char *display = purple_account_get_string(acct, PLUGIN_ACCOUNT_OPT_DISPLAY_NAME, NULL); const char *imap_host = purple_account_get_string(acct, PLUGIN_ACCOUNT_OPT_IMAP_SERVER_HOST, NULL); const char *imap_user = purple_account_get_string(acct, PLUGIN_ACCOUNT_OPT_IMAP_USER, NULL); const char *imap_pass = purple_account_get_password(acct); const char *imap_port = purple_account_get_string(acct, PLUGIN_ACCOUNT_OPT_IMAP_SERVER_PORT, NULL); const char *smtp_host = purple_account_get_string(acct, PLUGIN_ACCOUNT_OPT_SMTP_SERVER_HOST, NULL); const char *smtp_user = purple_account_get_string(acct, PLUGIN_ACCOUNT_OPT_SMTP_USER, NULL); const char *smtp_pass = purple_account_get_string(acct, PLUGIN_ACCOUNT_OPT_SMTP_PASS, NULL); const char *smtp_port = purple_account_get_string(acct, PLUGIN_ACCOUNT_OPT_SMTP_SERVER_PORT, NULL); gboolean bcc_self = purple_account_get_bool(acct, PLUGIN_ACCOUNT_OPT_BCC_SELF, FALSE); dc_set_config(mailbox, PLUGIN_ACCOUNT_OPT_ADDR, addr); dc_set_config(mailbox, PLUGIN_ACCOUNT_OPT_DISPLAY_NAME, display); dc_set_config(mailbox, PLUGIN_ACCOUNT_OPT_IMAP_SERVER_HOST, imap_host); dc_set_config(mailbox, PLUGIN_ACCOUNT_OPT_IMAP_USER, imap_user); dc_set_config(mailbox, PLUGIN_ACCOUNT_OPT_IMAP_PASS, imap_pass); dc_set_config(mailbox, PLUGIN_ACCOUNT_OPT_IMAP_SERVER_PORT, imap_port); dc_set_config(mailbox, PLUGIN_ACCOUNT_OPT_SMTP_SERVER_HOST, smtp_host); dc_set_config(mailbox, PLUGIN_ACCOUNT_OPT_SMTP_USER, smtp_user); dc_set_config(mailbox, PLUGIN_ACCOUNT_OPT_SMTP_PASS, smtp_pass); dc_set_config(mailbox, PLUGIN_ACCOUNT_OPT_SMTP_SERVER_PORT, smtp_port); if (bcc_self) { dc_set_config(mailbox, PLUGIN_ACCOUNT_OPT_BCC_SELF, "1"); } else { dc_set_config(mailbox, PLUGIN_ACCOUNT_OPT_BCC_SELF, "0"); }; } typedef struct { DeltaConnectionData *conn; // Used by delta_process_incoming_message uint32_t msg_id; gboolean msg_changed; // Used by delta_process_connection_state // int connection_state; } ProcessRequest; gboolean delta_process_incoming_message(void *data) { ProcessRequest *pr = (ProcessRequest *)data; g_assert(pr != NULL); g_assert(pr->conn != NULL); dc_msg_t *msg = dc_get_msg(pr->conn->mailbox, pr->msg_id); delta_recv_im(pr->conn, msg); dc_msg_unref(msg); g_free(data); return FALSE; } /* gboolean delta_process_connection_state(void *data) { ProcessRequest *pr = (ProcessRequest *)data; g_assert(pr != NULL); g_assert(pr->conn != NULL); purple_connection_update_progress( pr->conn->pc, "Connecting...", pr->connection_state, MAX_DELTA_CONFIGURE ); if (pr->connection_state == MAX_DELTA_CONFIGURE) { } g_free(data); return FALSE; } */ gboolean delta_process_fresh_messages(void *data) { ProcessRequest *pr = (ProcessRequest *)data; g_assert(pr != NULL); g_assert(pr->conn != NULL); dc_context_t *mailbox = pr->conn->mailbox; g_assert(mailbox != NULL); // Spot any messages received while offline dc_array_t *fresh_msgs = dc_get_fresh_msgs(mailbox); size_t fresh_count = dc_array_get_cnt(fresh_msgs); purple_debug_info(PLUGIN_ID, "fresh_count: %zu\n", fresh_count); for(size_t i = 0; i < fresh_count; i++) { uint32_t msg_id = dc_array_get_id(fresh_msgs, i); dc_msg_t *msg = dc_get_msg(mailbox, msg_id); delta_recv_im(pr->conn, msg); } dc_array_unref(fresh_msgs); return FALSE; } ProcessRequest * delta_build_process_request(DeltaConnectionData *conn) { g_assert(conn != NULL); ProcessRequest *pr = g_malloc(sizeof(ProcessRequest)); g_assert(pr != NULL); pr->conn = conn; return pr; } // Do not call any libpurple or delta functions in here, as it is not // thread-safe and events may be dispatched from any delta thread. Use // purple_timeout_add(0, callback, data) to run on the main thread instead void * delta_event_handler(void *context) { DeltaConnectionData *conn = (DeltaConnectionData *)context; g_assert(conn != NULL); dc_context_t *mailbox = conn->mailbox; dc_event_emitter_t* emitter = dc_get_event_emitter(mailbox); dc_event_t* event; // FIXME: do we still need runthreads? while (conn->runthreads && (event = dc_get_next_event(emitter)) != NULL) { ProcessRequest *pr = NULL; int event_id = dc_event_get_id(event); purple_debug_info(PLUGIN_ID, "Event %d received from Delta.\n", event_id); switch (event_id) { case DC_EVENT_SMTP_MESSAGE_SENT: case DC_EVENT_IMAP_CONNECTED: case DC_EVENT_SMTP_CONNECTED: case DC_EVENT_IMAP_MESSAGE_DELETED: case DC_EVENT_IMAP_MESSAGE_MOVED: case DC_EVENT_INFO: { char *info = dc_event_get_data2_str(event); purple_debug_info(PLUGIN_ID, "INFO from Delta: %s\n", info); dc_str_unref(info); break; } case DC_EVENT_WARNING: { char *warn = dc_event_get_data2_str(event); purple_debug_info(PLUGIN_ID, "WARNING from Delta: %s\n", warn); dc_str_unref(warn); break; } case DC_EVENT_ERROR: { int errcode = dc_event_get_data1_int(event); char *err = dc_event_get_data2_str(event); purple_debug_info(PLUGIN_ID, "ERROR from Delta: %d: %s\n", errcode, err); dc_str_unref(err); break; } case DC_EVENT_MSGS_CHANGED: { // This event may be issued for a single message, in which case the // message ID is in data2 and we should treat it as an incoming msg // FIXME: this leads to duplicate messages when it's an outgoing // message we just sent uint32_t msg_id = dc_event_get_data2_int(event); pr = delta_build_process_request(conn); if (msg_id) { // FIXME: for now, only display IMEX setup messages to avoid duplicates pr->msg_id = msg_id; pr->msg_changed = TRUE; purple_timeout_add(0, delta_process_incoming_message, pr); } else { purple_timeout_add(0, delta_process_fresh_messages, pr); } break; } case DC_EVENT_INCOMING_MSG: // data1 is chat_id, which we don't seem to need yet. // TODO: It may be needed for group chats pr = delta_build_process_request(conn); pr->msg_id = (uint32_t)dc_event_get_data2_int(event); purple_timeout_add(0, delta_process_incoming_message, pr); break; // Things left to do case DC_EVENT_CHAT_EPHEMERAL_TIMER_MODIFIED: case DC_EVENT_NEW_BLOB_FILE: case DC_EVENT_DELETED_BLOB_FILE: case DC_EVENT_MSG_DELIVERED: case DC_EVENT_MSG_READ: case DC_EVENT_MSG_FAILED: case DC_EVENT_CHAT_MODIFIED: case DC_EVENT_CONTACTS_CHANGED: case DC_EVENT_ERROR_SELF_NOT_IN_GROUP: case DC_EVENT_IMEX_FILE_WRITTEN: case DC_EVENT_IMEX_PROGRESS: case DC_EVENT_LOCATION_CHANGED: case DC_EVENT_MSGS_NOTICED: case DC_EVENT_SECUREJOIN_INVITER_PROGRESS: case DC_EVENT_SECUREJOIN_JOINER_PROGRESS: purple_debug_info(PLUGIN_ID, "Event %d is TODO\n", event_id); break; case DC_EVENT_CONFIGURE_PROGRESS: //pr = delta_build_process_request(conn); //pr->connection_state = dc_event_get_data1_int(event); //purple_timeout_add(0, delta_process_connection_state, pr); purple_debug_info(PLUGIN_ID, "Configure progress: %d\n", dc_event_get_data1_int(event)); break; default: purple_debug_info(PLUGIN_ID, "Unknown Delta event: %d\n", event_id); } dc_event_unref(event); } dc_event_emitter_unref(emitter); return NULL; } void delta_connection_new(PurpleConnection *pc) { DeltaConnectionData *conn = NULL; g_assert(purple_connection_get_protocol_data(pc) == NULL); conn = g_new0(DeltaConnectionData, 1); conn->pc = pc; purple_connection_set_protocol_data(pc, conn); } void delta_connection_free(PurpleConnection *pc) { DeltaConnectionData *conn = purple_connection_get_protocol_data(pc); g_assert(conn != NULL); conn->runthreads = 0; if (conn->mailbox != NULL) { dc_stop_ongoing_process(conn->mailbox); dc_stop_io(conn->mailbox); // TODO: correctly handle join failing purple_debug_info(PLUGIN_ID, "Joining event thread\n"); if (pthread_join(conn->event_thread, NULL) != 0) { purple_debug_info(PLUGIN_ID, "joining event thread failed\n"); } dc_context_unref(conn->mailbox); } purple_connection_set_protocol_data(pc, NULL); // TODO: free resources as they are added to DeltaConnectionData conn->pc = NULL; conn->mailbox = NULL; g_free(conn); } void delta_connection_start_login(PurpleConnection *pc) { char dbname[1024]; PurpleAccount *acct = pc->account; DeltaConnectionData *conn = purple_connection_get_protocol_data(pc); dc_context_t *mailbox = NULL; g_snprintf( dbname, 1024, "%s%sdelta_db-%s", purple_user_dir(), G_DIR_SEPARATOR_S, acct->username ); mailbox = dc_context_new(PLUGIN_ID, dbname, NULL); conn->mailbox = mailbox; _transpose_config(mailbox, acct); conn->runthreads = 1; pthread_create(&conn->event_thread, NULL, delta_event_handler, conn); dc_configure(mailbox); dc_start_io(mailbox); dc_maybe_network(mailbox); purple_connection_set_state(pc, PURPLE_CONNECTED); return; } gboolean delta_try_process_imex(dc_context_t *mailbox, char *text) { if (!g_str_has_prefix(text, "IMEX: ")) { return FALSE; } gchar **parts = g_strsplit(text, " ", 3); if (g_strv_length(parts) != 3) { g_strfreev(parts); return FALSE; } int msg_id = atoi(parts[1]); gboolean success = dc_continue_key_transfer(mailbox, msg_id, parts[2]); g_strfreev(parts); return success; } int delta_send_im(PurpleConnection *pc, const char *who, const char *message, PurpleMessageFlags flags) { UNUSED(flags); DeltaConnectionData *conn = (DeltaConnectionData *)purple_connection_get_protocol_data(pc); g_assert(conn != NULL); dc_context_t *mailbox = conn->mailbox; g_assert(mailbox != NULL); uint32_t contact_id = dc_create_contact(mailbox, NULL, who); uint32_t chat_id = dc_create_chat_by_contact_id(mailbox, contact_id); GData *attrs; const char *msg_ptr, *start, *end; msg_ptr = message; // Send each image included in the message. while (purple_markup_find_tag("img", msg_ptr, &start, &end, &attrs) == TRUE) { char *id_str = g_datalist_id_get_data(&attrs, g_quark_from_string("id")); purple_debug_info(PLUGIN_ID, "In a loop, got %s\n", id_str); msg_ptr = end + 1; if (id_str == NULL || strlen(id_str) == 0) { g_datalist_clear(&attrs); continue; } int id = atoi(id_str); g_datalist_clear(&attrs); if (id <= 0) { continue; } GError *err = NULL; char *tempdir = g_dir_make_tmp(NULL, &err); if (err != NULL) { purple_debug_info(PLUGIN_ID, "Couldn't get a temporary dir for image %d: %s", id, err->message); g_free(err); continue; } PurpleStoredImage *img = purple_imgstore_find_by_id(id); const char *filename = purple_imgstore_get_filename(img); const char *extension = purple_imgstore_get_extension(img); gconstpointer data = purple_imgstore_get_data(img); char *path = g_strdup_printf("%s/%s", tempdir, filename); g_file_set_contents(path, data, purple_imgstore_get_size(img), &err); if (err != NULL) { purple_debug_info(PLUGIN_ID, "failed to write %s to temporary file: %s\n", filename, err->message); g_free(err); goto next; } purple_debug_info(PLUGIN_ID, "Sending image %s from imgstore: %d\n", filename, id); dc_msg_t *img_msg = dc_msg_new(mailbox, DC_MSG_IMAGE); dc_msg_set_file(img_msg, path, extension); dc_send_msg(mailbox, chat_id, img_msg); dc_msg_unref(img_msg); next: remove(path); remove(tempdir); g_free(path); } // Send any text left char *stripped_message = purple_markup_strip_html(message); g_assert(stripped_message != NULL); if (strlen(stripped_message) > 0) { if (!delta_try_process_imex(mailbox, stripped_message)) { dc_send_text_msg(mailbox, chat_id, stripped_message); } } g_free(stripped_message); return 0; // success; don't echo the message to the chat window since we display it anyway } void delta_recv_im(DeltaConnectionData *conn, dc_msg_t *msg) { dc_context_t *mailbox = conn->mailbox; g_assert(mailbox != NULL); PurpleConnection *pc = conn->pc; g_assert(pc != NULL); uint32_t msg_id = dc_msg_get_id(msg); int viewtype = dc_msg_get_viewtype(msg); time_t timestamp = dc_msg_get_timestamp(msg); char *text = dc_msg_get_text(msg); uint32_t chat_id = dc_msg_get_chat_id(msg); dc_contact_t *from = dc_get_contact(mailbox, dc_msg_get_from_id(msg)); dc_chat_t *chat = dc_get_chat(mailbox, chat_id); dc_array_t *contacts = dc_get_chat_contacts(mailbox, chat_id); int num_contacts = dc_array_get_cnt(contacts); if (chat == NULL) { purple_debug_info(PLUGIN_ID, "Receiving IM: unknown chat: %d\n", chat_id); goto out; } if (dc_chat_get_type(chat) == DC_CHAT_TYPE_GROUP) { purple_debug_info(PLUGIN_ID, "Receiving IM: group chat with ID %d! Not yet supported\n", chat_id); goto out; } if (num_contacts != 1) { purple_debug_info(PLUGIN_ID, "Receiving IM: 1-1 chat %d with %d contacts instead of 1!\n", chat_id, num_contacts); goto out; } // FIXME: using dc_array_get_contact_id fails here, complaining that it's not an array of locations dc_contact_t *contact = dc_get_contact(mailbox, dc_array_get_id(contacts, 0)); char *who = NULL; // In the current architecture, delta_send_im and delta_recv_im must agree // on the value for 'who'. Using the email address is an easy cheat for this // but gets shaky in the long term. if (contact != NULL) { who = dc_contact_get_addr(contact); } else { who = dc_chat_get_name(chat); } int flags = 0; int state = dc_msg_get_state(msg); if (state == DC_STATE_IN_FRESH || state == DC_STATE_IN_NOTICED || state == DC_STATE_IN_SEEN) { flags |= PURPLE_MESSAGE_RECV; } else { flags |= PURPLE_MESSAGE_SEND; } // FIXME: as a massive hack, convert IMEX setup messages into a text message // prompting the user how to trigger the IMEX filter in outgoing messages. if (dc_msg_is_setupmessage(msg)) { purple_debug_info(PLUGIN_ID, "Receiving IMEX: ID=%d\n", msg_id); viewtype = DC_MSG_TEXT; dc_str_unref(text); text = g_strndup("", 1024); g_assert(text != NULL); g_snprintf(text, 1024, IMEX_RECEIVED_MESSAGE, msg_id); } switch(viewtype) { case DC_MSG_GIF: case DC_MSG_IMAGE: case DC_MSG_STICKER: flags = flags | PURPLE_MESSAGE_IMAGES; break; case DC_MSG_TEXT: flags = flags | PURPLE_MESSAGE_RAW; break; case DC_MSG_VIDEO: // Pidgin only supports these as files for download case DC_MSG_FILE: case DC_MSG_AUDIO: // Sound to play case DC_MSG_VOICE: break; default: purple_debug_info(PLUGIN_ID, "Message %d: unknown message type: %d\n", msg_id, viewtype); } int image_id = 0; if ((flags & PURPLE_MESSAGE_IMAGES) > 0) { char *filename = dc_msg_get_file(msg); gchar *data; gsize length; GError *err = NULL; g_file_get_contents(filename, &data, &length, &err); if (err != NULL) { purple_debug_info(PLUGIN_ID, "Failed to read image %s: %s\n", filename, err->message); g_error_free(err); goto out; } image_id = purple_imgstore_add_with_id(data, length, filename); text = g_strdup_printf("
%s", image_id, text); } char *name = dc_contact_get_display_name(from); int msglen = strlen(name) + 3 + strlen(text); char *msgtext = malloc(msglen); g_snprintf(msgtext, msglen, "%s: %s", name, text); serv_got_im(pc, who, msgtext, flags, timestamp); if (image_id > 0) { purple_imgstore_unref_by_id(image_id); } dc_markseen_msgs(mailbox, &msg_id, 1); g_free(msgtext); dc_str_unref(who); dc_str_unref(name); dc_contact_unref(contact); out: dc_str_unref(text); dc_chat_unref(chat); dc_array_unref(contacts); }