diff --git a/CHANGELOG b/CHANGELOG index a76fffd..dda401b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ 1.3.2 * use TGL-2.0.2 * add block/unblock user methods +* support for autocomplete for bot's commands 1.3.1 * added error codes 1.3.0 diff --git a/interface.c b/interface.c index b651310..66b7562 100644 --- a/interface.c +++ b/interface.c @@ -299,6 +299,30 @@ void next_token_end (void) { } } +void next_token_end_ac (void) { + skip_wspc (); + + if (*line_ptr && *line_ptr != '"' && *line_ptr != '\'') { + cur_token_quoted = 0; + cur_token = line_ptr; + while (*line_ptr) { line_ptr ++; } + cur_token_len = line_ptr - cur_token; + assert (cur_token_len > 0); + cur_token_end_str = !force_end_mode; + return; + } else { + if (*line_ptr) { + next_token (); + skip_wspc (); + if (*line_ptr) { + cur_token_len = -1; + } + } else { + next_token (); + } + } +} + #define NOT_FOUND (int)0x80000000 tgl_peer_id_t TGL_PEER_NOT_FOUND = {.id = NOT_FOUND}; @@ -569,6 +593,7 @@ enum command_argument { ca_number, ca_double, ca_string_end, + ca_msg_string_end, ca_string, ca_modifier, ca_command, @@ -1360,11 +1385,11 @@ struct command commands[MAX_COMMANDS_SIZE] = { {"load_video_thumb", {ca_number, ca_none}, do_load_video_thumb, "load_video_thumb \tDownloads file to downloads dirs. Prints file name after download end", NULL}, {"main_session", {ca_none}, do_main_session, "main_session\tSends updates to this connection (or terminal). Useful only with listening socket", NULL}, {"mark_read", {ca_peer, ca_none}, do_mark_read, "mark_read \tMarks messages with peer as read", NULL}, - {"msg", {ca_peer, ca_string_end, ca_none}, do_msg, "msg \tSends text message to peer", NULL}, + {"msg", {ca_peer, ca_msg_string_end, ca_none}, do_msg, "msg \tSends text message to peer", NULL}, {"quit", {ca_none}, do_quit, "quit\tQuits immediately", NULL}, {"rename_chat", {ca_chat, ca_string_end, ca_none}, do_rename_chat, "rename_chat \tRenames chat", NULL}, {"rename_contact", {ca_user, ca_string, ca_string, ca_none}, do_rename_contact, "rename_contact \tRenames contact", NULL}, - {"reply", {ca_number, ca_string_end, ca_none}, do_reply, "reply \tSends text reply to message", NULL}, + {"reply", {ca_number, ca_msg_string_end, ca_none}, do_reply, "reply \tSends text reply to message", NULL}, {"reply_audio", {ca_number, ca_file_name, ca_none}, do_send_audio, "reply_audio \tSends audio to peer", NULL}, {"reply_contact", {ca_number, ca_string, ca_string, ca_string, ca_none}, do_reply_contact, "reply_contact \tSends contact (not necessary telegram user)", NULL}, {"reply_document", {ca_number, ca_file_name, ca_none}, do_reply_document, "reply_document \tSends document to peer", NULL}, @@ -1422,9 +1447,14 @@ void register_new_command (struct command *cmd) { commands[i] = *cmd; } +tgl_peer_t *autocomplete_peer; +int autocomplete_id; + enum command_argument get_complete_mode (void) { force_end_mode = 0; line_ptr = rl_line_buffer; + autocomplete_peer = NULL; + autocomplete_id = 0; while (1) { next_token (); @@ -1471,8 +1501,9 @@ enum command_argument get_complete_mode (void) { int opt = (*flags) & ca_optional; if (op == ca_none) { return ca_none; } - if (op == ca_string_end || op == ca_file_name_end) { - next_token_end (); + if (op == ca_string_end || op == ca_file_name_end || op == ca_msg_string_end) { + next_token_end_ac (); + if (cur_token_len < 0 || !cur_token_end_str) { return ca_none; } else { @@ -1510,10 +1541,18 @@ enum command_argument get_complete_mode (void) { ok = (tgl_get_peer_type (cur_token_encr_chat ()) != NOT_FOUND); break; case ca_peer: - ok = (tgl_get_peer_type (cur_token_user ()) != NOT_FOUND); + ok = (tgl_get_peer_type (cur_token_peer ()) != NOT_FOUND); + if (ok) { + autocomplete_peer = tgl_peer_get (TLS, cur_token_peer ()); + autocomplete_id = 0; + } break; case ca_number: ok = (cur_token_int () != NOT_FOUND); + if (ok) { + autocomplete_peer = NULL; + autocomplete_id = cur_token_int (); + } break; case ca_double: ok = (cur_token_double () != NOT_FOUND); @@ -1585,6 +1624,70 @@ int complete_command_list (int index, const char *text, int len, char **R) { } } + +int complete_spec_message_answer (struct tgl_message *M, int index, const char *text, int len, char **R) { + if (!M || !M->reply_markup || !M->reply_markup->rows) { + *R = NULL; + return -1; + } + index ++; + + int total = M->reply_markup->row_start[M->reply_markup->rows]; + while (index < total && strncmp (M->reply_markup->buttons[index], text, len)) { + index ++; + } + + if (index < total) { + *R = strdup (M->reply_markup->buttons[index]); + assert (*R); + return index; + } else { + *R = NULL; + return -1; + } +} + +int complete_message_answer (tgl_peer_t *P, int index, const char *text, int len, char **R) { + struct tgl_message *M = P->last; + while (M && (M->flags & TGLMF_OUT)) { + M = M->next; + } + + + return complete_spec_message_answer (M, index, text, len, R); +} + +int complete_user_command (tgl_peer_t *P, int index, const char *text, int len, char **R) { + if (len <= 0 || *text != '/') { + return complete_message_answer (P, index, text, len, R); + } + text ++; + len --; + struct tgl_user *U = (void *)P; + index ++; + if (!U->bot_info) { + *R = NULL; + return -1; + } + while (index < U->bot_info->commands_num && strncmp (U->bot_info->commands[index].command, text, len)) { + index ++; + } + if (index < U->bot_info->commands_num) { + *R = NULL; + assert (asprintf (R, "/%s", U->bot_info->commands[index].command) >= 0); + assert (*R); + return index; + } else { + *R = NULL; + return -1; + } +} + +int complete_chat_command (tgl_peer_t *P, int index, const char *text, int len, char **R) { + *R = NULL; + return -1; +} + char *command_generator (const char *text, int state) { #ifndef DISABLE_EXTF static int len; @@ -1616,7 +1719,7 @@ char *command_generator (const char *text, int state) { if (mode != ca_file_name && mode != ca_file_name_end && index == -1) { return 0; } } - if (mode == ca_none || mode == ca_string || mode == ca_string_end || mode == ca_number || mode == ca_double) { + if (mode == ca_none || mode == ca_string || mode == ca_string_end || mode == ca_number || mode == ca_double) { if (c) { rl_line_buffer[rl_point] = c; } return 0; } @@ -1653,6 +1756,30 @@ char *command_generator (const char *text, int state) { index = complete_string_list (modifiers, index, command_pos, command_len, &R); if (c) { rl_line_buffer[rl_point] = c; } return R; + case ca_msg_string_end: + if (autocomplete_peer) { + if (tgl_get_peer_type (autocomplete_peer->id) == TGL_PEER_USER) { + index = complete_user_command (autocomplete_peer, index, command_pos, command_len, &R); + } + if (tgl_get_peer_type (autocomplete_peer->id) == TGL_PEER_CHAT) { + index = complete_chat_command (autocomplete_peer, index, command_pos, command_len, &R); + } + } + if (autocomplete_id) { + struct tgl_message *M = tgl_message_get (TLS, autocomplete_id); + if (M) { + if (command_len > 0 && *command_pos == '/') { + tgl_peer_t *P = tgl_peer_get (TLS, M->from_id); + if (P) { + index = complete_user_command (autocomplete_peer, index, command_pos, command_len, &R); + } + } else { + index = complete_spec_message_answer (M, index, command_pos, command_len, &R); + } + } + } + if (c) { rl_line_buffer[rl_point] = c; } + return R; #ifndef DISABLE_EXTF case ca_extf: index = tglf_extf_autocomplete (TLS, text, len, index, &R, rl_line_buffer, rl_point); @@ -2049,6 +2176,17 @@ void print_user_info_gw (struct tgl_state *TLSR, void *extra, int success, struc mprintf (ev, "\t"); print_user_status (&U->status, ev); mprintf (ev, "\n"); + + if (U->bot_info) { + mprintf (ev, "\tshare_text: %s\n", U->bot_info->share_text); + mprintf (ev, "\tdescription: %s\n", U->bot_info->description); + mprintf (ev, "\tcommands:\n"); + + int i; + for (i = 0; i < U->bot_info->commands_num; i++) { + mprintf (ev, "\t\t/%s <%s>: %s\n", U->bot_info->commands[i].command, U->bot_info->commands[i].params, U->bot_info->commands[i].description); + } + } mpop_color (ev); } else { #ifdef USE_JSON @@ -2782,7 +2920,7 @@ void interpreter_ex (char *line, void *ex) { break; } - if (op == ca_string_end || op == ca_file_name_end) { + if (op == ca_string_end || op == ca_file_name_end || op == ca_msg_string_end) { next_token_end (); if (cur_token_len < 0) { fail_interface (TLS, ex, ENOSYS, "can not parse string_end arg #%d", args_num); @@ -2915,7 +3053,7 @@ void interpreter_ex (char *line, void *ex) { continue; } } - assert (0); + //assert (0); } int i; for (i = 0; i < args_num; i++) { diff --git a/json-tg.c b/json-tg.c index 1a74d16..5dcf3ea 100644 --- a/json-tg.c +++ b/json-tg.c @@ -84,7 +84,7 @@ void json_pack_encr_chat (json_t *res, tgl_peer_t *P) { json_t *json_pack_peer (tgl_peer_id_t id) { tgl_peer_t *P = tgl_peer_get (TLS, id); - assert (P); + //assert (P); json_t *res = json_object (); assert (json_object_set (res, "id", json_integer (tgl_get_peer_id (id))) >= 0); diff --git a/loop.c b/loop.c index 050537b..f4ac7c9 100644 --- a/loop.c +++ b/loop.c @@ -76,6 +76,7 @@ int verbosity; extern int readline_disabled; +extern int bot_mode; int binlog_read; extern char *default_username; extern char *auth_token; @@ -360,10 +361,10 @@ void write_dc (struct tgl_dc *DC, void *extra) { assert (DC->flags & TGLDCF_LOGGED_IN); - assert (write (auth_file_fd, &DC->port, 4) == 4); - int l = strlen (DC->ip); + assert (write (auth_file_fd, &DC->options[0]->port, 4) == 4); + int l = strlen (DC->options[0]->ip); assert (write (auth_file_fd, &l, 4) == 4); - assert (write (auth_file_fd, DC->ip, l) == l); + assert (write (auth_file_fd, DC->options[0]->ip, l) == l); assert (write (auth_file_fd, &DC->auth_key_id, 8) == 8); assert (write (auth_file_fd, DC->auth_key, 256) == 256); } @@ -701,6 +702,9 @@ int loop (void) { if (ipv6_enabled) { tgl_enable_ipv6 (TLS); } + if (bot_mode) { + tgl_enable_bot (TLS); + } if (disable_link_preview) { tgl_disable_link_preview (TLS); } diff --git a/main.c b/main.c index 65a33c8..851799f 100644 --- a/main.c +++ b/main.c @@ -100,6 +100,7 @@ "# This is an empty config file\n" \ "# Feel free to put something here\n" +int bot_mode; int verbosity; int msg_num_mode; char *default_username; @@ -492,6 +493,7 @@ void usage (void) { printf (" --help/-h prints this help\n"); printf (" --accept-any-tcp accepts tcp connections from any src (only loopback by default)\n"); printf (" --disable-link-preview disables server-side previews to links\n"); + printf (" --bot/-b bot mode\n"); #ifdef USE_JSON printf (" --json prints answers and values in json format\n"); #endif @@ -640,6 +642,7 @@ void args_parse (int argc, char **argv) { {"exec", required_argument, 0, 'e'}, {"disable-names", no_argument, 0, 'I'}, {"enable-ipv6", no_argument, 0, '6'}, + {"bot", no_argument, 0, 'b'}, {"help", no_argument, 0, 'h'}, {"accept-any-tcp", no_argument, 0, 1001}, {"disable-link-preview", no_argument, 0, 1002}, @@ -651,7 +654,7 @@ void args_parse (int argc, char **argv) { int opt = 0; - while ((opt = getopt_long (argc, argv, "u:hk:vNl:fEwWCRdL:DU:G:qP:S:e:I6" + while ((opt = getopt_long (argc, argv, "u:hk:vNl:fEwWCRdL:DU:G:qP:S:e:I6b" #ifdef HAVE_LIBCONFIG "c:p:" #else @@ -667,6 +670,9 @@ void args_parse (int argc, char **argv) { )) != -1) { switch (opt) { + case 'b': + bot_mode ++; + break; case 1000: tgl_allocator = &tgl_allocator_debug; break; diff --git a/telegram.h b/telegram.h index 1abe0ff..aaca500 100644 --- a/telegram.h +++ b/telegram.h @@ -21,4 +21,4 @@ #define PROG_NAME "telegram-cli" #endif -#define TELEGRAM_CLI_VERSION "1.3.1" +#define TELEGRAM_CLI_VERSION "1.3.2" diff --git a/tgl b/tgl index 41121e9..73482c7 160000 --- a/tgl +++ b/tgl @@ -1 +1 @@ -Subproject commit 41121e9908fc5ddebe3ec6eca9f55172b630f21a +Subproject commit 73482c7a461ff4341ea098dd258b6373f530ab70