mIRC Home    About    Download    Register    News    Help

Print Thread
#271281 05/02/23 04:07 AM
Joined: Jan 2022
Posts: 15
F
fusion9 Offline OP
Pikka bird
OP Offline
Pikka bird
F
Joined: Jan 2022
Posts: 15
Hello all,

I've searched and read everything I can find, and can't figure out to to read a single text string from the last line from a query window reply.

Use Case: challenge-response. I send a 4 letter random text string, and they reply with it in the same query window. I match them, and give the user voice. This keeps simple bots from getting voice in the channel.

I've looked at on Open, and using on Text, but this window is already open.

I'm reading about /filter -w[windowname] -k[aliasname] (using a window as the "infile" and passing it to an alias). I'm confused about the -r switch (range of lines to filter)., though I think that's what I need. Should that be -r1 or r2, or do I need to specify my own sort? I'm actually confused by all of it - lol. I feel like I'm making this way too hard.

Any ideas on this? Or a simple example? I just need that most recent line in a variable, and I'm golden.

Thanks in advance!

Joined: Jan 2004
Posts: 2,127
Hoopy frood
Offline
Hoopy frood
Joined: Jan 2004
Posts: 2,127
Well, you can find the Nth line of $query($nick) with $line( $nick,N) and $line($Nick,0) tells you how many lines there are in the window. So since you want the last line, you want N to be the total number of lines. So:

//echo -a $line( $nick , $line($nick,0) )

Note that this works only if there is at least 1 line of text in the window, or else this returns 0.

Here's another option you can do instead. If you're doing a random 4-digit number, I assume you're doing something like $rand(1000,9999). You can instead do something that changes the number every 30 seconds, makes it be a 9 digit number by default, and you can even make it nick-specific so it can't be used by the bot to invite all its friends to query you.

//echo -a $totp(any secret $nick)

The catch with this method is that this changes every time the clock goes into a different 30-second window, so if you give them the code with 1 second left, they don't have time to reply. But you can cut down on that by making the time window be larger. This example makes the number change every 900 seconds, aka 15 minutes.

//echo -a $totp(any secret $nick,$ctime,sha256,6,900)

You can make this be an hourly code by changing 900 to 3600 in all versions recognizing this identifier, and in the newest version you can make it be a code that changes every 48 hours. enjoy!

Joined: Jan 2022
Posts: 15
F
fusion9 Offline OP
Pikka bird
OP Offline
Pikka bird
F
Joined: Jan 2022
Posts: 15
I went a different way with this. It all works but I've painted myself into a corner.

The issue is I didn't know you can't nest ON events. When I try I get ON Unknown command.

I need to see if the reply text matched the original text, and tell the user if it doesn't match, and to try again. It needs to be done in the routine so it's not replying to everyone that sends me text that doesn't match!

Here's what I have so far:

Code
; this is all working. It needs to do something when the text doesn't match, and I'm not sure how.
on 1:MODE:#mychan:{

  if $1- == +m {

    //echo -a autovoice activated
    enable #autovoice
  }
  elseif $1- == -m {

    //echo -a autovoice deactivated
    disable #autovoice
  }
}

#autovoice on

on *:TEXT:voiceme:?: {
  var -g %currentnick = $nick
  //echo -a voiceme message received and matched
  ;random text - working
  var %i = 0
  var -g %mytext = ""
  var %newchar = ""
  while (%i <= 3) {
    %newchar = $rand(a,z)
    %mytext = %mytext $+ %newchar
    inc %i
  }

  ;send the message
  .msg $nick Please reply with this text and I will give you voice: %mytext
}

on *:TEXT:%mytext:?: {

  if $nick ison #mychan && $nick !isvoice #mychan {
    //echo -a nick in channel

    /mode #mychan +v $nick
    .msg $nick Congrats! You have voice (+v) and can chat in channel. 
  }
  else {
    //echo -a $nick not in channel
    .ignore -pu300 $nick
  }
}

#autovoice end

Joined: Jul 2006
Posts: 4,145
W
Hoopy frood
Offline
Hoopy frood
W
Joined: Jul 2006
Posts: 4,145
Hello, using on text is correct to access the user reply, the problem is that if you listen only for one specific random 4 letters (a single %variable), it only works for one user at a time.
To access the last line of a query window outside on text, you would use $line() but i don't think it's relevant here.

In any case it would probably be better to make the length of the password longer to avoid people getting lucky, or worse, brute forcing you (see %length in code).

To allow it to work for multiple users at the same time with different passwords, you'll need to store data per nickname

Code
on *:TEXT:voiceme:?: {
  var %i = 0,%length 3,%mytext 
  while (%i <= %length) {
    %mytext = %mytext $+ $rand(a,z)
    inc %i
  }
  .msg $nick Please reply with this text and I will give you voice: %mytext
  set %botvoiceme $+ $nick %mytext
}

on *:TEXT:*:?: {
  if ($nick ison #mychan) {
    if ($eval($+(%,botvoiceme,$nick),2) == $1-) {
       mode #mychan +v $nick 
       unset %botvoiceme $+ $nick
       .msg $nick Congrats! You have voice (+v) and can chat in channel. 
    }
  }
  else {
    //echo -a $nick not in channel
    .ignore -pu300 $nick
  }
}
Something like that, it uses dynamic variables: https://en.wikichip.org/wiki/mirc/variables#Dynamic_Variable_Names

Atm the variables to store the data are global and only destroyed when the user complete the action, if he doesn't or if the bot disconnect in the middle, the variable will be still there and it would be possible to complete the action at any time in the future, you would want to maybe make the variable destroy themselves after a few seconds with set -u60 %botvoicele $+ $nick %mytext for 60 seconds, for example.


#mircscripting @ irc.swiftirc.net == the best mIRC help channel
Joined: Jan 2004
Posts: 2,127
Hoopy frood
Offline
Hoopy frood
Joined: Jan 2004
Posts: 2,127
An obvious problem I see with your method is that if 2 nicks join the channel close together, the 2nd nick joining causes %mytext to change between the message seen by the 1st nick, causing them to give theh 'wrong' answer.

This example is what I mentioned in my earlier post. You just create a global variable in the editbox:

/set %voicepassword Anything Can Go Here

And it will return a 6-digit number code that is different for each nick, and changes each day at the same time.

Since there are 10^6 possible numbers of this length, it's literally a 1 in a million chance that someone can guess the right number. You can change the '6' to a different number of digits depending on how hard it should be to guess the number, or how easy you want to make it for someone to input the number. It seems like you're wanting to make this easy for people to voice themselves, and I think you'll find that, for the people who are not simply pasting the text back into the query window, it's easier for someone to type a 6-digit number than 4 random letters.

Since you don't need to share the password with anyone, you can change the password any time you wish, and nobody would notice, except that their daily number would change immediately. Someone who knows anyone else's password for any day, or knowing their password for any day other than today would not help them to know what today's password is.

In my codenumber example, it's changing the code daily and at midnight, so if you want the code to change more frequently, change the 86400 to the new number of seconds in the interval. i.e. changing 86400 to 3600 makes it change hourly. If you want the code to change at 14:00 each day, then change the 0*3600 to 14*3600.

Since you don't really care what the password is, I've also included an ON START command that changes the voice password each time you restart mIRC, and an ON CONNECT command that changes the password each time you connect to a specific Network name (change to the correct word). You can alternatively put the SET command into the perform-on-connect settings for the appropriate network.

Setting the password like this will make it hard for someone to guess the password that allows them to know the code number, and changing it like this means it wouldn't work once the password changes. Of course it does sound like you realize that this really isn't a defense against a bot being able to script something to handle this challenge. To handle something like that, you can have a list of challenge question and answers, but that will make things a lot more complicated.

Code
ON *:START:{ set %voicepassword $regsubex($str(x,50),/x/g,$rand(a,z)) }
ON *:CONNECT:{ if ($network == Change_Me) set %voicepassword $regsubex($str(x,50),/x/g,$rand(a,z)) }

alias voicepassword  { return $hotp(%voicepassword $$1, $calc( ($ctime - $timezone + 0*3600) // 86400),sha256,6) }

on *:TEXT:voiceme:?: {
  ;send the message
  .msg $nick Please reply with this text and I will give you voice: voiceme $voicepassword($nick)
}

on *:TEXT:voiceme *:?: {
  if $nick ison #mychan && $nick !isvoice #mychan {

    if ($1 == $voicepassword($nick)) {
      //echo -a nick in channel

      /mode #mychan +v $nick
      .msg $nick Congrats! You have voice (+v) and can chat in channel. 
    }
    else {
      echo -a here is what to do when they give the wrong number
    }

  }
  else {
    //echo -a $nick not in channel
    .ignore -pu300 $nick
  }
}

Joined: Jan 2022
Posts: 15
F
fusion9 Offline OP
Pikka bird
OP Offline
Pikka bird
F
Joined: Jan 2022
Posts: 15
That first one looks great to me.. I suppose I need to loop with a timer to wait for a response, with a short delay between loops, then match the contents of the last line. There should only be two lines for this, then if they get it wrong, three lines, and so on. I could test on the number of lines to see the change.

I wonder how I missed $line.. that seems too easy smile

I could not get /filter to work, and my 2nd version is much more simple, I think. I think the window wasn't available when the filter line ran..

Thanks for the tips!

Joined: Jan 2022
Posts: 15
F
fusion9 Offline OP
Pikka bird
OP Offline
Pikka bird
F
Joined: Jan 2022
Posts: 15
Thank you! I was going to work on storing the values after I figured out how to match the response and reply if they get it wrong.

I'm going to have to study this line and add another if clause, I think, and not do the unset if they need to try again.

Code
if ($eval($+(%,botvoiceme,$nick),2) == $1-) {

I also wonder if there's a way to only invoke that 2nd On TEXT if it's a response to the question, and not fire on every message I receive. Maybe check for the existence of the %botvoiceme variable outside the 2nd On TEXT?

The 60 second timeout is a great tip. Thanks!

Joined: Jan 2022
Posts: 15
F
fusion9 Offline OP
Pikka bird
OP Offline
Pikka bird
F
Joined: Jan 2022
Posts: 15
This works very well - I just had to change the $1 to $2 in the line below:

Code
($1 == $voicepassword($nick))

I've added some other checks, and am working on the messaging if they don't get it right the first time. I think I'll give them three tries, then put them on ignore for a few minutes. I like how you got around the ON TEXT issue by adding the voiceme * to the matchtext. That keeps other private messages from triggering it perfectly.

Originally Posted by maroon
Code
ON *:START:{ set %voicepassword $regsubex($str(x,50),/x/g,$rand(a,z)) }
ON *:CONNECT:{ if ($network == Change_Me) set %voicepassword $regsubex($str(x,50),/x/g,$rand(a,z)) }

alias voicepassword  { return $hotp(%voicepassword $$1, $calc( ($ctime - $timezone + 0*3600) // 86400),sha256,6) }

on *:TEXT:voiceme:?: {
  ;send the message
  .msg $nick Please reply with this text and I will give you voice: voiceme $voicepassword($nick)
}

on *:TEXT:voiceme *:?: {
  if $nick ison #mychan && $nick !isvoice #mychan {

    if ($1 == $voicepassword($nick)) {
      //echo -a nick in channel

      /mode #mychan +v $nick
      .msg $nick Congrats! You have voice (+v) and can chat in channel. 
    }
    else {
      echo -a here is what to do when they give the wrong number
    }

  }
  else {
    //echo -a $nick not in channel
    .ignore -pu300 $nick
  }
}

Last edited by fusion9; 06/02/23 03:03 AM. Reason: removed a bad assumption (that I just tested)
Joined: Jan 2004
Posts: 2,127
Hoopy frood
Offline
Hoopy frood
Joined: Jan 2004
Posts: 2,127
If I understand what you're saying now, you want to just have them reply the number without the 'voiceme' in front of that. If so, that makes it more complicated for your script, and also introduces the chance of a false positive match.

Without the 'voiceme' in front of it, you'll need to check all incoming query messages to see if any of them consist entirely of a 6 digit number. And you'll also need to check for even shorter numbers, because even though 10% of the time it will be a number with at least 1 leading zero, some people might just type the number without the leading zero(es).

But if you happen to be having an unrelated conversation with someone else, and they happen to type a numeric message such as a yymmdd style date, that could trigger this script to see that someone outside the channel is sending you a number, and your script is now styled to respond to that by putting them on your ignore list.

It's already pretty simple, because your text is showing up in their query window with you, so all they need to do is paste the line right back to you, so you can just have it be something like

.msg $nick Please reply by typing or pasting this text in this window and I will give you voice: voiceme $voicepassword($nick)

If you want to give them 3 chances, an easy way to do that is to create a hashtable containing their fail count. something like:

//hinc -mu10 voicefails $+($network,.,$nick)

... which will either create the item with value of 1, or will increment the prior value. The u10 makes the item go away after 10 seconds, which avoids the need to delete the item after they have success

You can then check their current count by checking $hget( voicefails, $+($network,.,$nick) )

Once they succeed, if you need to delete their item,

/hdel voicefails $+($network,.,$nick)

But the /hdel command halts your script if you try to delete something from a table that doesn't exist.

Also something I hadn't mentioned, was that this is keying off their nick, so someone who changes nick after they get their code will only have the 1 in a million chance that their code works. But at least with the new nick they get their 3 guesses

Joined: Jan 2022
Posts: 15
F
fusion9 Offline OP
Pikka bird
OP Offline
Pikka bird
F
Joined: Jan 2022
Posts: 15
Here's the final status of "version 1", in case anyone is looking for something like this. Please let me know if you see any problems, issues, etc.

Thanks to everyone that replied - I've learned a LOT from all of you. I also learned to be careful to not use the tab key in the mIRC editor, after chasing the "unknown command" error message for a few hours. I thought my nested if statement was causing it - I must have matched braces 30 times smile

Code
; ***** autovoice: allows users to grab +v when the channel is set to +m (moderated). *****
; 1. This ONLY works in ONE channel at a time. BE SURE to set your own channel name throughout (#mychan by default)!
; 2. The autovoice function Automatically Enables when the channel mode changes to +m, and automatically disables on -m. 
; 3. To turn off the auto enable feature, type /disable #autovoice. To turn it back on, type /enable #autovoice, 
;or right-click in the channel and use the Menu. It's best to leave it on, unless there are errors/problems.
; 4. To immediately change the password, type /voicepassword - any requests in progress will fail due to the new password. 

ON *:START:{ set %voicepassword $regsubex($str(x,50),/x/g,$rand(a,z)) }
ON *:CONNECT:{ if ($network == Undernet) set %voicepassword $regsubex($str(x,50),/x/g,$rand(a,z)) }

alias voicepassword  { 
  return $hotp(%voicepassword $$1, $calc( ($ctime - $timezone + 0*3600) // 3600 ),sha256,6) 
}

menu channel {
  AutoVoice
  .Enable AutoEnable:/enable #autovoice
  .Disable AutoEnable:/disable #autovoice
  .Generate New Passsword: /voicepassword
  -
  .set channel +m:/mode # +m
  .set channel -m:/mode # -m
}

on 1:MODE:#mychan:{

  if $1- == +m {

    ; creates a timer to send a message to the channel about getting voice. Set to 5 minutes. Change the 300 seconds to change the interval. 
    /timerMod 0 300 //msg #mychan The channel is being moderated. To get Voice (+nickname) to talk in channel, please send a message to me, with voiceme as the only text (copy and paste is easiest): /msg $me voiceme
    ; executes the timer immediately
    /timerMod -e 

    enable #autovoice
    ; creates a fresh password - see above
    /voicepassword
  }
  elseif $1- == -m {

    disable #autovoice
    /msg #mychan Channel Moderation is now off. Everyone can chat again :)
    ; turns off the message timer
    /timerMod off
  }
}

#autovoice off

;receive the intial voiceme message, do checks, send the password string

on *:TEXT:voiceme:?: {

  ;//echo -s request received
  if $nick ison #mychan && $nick !isvoice #mychan {
    ;//echo -s is on the channel, not voice
    .privmsg $nick Please reply with this text and I will give you voice ( copy and paste for best results. Please include: voiceme xxxxxx ): voiceme $voicepassword($nick)

  }

  elseif $nick ison #mychan && $nick isvoice #mychan {

    .privmsg $nick You already have voice! Your messages are being ignored for 5 minutes. 
    .ignore -pu300 $nick
    /closemsg $nick
  }

  else {
    ;//echo -s not in channel
    .privmsg $nick You are not in channel and will be ignored for 5 minutes.
    .ignore -pu300 $nick 
    .closemsg $nick  
  } ;close if
} ;close on text

on *:TEXT:voiceme *:?: {

  if $nick ison #mychan && $nick !isvoice #mychan {
    ;outer if
    ;//echo -s $1 $2 
    ;//echo -s nick in channel and not voiced 
    ;//echo -s $voicepassword($nick)

    if ($2 == $strip($voicepassword($nick))) {
      ;inner if
      ;//echo -s should have voice here
      .mode #mychan +v $nick

      .privmsg $nick Congrats! You have voice +v and can chat in channel.
      .closemsg $nick 

    } 

    else {      
      ;inner if
      .privmsg $nick Sorry, that didn't match. Please copy and paste voiceme and the numbers: voiceme xxxxxx
      .closemsg $nick

    } ;close inner if
  }
  elseif $nick isvoice #mychan {
    ;back to outer if
    .privmsg $nick You silly goose you already have voice! Your messages are being ignored for 5 minutes. 
    .ignore -pu300 $nick
    .closemsg $nick
  }

  else {
    ;outer if
    //echo -s $nick not in channel
    .closemsg $nick
    .ignore -pu300 $nick 
  } ;close outer if

}  ;close ON TEXT
}
}

#autovoice end


Joined: Jan 2004
Posts: 2,127
Hoopy frood
Offline
Hoopy frood
Joined: Jan 2004
Posts: 2,127
Few minor things.

1. Change

from
on 1:MODE...
to
on *:MODE...

As you saw how I changed your 1:TEXT to *:TEXT, this avoids rare cases where someone can be put into a 'level' that matches * but doesn't match '1'

2. Instead of having a compound if() to check both items, you can use $nick(channel,$nick,included,[excluded]) to check both things.

From
if $nick ison #mychan && $nick !isvoice #mychan
To
if ($nick(#mychan,$nick,a,v))

The 'a' means 'all', so this finds them in a version of the nicklist where anyone having voice isn't present, returns 0 if they're not on that altered list, or returns N if they're the Nth on that list.

3. Likewise,

From
elseif $nick ison #mychan && $nick isvoice #mychan
To
elseif ($nick(#mychan,$nick))

... matches anyone who is on the list. For this case you don't need the 'isvoice' because this is an elseif condition, and the only time this is reached is when someone having voice in the channel does this.

4. If the #2 setting needed to see only the people who were not voice-and-above, you can use 'r' to see only those without any status, $nick(#mychan,$nick,r). But I didnt do that, because you don't want to ignore an @op in the elseif condition because they did a !voiceme.

In fact, you may want to have the script just ignore the 'voiceme' from anyone who's already got a better status by putting at the top:

if ($nick(#mychan,$nick,a,rv)) return

Note this isn't the same as $nick(#mychan,$nick,o) since it's possible in some networks for someone to have & or ~ status without also having @

5. If you don't want the /enable or /disable commands to generate a message in the status window, you can use the silencing dot, like use used for .privmsg

.enable #autovoice
.disable #autovoice

6. There are alternatives to using /enable and /disable, depending on how you use them. Each time you open the scripts editor, any global %variable that's been created gets written to vars.ini, which is fine. However it also triggers a diskwrite of the script file if a line has been changed by the /enable or /disable command, which can sometimes be confusing "why is my scriptfile changing if I didn't touch it..."

A cheater way to create a setting is to use /ialmark to create an ialmark against your own nick or anyone else's nick. It travels to the new nick when it changes nick, and only goes away when you no longer share any channels with that nick. Which includes going away when you leave your last channel, or you disconnect, or you /ialclear

Think of /ialmark as a hashtable attached to each nick in the $ial(), which is a separate list for each network.

//echo -a result: $ialmark($me,autovoice).mark | ialmark -n $me autovoice on | echo -a result: $ialmark($me,autovoice).mark | ialmark -n $me autovoice off | echo -a result: $ialmark($me,autovoice).mark | ialmark -rn $me autovoice | echo -a result: $ialmark($me,autovoice).mark

Instead of enabling in a way that changes the scriptfile with
/enable #autovoice
you can just set an ialmark against your own nick:
//ialmark -n $me autovoice on
(can use the silencing dot too)
You'd then disable, instead of
/disable #autovoice
you would either remove the ialmark
//ialmark -rn $me autovoice
or
//ialmark -n $me autovoice off

Then, since the #group is not there to hide the code when it's disabled, you could have as the 1st line of each of your ON TEXT events:

if ($ialmark($me,autovoice).mark != ON) return

Note that my example assumes just 1 channel is being autovoiced, as otherwise you'd probably want an ialmark-name of something like $+($network,$chan)

7. The slashes in /command or //command are needed in the editbox but are ignored in scripts, though I sometimes leave them there to make it easier to copypaste them to the editbox.

Joined: Jan 2004
Posts: 2,127
Hoopy frood
Offline
Hoopy frood
Joined: Jan 2004
Posts: 2,127
Since it sounds like you're fresh into scripting, here's a few tips.

1. Wikichip has a little more detail and examples as a supplement for F1 help

https://en.wikichip.org/wiki/mirc

Especially if you're going to use timers in your script, be sure to read the link on the right margin about injection

2. In scripts editor, make sure that options/ 'identifier warning' is enabled. It causes $spellingmistake to generate an error so you can fix your script instead of having the fake identifier being $null, which can make it hard to debut your script

3. By default, messages in the status window have "-" lines between them, which can make things be spread out. If you want to get rid of those extra rows, you can change options/other "line separator" to be blank

4. If you want to make it so that you can paste things into the editbox without risking them getting messaged to channel as soon as you paste them, without pressing <enter> -- change options/display/editbox lines to 'automatic'. This can be most commonly caused by the behavior of marking text in the script editor. In most programs, pressing Shift+End highlights through the end of the line without also highlighting the $crlf at the end of the line, but in the script editor the $crlf would be in the clipboard. So if you don't want the $crlf to be clipboarded, you can follow the Shift+End with Shift+LeftArrow

5. Once you start getting a lot of scripts, here's a couple of aliases that can come in handy.

https://mircscripts.info/?page=project&id=5kLCC

The /srch alias lets you find a wildcard or regex string by searching all files in the [remote] and [aliases] tabs. While mIRC does have something to search all files, it searches only-the-remotes or only-the-aliases, and it can't find anything in the same file that's on a row above your cursor. Plus, if you have a lot of matches in a lot of files, it can be useful to see a concise listing of the rows where they're found. You can find all scripts where you have an on-text event like:

/srch * :TEXT:

While the editor has something to warn about { mismatch }, it doesn't do the same thing for ((parenthesis) or [[braces]

It defaults to doing wildcard searches, so if you want to find only ON TEXT events having :?: and not :*: or :#channe:, it would be

/srch -r * /:TEXT:.*:\?:/i

Since regex defaults as case-sensitive, I used the /i flag there to make it insensitive.

This will find any tab characters you may still have in a script:

//srch * $chr(9)

The /pairs alias hunts for rows in all scripts where the open/close for parenthesis or braces isn't the same. It's not perfect, as out of an abundance of caution it's triggered by lines containing a smiley containing the unmatched parenthesis and isn't triggered if they're backwards. So lines with unmatched:

/pairs ( ) *
/pairs [ ] *

6. Also at the script site is the font for Fixedsys Excelsior. Many people consider it horrible as a channel font, while others liked it because it had more sizes than normal Fixedsys. But 1 thing it has going for it is that using it as a script editor font lets you see the Ctrl+K color symbol as [c], and same for the ^B and ^O symbols being [b] and [o].

Unfortunately, the ^U ^E ^I codes for underline/strikethru/italic continue to be zero-width invisibles, and the ^R reverse isn't helpful either, and nobody has volunteered to come up with a font that gives a special display for the rest of these controlcodes.

7. You can find scripting help in the #mirc and #mircscripting channels at some of the networks in the new favorites list, though sometimes you might need to wait a while since not everyone is watching 24x7


Link Copied to Clipboard