1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
// Copyright 2020 - developers of the `grammers` project.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use crate::client::messages::parse_mention_entities;
use crate::utils::generate_random_id;
use crate::Client;
use crate::{types::IterBuffer, InputMessage};
pub use grammers_mtsender::{AuthorizationError, InvocationError};
use grammers_session::PackedChat;
use grammers_tl_types as tl;

const MAX_LIMIT: usize = 50;

pub struct InlineResult {
    client: Client,
    query_id: i64,
    result: tl::enums::BotInlineResult,
}

pub type InlineResultIter = IterBuffer<tl::functions::messages::GetInlineBotResults, InlineResult>;

impl InlineResult {
    /// Send this inline result to the specified chat.
    // TODO return the produced message
    pub async fn send<C: Into<PackedChat>>(&self, chat: C) -> Result<(), InvocationError> {
        self.client
            .invoke(&tl::functions::messages::SendInlineBotResult {
                silent: false,
                background: false,
                clear_draft: false,
                hide_via: false,
                peer: chat.into().to_input_peer(),
                reply_to: None,
                random_id: generate_random_id(),
                query_id: self.query_id,
                id: self.id().to_string(),
                schedule_date: None,
                send_as: None,
                quick_reply_shortcut: None,
            })
            .await
            .map(drop)
    }

    /// The ID for this result.
    pub fn id(&self) -> &str {
        use tl::enums::BotInlineResult::*;

        match &self.result {
            Result(r) => &r.id,
            BotInlineMediaResult(r) => &r.id,
        }
    }

    /// The title for this result, if any.
    pub fn title(&self) -> Option<&String> {
        use tl::enums::BotInlineResult::*;

        match &self.result {
            Result(r) => r.title.as_ref(),
            BotInlineMediaResult(r) => r.title.as_ref(),
        }
    }
}

impl InlineResultIter {
    fn new(client: &Client, bot: PackedChat, query: &str) -> Self {
        Self::from_request(
            client,
            MAX_LIMIT,
            tl::functions::messages::GetInlineBotResults {
                bot: bot.to_input_user_lossy(),
                peer: tl::enums::InputPeer::Empty,
                geo_point: None,
                query: query.to_string(),
                offset: String::new(),
            },
        )
    }

    /// Indicate the bot the chat where this inline query will be sent to.
    ///
    /// Some bots use this information to return different results depending on the type of the
    /// chat, and some even "need" it to give useful results.
    pub fn chat<C: Into<PackedChat>>(mut self, chat: C) -> Self {
        self.request.peer = chat.into().to_input_peer();
        self
    }

    /// Return the next `InlineResult` from the internal buffer, filling the buffer previously if
    /// it's empty.
    ///
    /// Returns `None` if the `limit` is reached or there are no results left.
    pub async fn next(&mut self) -> Result<Option<InlineResult>, InvocationError> {
        if let Some(result) = self.next_raw() {
            return result;
        }

        let tl::enums::messages::BotResults::Results(tl::types::messages::BotResults {
            query_id,
            next_offset,
            results,
            ..
        }) = dbg!(self.client.invoke(&self.request).await?);

        if let Some(offset) = next_offset {
            self.request.offset = offset;
        } else {
            self.last_chunk = true;
        }

        let client = self.client.clone();
        self.buffer
            .extend(results.into_iter().map(|r| InlineResult {
                client: client.clone(),
                query_id,
                result: r,
            }));

        Ok(self.pop_item())
    }
}

/// Method implementations related to dealing with bots.
impl Client {
    /// Perform an inline query to the specified bot.
    ///
    /// The query text may be empty.
    ///
    /// The return value is used like any other async iterator, by repeatedly calling `next`.
    ///
    /// Executing the query will fail if the input chat does not actually represent a bot account
    /// supporting inline mode.
    ///
    /// # Examples
    ///
    /// ```
    /// # async fn f(bot: grammers_client::types::User, client: grammers_client::Client) -> Result<(), Box<dyn std::error::Error>> {
    /// // This is equivalent to writing `@bot inline query` in a Telegram app.
    /// let mut inline_results = client.inline_query(&bot, "inline query");
    ///
    /// while let Some(result) = inline_results.next().await? {
    ///     println!("{}", result.title().unwrap());
    /// }
    /// # Ok(())
    /// # }
    /// ```
    pub fn inline_query<C: Into<PackedChat>>(&self, bot: C, query: &str) -> InlineResultIter {
        InlineResultIter::new(self, bot.into(), query)
    }

    pub async fn edit_inline_message<M: Into<InputMessage>>(
        &self,
        message_id: tl::enums::InputBotInlineMessageId,
        input_message: M,
    ) -> Result<bool, InvocationError> {
        let message: InputMessage = input_message.into();
        let entities = parse_mention_entities(self, message.entities);
        let result = self
            .invoke(&tl::functions::messages::EditInlineBotMessage {
                id: message_id,
                message: Some(message.text),
                media: message.media,
                entities,
                no_webpage: !message.link_preview,
                reply_markup: message.reply_markup,
                invert_media: false,
            })
            .await?;
        Ok(result)
    }
}