char *make_full_path (char *s) { if (*s != '/') { char *t = s; tasprintf (&s, "%s/%s", get_home_directory (), s); tfree_str (t); } return s; }
static void one_string_read_end (void) { printf ("\n"); fflush (stdout); read_one_string = 0; tfree_str (one_string_prompt); one_string_prompt = NULL; reactivate_readline (); one_string_results[one_string_num] = tstrdup (one_string); ++one_string_num; if (one_string_num < one_string_total_args) { do_get_string (TLS); } else { one_string_cb (TLS, (void *)one_string_results, string_cb_arg); int i; for (i = 0; i < one_string_total_args; i++) { tfree_str (one_string_results[i]); } } }
void running_for_first_time (void) { check_type_sizes (); if (config_filename) { return; // Do not create custom config file } tasprintf (&config_filename, "%s/%s/%s", get_home_directory (), CONFIG_DIRECTORY, CONFIG_FILE); config_filename = make_full_path (config_filename); int config_file_fd; char *config_directory = get_config_directory (); //char *downloads_directory = get_downloads_directory (); if (!mkdir (config_directory, CONFIG_DIRECTORY_MODE)) { if (!disable_output) { printf ("[%s] created\n", config_directory); } } tfree_str (config_directory); config_directory = NULL; // see if config file is there if (access (config_filename, R_OK) != 0) { // config file missing, so touch it config_file_fd = open (config_filename, O_CREAT | O_RDWR, 0600); if (config_file_fd == -1) { perror ("open[config_file]"); exit (EXIT_FAILURE); } if (write (config_file_fd, DEFAULT_CONFIG_CONTENTS, strlen (DEFAULT_CONFIG_CONTENTS)) <= 0) { perror ("write[config_file]"); exit (EXIT_FAILURE); } close (config_file_fd); /*int auth_file_fd = open (get_auth_key_filename (), O_CREAT | O_RDWR, 0600); int x = -1; assert (write (auth_file_fd, &x, 4) == 4); close (auth_file_fd); printf ("[%s] created\n", config_filename);*/ /* create downloads directory */ /*if (mkdir (downloads_directory, 0755) !=0) { perror ("creating download directory"); exit (EXIT_FAILURE); }*/ } }
void running_for_first_time (void) { check_type_sizes (); if (!str_empty (config_filename)) { return; // Do not create custom config file } if (str_empty (config_directory)) { config_directory = get_config_directory (); } tasprintf (&config_filename, "%s/%s", config_directory, CONFIG_FILE); config_filename = make_full_path (config_filename); if (!disable_output) { printf ("I: config dir=[%s]\n", config_directory); } // printf ("I: config file=[%s]\n", config_filename); int config_file_fd; //char *config_directory = get_config_directory (); //char *downloads_directory = get_downloads_directory (); if (!mkdir (config_directory, CONFIG_DIRECTORY_MODE)) { if (!disable_output) { printf ("[%s] created\n", config_directory); } } tfree_str (config_directory); config_directory = NULL; // see if config file is there if (access (config_filename, R_OK) != 0) { // config file missing, so touch it config_file_fd = open (config_filename, O_CREAT | O_RDWR, 0600); if (config_file_fd == -1) { perror ("open[config_file]"); printf ("I: config_file=[%s]\n", config_filename); exit (EXIT_FAILURE); } if (write (config_file_fd, DEFAULT_CONFIG_CONTENTS, strlen (DEFAULT_CONFIG_CONTENTS)) <= 0) { perror ("write[config_file]"); exit (EXIT_FAILURE); } close (config_file_fd); } }
void tgl_set_app_version (struct tgl_state *TLS, const char *app_version) { if (TLS->app_version) { tfree_str (TLS->app_version); } TLS->app_version = tstrdup (app_version); }
static void send_file_encrypted_end (struct tgl_state *TLS, struct send_file *f, void *callback, void *callback_extra) { out_int (CODE_messages_send_encrypted_file); out_int (CODE_input_encrypted_chat); out_int (tgl_get_peer_id (f->to_id)); tgl_peer_t *P = tgl_peer_get (TLS, f->to_id); assert (P); out_long (P->encr_chat.access_hash); long long r; tglt_secure_random (&r, 8); out_long (r); encr_start (); out_int (CODE_decrypted_message_layer); out_random (15 + 4 * (lrand48 () % 3)); out_int (TGL_ENCRYPTED_LAYER); out_int (2 * P->encr_chat.in_seq_no + (P->encr_chat.admin_id != TLS->our_id)); out_int (2 * P->encr_chat.out_seq_no + (P->encr_chat.admin_id == TLS->our_id)); out_int (CODE_decrypted_message); out_long (r); out_int (P->encr_chat.ttl); out_string (""); int *save_ptr = packet_ptr; if (f->flags == -1) { out_int (CODE_decrypted_message_media_photo); } else if ((f->flags & TGLDF_VIDEO)) { out_int (CODE_decrypted_message_media_video); } else if ((f->flags & TGLDF_AUDIO)) { out_int (CODE_decrypted_message_media_audio); } else { out_int (CODE_decrypted_message_media_document); } if (f->flags == -1 || !(f->flags & TGLDF_AUDIO)) { out_cstring ("", 0); out_int (90); out_int (90); } if (f->flags == -1) { out_int (f->w); out_int (f->h); } else if (f->flags & TGLDF_VIDEO) { out_int (f->duration); out_string (tg_mime_by_filename (f->file_name)); out_int (f->w); out_int (f->h); } else if (f->flags & TGLDF_AUDIO) { out_int (f->duration); out_string (tg_mime_by_filename (f->file_name)); } else { out_string (""); out_string (tg_mime_by_filename (f->file_name)); // document } out_int (f->size); out_cstring ((void *)f->key, 32); out_cstring ((void *)f->init_iv, 32); int *save_in_ptr = in_ptr; int *save_in_end = in_end; in_ptr = save_ptr; in_end = packet_ptr; assert (skip_type_any (TYPE_TO_PARAM(decrypted_message_media)) >= 0); assert (in_ptr == in_end); in_ptr = save_ptr; in_end = packet_ptr; struct tl_ds_decrypted_message_media *DS_DMM = fetch_ds_type_decrypted_message_media (TYPE_TO_PARAM (decrypted_message_media)); in_end = save_in_ptr; in_ptr = save_in_end; int peer_type = tgl_get_peer_type (f->to_id); int peer_id = tgl_get_peer_id (f->to_id); int date = time (NULL); encr_finish (&P->encr_chat); if (f->size < (16 << 20)) { out_int (CODE_input_encrypted_file_uploaded); } else { out_int (CODE_input_encrypted_file_big_uploaded); } out_long (f->id); out_int (f->part_num); if (f->size < (16 << 20)) { out_string (""); } unsigned char md5[16]; unsigned char str[64]; memcpy (str, f->key, 32); memcpy (str + 32, f->init_iv, 32); MD5 (str, 64, md5); out_int ((*(int *)md5) ^ (*(int *)(md5 + 4))); tfree_secure (f->iv, 32); bl_do_create_message_encr_new (TLS, r, &TLS->our_id, &peer_type, &peer_id, &date, NULL, 0, DS_DMM, NULL, NULL, TGLMF_OUT | TGLMF_UNREAD | TGLMF_ENCRYPTED | TGLMF_CREATE | TGLMF_CREATED); free_ds_type_decrypted_message_media (DS_DMM, TYPE_TO_PARAM (decrypted_message_media)); struct tgl_message *M = tgl_message_get (TLS, r); assert (M); tglq_send_query (TLS, TLS->DC_working, packet_ptr - packet_buffer, packet_buffer, &send_encr_file_methods, M, callback, callback_extra); tfree_str (f->file_name); tfree (f, sizeof (*f)); }
void set_default_username (const char *s) { if (default_username) { tfree_str (default_username); } default_username = tstrdup (s); }
void parse_config (void) { //config_filename = make_full_path (config_filename); config_t conf; config_init (&conf); if (config_read_file (&conf, config_filename) != CONFIG_TRUE) { fprintf (stderr, "Can not read config '%s': error '%s' on the line %d\n", config_filename, config_error_text (&conf), config_error_line (&conf)); exit (2); } if (!prefix) { config_lookup_string (&conf, "default_profile", (void *)&prefix); } static char buf[1000]; int l = 0; if (prefix) { l = strlen (prefix); memcpy (buf, prefix, l); buf[l ++] = '.'; } int test_mode = 0; strcpy (buf + l, "test"); config_lookup_bool (&conf, buf, &test_mode); if (test_mode) { tgl_set_test_mode (TLS); } strcpy (buf + l, "log_level"); long long t = log_level; config_lookup_int (&conf, buf, (void *)&t); log_level = t; if (!msg_num_mode) { strcpy (buf + l, "msg_num"); config_lookup_bool (&conf, buf, &msg_num_mode); } parse_config_val (&conf, &config_directory, "config_directory", CONFIG_DIRECTORY, 0); config_directory = make_full_path (config_directory); parse_config_val (&conf, &auth_file_name, "auth_file", AUTH_KEY_FILE, config_directory); parse_config_val (&conf, &downloads_directory, "downloads", DOWNLOADS_DIRECTORY, config_directory); if (!lua_file) { parse_config_val (&conf, &lua_file, "lua_script", 0, config_directory); } if (!python_file) { parse_config_val (&conf, &python_file, "python_script", 0, config_directory); } #if 0 strcpy (buf + l, "binlog_enabled"); config_lookup_bool (&conf, buf, &binlog_enabled); #else binlog_enabled = 0; #endif int pfs_enabled = 0; strcpy (buf + l, "pfs_enabled"); config_lookup_bool (&conf, buf, &pfs_enabled); if (pfs_enabled) { tgl_enable_pfs (TLS); } if (binlog_enabled) { parse_config_val (&conf, &binlog_file_name, "binlog", BINLOG_FILE, config_directory); tgl_set_binlog_mode (TLS, 1); tgl_set_binlog_path (TLS, binlog_file_name); } else { tgl_set_binlog_mode (TLS, 0); parse_config_val (&conf, &state_file_name, "state_file", STATE_FILE, config_directory); parse_config_val (&conf, &secret_chat_file_name, "secret", SECRET_CHAT_FILE, config_directory); //tgl_set_auth_file_path (auth_file_name); } tgl_set_download_directory (TLS, downloads_directory); if (!mkdir (config_directory, CONFIG_DIRECTORY_MODE)) { if (!disable_output) { printf ("[%s] created\n", config_directory); } } if (!mkdir (downloads_directory, CONFIG_DIRECTORY_MODE)) { if (!disable_output) { printf ("[%s] created\n", downloads_directory); } } tfree_str (config_directory); config_directory = NULL; config_destroy (&conf); }
int loop (void) { on_start (); if (binlog_enabled) { double t = get_double_time (); logprintf ("replay log start\n"); replay_log (); logprintf ("replay log end in %lf seconds\n", get_double_time () - t); write_binlog (); #ifdef USE_LUA lua_binlog_end (); #endif } else { read_auth_file (); } update_prompt (); assert (DC_list[dc_working_num]); if (!DC_working || !DC_working->auth_key_id) { // if (auth_state == 0) { DC_working = DC_list[dc_working_num]; assert (!DC_working->auth_key_id); dc_authorize (DC_working); assert (DC_working->auth_key_id); auth_state = 100; write_auth_file (); } if (verbosity) { logprintf ("Requesting info about DC...\n"); } do_help_get_config (); net_loop (0, mcs); if (verbosity) { logprintf ("DC_info: %d new DC got\n", new_dc_num); } int i; for (i = 0; i <= MAX_DC_NUM; i++) if (DC_list[i] && !DC_list[i]->auth_key_id) { dc_authorize (DC_list[i]); assert (DC_list[i]->auth_key_id); write_auth_file (); } if (auth_state == 100 || !(DC_working->has_auth)) { if (!default_username) { size_t size = 0; char *user = 0; if (!user) { printf ("Telephone number (with '+' sign): "); if (net_getline (&user, &size) == -1) { perror ("getline()"); exit (EXIT_FAILURE); } set_default_username (user); } } int res = do_auth_check_phone (default_username); assert (res >= 0); logprintf ("%s\n", res > 0 ? "phone registered" : "phone not registered"); if (res > 0 && !register_mode) { do_send_code (default_username); char *code = 0; size_t size = 0; printf ("Code from sms (if you did not receive an SMS and want to be called, type \"call\"): "); while (1) { if (net_getline (&code, &size) == -1) { perror ("getline()"); exit (EXIT_FAILURE); } if (!strcmp (code, "call")) { printf ("You typed \"call\", switching to phone system.\n"); do_phone_call (default_username); printf ("Calling you! Code: "); continue; } if (do_send_code_result (code) >= 0) { break; } printf ("Invalid code. Try again: "); tfree_str (code); } auth_state = 300; } else { printf ("User is not registered. Do you want to register? [Y/n] "); char *code; size_t size; if (net_getline (&code, &size) == -1) { perror ("getline()"); exit (EXIT_FAILURE); } if (!*code || *code == 'y' || *code == 'Y') { printf ("Ok, starting registartion.\n"); } else { printf ("Then try again\n"); exit (EXIT_SUCCESS); } char *first_name; printf ("First name: "); if (net_getline (&first_name, &size) == -1) { perror ("getline()"); exit (EXIT_FAILURE); } char *last_name; printf ("Last name: "); if (net_getline (&last_name, &size) == -1) { perror ("getline()"); exit (EXIT_FAILURE); } int dc_num = do_get_nearest_dc (); assert (dc_num >= 0 && dc_num <= MAX_DC_NUM && DC_list[dc_num]); dc_working_num = dc_num; DC_working = DC_list[dc_working_num]; do_send_code (default_username); printf ("Code from sms (if you did not receive an SMS and want to be called, type \"call\"): "); while (1) { if (net_getline (&code, &size) == -1) { perror ("getline()"); exit (EXIT_FAILURE); } if (!strcmp (code, "call")) { printf ("You typed \"call\", switching to phone system.\n"); do_phone_call (default_username); printf ("Calling you! Code: "); continue; } if (do_send_code_result_auth (code, first_name, last_name) >= 0) { break; } printf ("Invalid code. Try again: "); tfree_str (code); } auth_state = 300; } } for (i = 0; i <= MAX_DC_NUM; i++) if (DC_list[i] && !DC_list[i]->has_auth) { do_export_auth (i); do_import_auth (i); bl_do_dc_signed (i); write_auth_file (); } write_auth_file (); fflush (stdout); fflush (stderr); read_state_file (); read_secret_chat_file (); set_interface_callbacks (); do_get_difference (); net_loop (0, dgot); #ifdef USE_LUA lua_diff_end (); #endif send_all_unsent (); do_get_dialog_list (); if (wait_dialog_list) { dialog_list_got = 0; net_loop (0, dlgot); } return main_loop (); }
void bl_do_channel (struct tgl_state *TLS, int id, long long *access_hash, int *date, const char *title, int title_len, const char *username, int username_len, struct tl_ds_chat_photo *chat_photo, struct tl_ds_photo *photo, int *version, char *about, int about_len, int *participants_count, int *admins_count, int *kicked_count, int *last_read_in, int flags) /* {{{ */ { tgl_peer_t *_U = tgl_peer_get (TLS, TGL_MK_CHANNEL (id)); unsigned updates = 0; if ((flags & TGLPF_CREATE) && (flags != TGL_FLAGS_UNCHANGED)) { if (!_U) { _U = talloc0 (sizeof (*_U)); _U->id = TGL_MK_CHANNEL (id); tglp_insert_channel (TLS, _U); } else { assert (!(_U->flags & TGLPF_CREATED)); } updates |= TGL_UPDATE_CREATED; } else { assert (_U->flags & TGLPF_CREATED); } struct tgl_channel *C = &_U->channel; if (flags == TGL_FLAGS_UNCHANGED) { flags = C->flags; } flags &= TGLCHF_TYPE_MASK; if ((flags & TGLCHF_TYPE_MASK) != (C->flags & TGLCHF_TYPE_MASK)) { updates |= TGL_UPDATE_FLAGS; } C->flags = (C->flags & ~TGLCHF_TYPE_MASK) | flags; if (access_hash && *access_hash != C->access_hash) { C->access_hash = *access_hash; C->id.access_hash = *access_hash; updates |= TGL_UPDATE_ACCESS_HASH; } if (date) { C->date = *date; } if (title && (!C->title || mystreq1 (C->title, title, title_len))) { if (C->title) { tfree_str (C->title); } C->title = tstrndup (title, title_len); if (C->print_title) { tglp_peer_delete_name (TLS, (void *)C); tfree_str (C->print_title); } C->print_title = TLS->callback.create_print_name (TLS, C->id, C->title, 0, 0, 0); tglp_peer_insert_name (TLS, (void *)C); updates |= TGL_UPDATE_TITLE; } if (chat_photo) { if (chat_photo->photo_big && DS_LVAL (chat_photo->photo_big->secret) != C->photo_big.secret) { tglf_fetch_file_location (TLS, &C->photo_big, chat_photo->photo_big); tglf_fetch_file_location (TLS, &C->photo_small, chat_photo->photo_small); updates |= TGL_UPDATE_PHOTO; } } if (photo) { if (!C->photo || C->photo->id != DS_LVAL (photo->id)) { if (C->photo) { tgls_free_photo (TLS, C->photo); } C->photo = tglf_fetch_alloc_photo (TLS, photo); C->flags |= TGLPF_HAS_PHOTO; } } if (username) { if (!C->username || mystreq1 (C->username, username, username_len)) { if (C->username) { tfree_str (C->username); } C->username = tstrndup (username, username_len); updates |= TGL_UPDATE_USERNAME; } } if (about) { if (!C->about || mystreq1 (C->about, about, about_len)) { tfree_str (C->about); } C->about = tstrndup (about, about_len); } if (admins_count) { C->admins_count = *admins_count; } if (participants_count) { C->participants_count = *participants_count; } if (kicked_count) { C->kicked_count = *kicked_count; } if (last_read_in) { C->last_read_in = *last_read_in; tgls_messages_mark_read (TLS, C->last, 0, C->last_read_in); } if (TLS->callback.channel_update && updates) { TLS->callback.channel_update (TLS, C, updates); } }
void bl_do_chat (struct tgl_state *TLS, int id, const char *title, int title_len, int *user_num, int *date, int *version, struct tl_ds_vector *participants, struct tl_ds_chat_photo *chat_photo, struct tl_ds_photo *photo, int *admin, int *last_read_in, int *last_read_out, int flags) /* {{{ */ { tgl_peer_t *_U = tgl_peer_get (TLS, TGL_MK_CHAT (id)); unsigned updates = 0; if ((flags & TGLPF_CREATE) && (flags != TGL_FLAGS_UNCHANGED)) { if (!_U) { _U = talloc0 (sizeof (*_U)); _U->id = TGL_MK_CHAT (id); tglp_insert_chat (TLS, _U); } else { assert (!(_U->flags & TGLPF_CREATED)); } updates |= TGL_UPDATE_CREATED; } else { assert (_U->flags & TGLPF_CREATED); } struct tgl_chat *C = &_U->chat; if (flags == TGL_FLAGS_UNCHANGED) { flags = C->flags; } flags &= TGLCF_TYPE_MASK; if ((flags & TGLCF_TYPE_MASK) != (C->flags & TGLCF_TYPE_MASK)) { updates |= TGL_UPDATE_FLAGS; } C->flags = (C->flags & ~TGLCF_TYPE_MASK) | flags; if (title && (!C->title || mystreq1 (C->title, title, title_len))) { if (C->title) { tfree_str (C->title); } C->title = tstrndup (title, title_len); if (C->print_title) { tglp_peer_delete_name (TLS, (void *)C); tfree_str (C->print_title); } C->print_title = TLS->callback.create_print_name (TLS, C->id, C->title, 0, 0, 0); tglp_peer_insert_name (TLS, (void *)C); updates |= TGL_UPDATE_TITLE; } if (user_num) { C->users_num = *user_num; } if (date) { C->date = *date; } if (chat_photo && chat_photo->photo_big) { if (DS_LVAL (chat_photo->photo_big->secret) != C->photo_big.secret) { tglf_fetch_file_location (TLS, &C->photo_big, chat_photo->photo_big); tglf_fetch_file_location (TLS, &C->photo_small, chat_photo->photo_small); updates |= TGL_UPDATE_PHOTO; } } if (photo) { if (!C->photo || C->photo->id != DS_LVAL (photo->id)) { if (C->photo) { tgls_free_photo (TLS, C->photo); } C->photo = tglf_fetch_alloc_photo (TLS, photo); C->flags |= TGLPF_HAS_PHOTO; //updates |= TGL_UPDATE_PHOTO; } } if (admin && *admin != C->admin_id) { C->admin_id = *admin; updates |= TGL_UPDATE_ADMIN; } if (version) { assert (participants); if (*version > C->version) { C->version = *version; if (C->user_list) { tfree (C->user_list, 12 * C->user_list_size); } C->user_list_size = DS_LVAL (participants->f1); C->user_list = talloc (12 * C->user_list_size); int i; for (i = 0; i < C->user_list_size; i++) { struct tl_ds_chat_participant *DS_P = participants->f2[i]; C->user_list[i].user_id = DS_LVAL (DS_P->user_id); C->user_list[i].inviter_id = DS_LVAL (DS_P->inviter_id); C->user_list[i].date = DS_LVAL (DS_P->date); } updates |= TGL_UPDATE_MEMBERS; } } if (last_read_in) { C->last_read_in = *last_read_in; tgls_messages_mark_read (TLS, C->last, 0, C->last_read_in); } if (last_read_out) { C->last_read_out = *last_read_out; tgls_messages_mark_read (TLS, C->last, TGLMF_OUT, C->last_read_out); } if (TLS->callback.chat_update && updates) { TLS->callback.chat_update (TLS, C, updates); } }
void bl_do_user (struct tgl_state *TLS, int id, long long *access_hash, const char *first_name, int first_name_len, const char *last_name, int last_name_len, const char *phone, int phone_len, const char *username, int username_len, struct tl_ds_photo *photo, struct tl_ds_user_profile_photo *profile_photo, int *last_read_in, int *last_read_out, struct tl_ds_bot_info *bot_info, int flags) /* {{{ */ { tgl_peer_t *_U = tgl_peer_get (TLS, TGL_MK_USER (id)); unsigned updates = 0; if ((flags & TGLPF_CREATE) && (flags != TGL_FLAGS_UNCHANGED)) { if (!_U) { _U = talloc0 (sizeof (*_U)); _U->id = TGL_MK_USER (id); tglp_insert_user (TLS, _U); } else { assert (!(_U->flags & TGLPF_CREATED)); } updates |= TGL_UPDATE_CREATED; } else { assert (_U->flags & TGLPF_CREATED); } struct tgl_user *U = (void *)_U; if (flags == TGL_FLAGS_UNCHANGED) { flags = U->flags; } flags &= TGLUF_TYPE_MASK; if ((flags & TGLUF_TYPE_MASK) != (U->flags & TGLUF_TYPE_MASK)) { updates |= TGL_UPDATE_FLAGS; } U->flags = (U->flags & ~TGLUF_TYPE_MASK) | flags; if (access_hash && *access_hash != U->access_hash) { U->access_hash = *access_hash; U->id.access_hash = *access_hash; updates |= TGL_UPDATE_ACCESS_HASH; } if (first_name || last_name) { if (!U->first_name || !U->last_name || mystreq1 (U->first_name, first_name, first_name_len) || mystreq1 (U->last_name, last_name, last_name_len)) { if (U->first_name) { tfree_str (U->first_name); } U->first_name = tstrndup (first_name, first_name_len); if (U->last_name) { tfree_str (U->last_name); } U->last_name = tstrndup (last_name, last_name_len); updates |= TGL_UPDATE_NAME; if (U->print_name) { tglp_peer_delete_name (TLS, (void *)U); tfree_str (U->print_name); } U->print_name = TLS->callback.create_print_name (TLS, U->id, U->first_name, U->last_name, 0, 0); tglp_peer_insert_name (TLS, (void *)U); } } if (phone && (!U->phone || mystreq1 (U->phone, phone, phone_len))) { if (U->phone) { tfree_str (U->phone); } U->phone = tstrndup (phone, phone_len); updates |= TGL_UPDATE_PHONE; } if (username && (!U->username || mystreq1 (U->username, username, username_len))) { if (U->username) { tfree_str (U->username); } U->username = tstrndup (username, username_len); updates |= TGL_UPDATE_USERNAME; } if (photo) { if (!U->photo || U->photo->id != DS_LVAL (photo->id)) { if (U->photo) { tgls_free_photo (TLS, U->photo); } U->photo = tglf_fetch_alloc_photo (TLS, photo); U->flags |= TGLUF_HAS_PHOTO; } } if (profile_photo) { if (U->photo_id != DS_LVAL (profile_photo->photo_id)) { U->photo_id = DS_LVAL (profile_photo->photo_id); tglf_fetch_file_location (TLS, &U->photo_big, profile_photo->photo_big); tglf_fetch_file_location (TLS, &U->photo_small, profile_photo->photo_small); updates |= TGL_UPDATE_PHOTO; } } if (last_read_in) { U->last_read_in = *last_read_in; tgls_messages_mark_read (TLS, U->last, 0, U->last_read_in); } if (last_read_out) { U->last_read_out = *last_read_out; tgls_messages_mark_read (TLS, U->last, TGLMF_OUT, U->last_read_out); } if (bot_info) { if (!U->bot_info || U->bot_info->version != DS_LVAL (bot_info->version)) { if (U->bot_info) { tgls_free_bot_info (TLS, U->bot_info); } U->bot_info = tglf_fetch_alloc_bot_info (TLS, bot_info); } } if (TLS->callback.user_update && updates) { TLS->callback.user_update (TLS, U, updates); } }