aatxe / irc

the irc crate – usable, async IRC for Rust
Mozilla Public License 2.0
525 stars 100 forks source link

Sending tags with messages #250

Closed sadjy closed 1 year ago

sadjy commented 1 year ago

Hi, first I'd like to say this is not an "issue", I'm asking a question/insights. Also I'm sorry if this has already been asked. I tried the research function but yielded no results. I'm a novice dev so please bare with me.

I'm trying to code my own custom bot to interact with Twitch IRC servers. I'm at the point where I'd like to leverage this functionality that requires to send a message containing a custom tag reply-parent-msg-id as documented here: https://dev.twitch.tv/docs/irc/send-receive-messages/#replying-to-a-chat-message.

I see the Client type has a send command allowing to send a Command. However, I haven't seen a function allowing me to craft a Command with custom tags in the Command docs. PRIVMSG does not do the trick as there is no way to add prefixes.

I also considered crafting a Message using this function but I don't seem to be able to use it with the send function or any other client method.

Would you have any insight on how I can accomplish sending custom tags? Thank you!

aatxe commented 1 year ago

The last approach that you identified is the right one: you should be constructing a Message with the appropriate tags you'd like to send, and sending it using send. Although the documentation says it sends a Command, the type signature shows that it actually sends anything that implements Into<Message> (meaning anything that defines a conversion into Message). There is a reflexive implementation of Into, so Into<T> is implemented for all types T. If that for some reason is not working, you should post the code and the associated errors and I can look into it because that would be a bug.

sadjy commented 1 year ago

Hey @aatxe, thank you for your response! That worked. This is a piece of my code:

            \\ Grabbing message id I should replay to
            let id = message.tags.as_ref().and_then(|tags| {
                tags.iter()
                    .find(|tag| tag.0 == "id")
                    .and_then(|tag| tag.1.clone())
            });
            \\ Creating the tag my custom Message should contain
            let tags = Some(vec![Tag("reply-parent-msg-id".to_string(), id)]);
            // let prefix = match message.prefix.clone().unwrap() {
            //     Prefix::ServerName(servername) => servername,
            //     Prefix::Nickname(nickname, username, hostname) => {
            //         format!("{}!{}@{}", nickname, username, hostname)
            //     }
            // };
            let prefix = Some("tmi.twitch.tv");
            let payload = String::from("Test.");
            \\ Crafting custom Message
            let message = Message::with_tags(
                tags,
                prefix,
                "PRIVMSG",
                vec![channel, &payload], // channel is a &str "sadjyeyo"
            );
            warn!("{:?}", message);

            if let Err(e) = client.send_privmsg(channel, format!("@{} {}", name, payload)) {
                error!("Failed to send reply: {:?}", e); //A
            }

            if let Err(e) = client.send(Command::PRIVMSG(channel.to_string(), payload)) {
                error!("Failed to send reply: {:?}", e); //B
            }

            if let Err(e) = client.send::<Message>(message.unwrap()) {
                error!("Failed to send reply: {:?}", e); //C
            }

I'm saying that "it works" because technically the //C part of the code does send the message (I get no errors), but unfortunately it doesn't display in the chat, unlike //A and //B .

Here what a generic Message I would type in the chat looks like:

Message { tags: Some([Tag("badge-info", Some("subscriber/2")), Tag("badges", Some("broadcaster/1,subscriber/0")), Tag("client-nonce", Some("aaae1ed4b8e87f0673479ecceca6f5bc")), Tag("color", Some("#9ACD32")), Tag("display-name", Some("Sadjyeyo")), Tag("emotes", Some("")), Tag("first-msg", Some("0")), Tag("flags", Some("")), Tag("id", Some("8ac51ec3-d9b4-4ecc-9834-fa1cf9a075c9")), Tag("mod", Some("0")), Tag("returning-chatter", Some("0")), Tag("room-id", Some("239025460")), Tag("subscriber", Some("1")), Tag("tmi-sent-ts", Some("1684334863332")), Tag("turbo", Some("0")), Tag("user-id", Some("239025460")), Tag("user-type", Some(""))]), prefix: Some(Nickname("sadjyeyo", "sadjyeyo", "sadjyeyo.tmi.twitch.tv")), command: PRIVMSG("#sadjyeyo", "This is a message") }

Here is what my crafted reply Message looks like:

As far as this repo is concerned, my "issue" is fixed! Thank you for your help! If you have any insight as to why this is not going through, I'm all ears!

sadjy commented 1 year ago

I figured it that latter part out: In the RFC it says:

Clients SHOULD NOT use a prefix when sending a
message; if they use one, the only valid prefix is the registered
nickname associated with the client.

https://datatracker.ietf.org/doc/html/rfc2812

So:

  let message = Message::with_tags(
                tags,
                None,
                "PRIVMSG",
                vec![channel, &payload], // channel is a &str "sadjyeyo"
            );

Did the trick!

aatxe commented 1 year ago

Right, yeah, we support the prefixes because the library has to be able to make them available when they get parsed coming from a server, but they're not something that clients are expected to use and so servers might ignore and overwrite them or might (evidently) go as far as completely dropping the messages altogether.