mIRC Home    About    Download    Register    News    Help

Print Thread
[twitch] nested @commands@ #255966 08/12/15 04:29 PM
Joined: Aug 2003
Posts: 325
W
Wolfie Offline OP
Fjord artisan
OP Offline
Fjord artisan
W
Joined: Aug 2003
Posts: 325
Okay simple problem, not sure how to handle it. The idea is similar to how some other bots are doing this, in this instance, DeepBot. I'm trying to simulate the ability to do @command@[value] but with the ability to handle nested commands. For example, I created a @lower@[] command and there is also the ability to do @target@[1] (where it returns only the first token of text from anything extra typed). So, @lower@[@target@[1]] should return the first token of text but in lowercase. The idea is simple enough, handle the inner most []'s first and work outward, but the problem is, how do I go about doing that?

I know that I could have it parse @target@'s first, but that addresses a symptom and not the problem, because it could be something else that needs to be parsed first or could be flip-flop order, etc.

I'm sure that regex is necessary to do this and I'm using that already to find the use of @commands@, but the way it's crapping out on me is if I have @command@[@othercommand@[value]] because the second ']' is chopped off. Can't exactly have it grab until the last ] because that'll cause problems too.

Any ideas?

Re: [twitch] nested @commands@ [Re: Wolfie] #255974 08/12/15 09:42 PM
Joined: Jan 2004
Posts: 1,274
L
Loki12583 Offline
Hoopy frood
Offline
Hoopy frood
L
Joined: Jan 2004
Posts: 1,274
Taking in user input in such a way can be dangerous, I had to use the $safe alias to guard against injection when evaluating the subcommand. Make sure to understand everything on this page if you're handling unknown content: http://en.wikichip.org/wiki/mirc/msl_injection

Here I've used a regex which captures a command and its arguments, if the arguments also match the regex I make a recursive call. The regex is designed such that empty or missing brackets are also acceptable: @command@ or @command@[]. Limiting character classes may help guard against invalid input depending on your preference. Reading back over your examples maybe I've misunderstood that subsequent text should be evaluated rather than the arguments in [], but you'll need to provide a more exact syntax if that's the case. I think trying to allow both would create an ambiguous syntax or complicate the code on a per-command basis.

Some work could be done in regard to $isid but that's a general issue and does not deal directly with the problem at hand (recursion).

This will embolden and underline text:
//echo -ag $processcommand(@bold@[@underline@[some @value@[] $chr(91) $chr(93) $!pi $($pi %foo,0) with pi]])

This will beep and echo text:
//noop $processcommand(@echo@[@beep@])

This will echo text as per your described commands:
//noop $processcommand(@echo@[@lower@[@target@[1] ONE TWO THREE]])

Code:
alias processcommand {
  var %regex = /^@(\S+?)@(?:\[(.*)\])?(.*)$/iS

  if (!$regex($1-,%regex)) return

  var %command = $regml(1)
  var %args = $regml(2)
  var %text = $regml(3)

  if (%args != $null) && ($regex(%args,%regex)) {
    %args = $recurse($!processcommand( $safe(%args) ))
  }

  if (%command == lower) {
    return $lower(%args)
  }
  if (%command == target) {
    return $gettok(%text,%args,32)
  }
  elseif (%command == bold) {
    return  $+ %args
  }
  elseif (%command == underline) {
    return  $+ %args
  }
  elseif (%command == beep) {
    beep | return beep
  }
  elseif (%command == echo) {
    echo -ag %args | return echo
  }

}

alias recurse { if ($isid) return $($1-,2) | else $1- }
alias safe return $!decode( $encode($1,m) ,m)

Re: [twitch] nested @commands@ [Re: Loki12583] #255976 09/12/15 12:03 AM
Joined: Aug 2003
Posts: 325
W
Wolfie Offline OP
Fjord artisan
OP Offline
Fjord artisan
W
Joined: Aug 2003
Posts: 325
Originally Posted By: Loki12583
Taking in user input in such a way can be dangerous, I had to use the $safe alias to guard against injection when evaluating the subcommand. Make sure to understand everything on this page if you're handling unknown content: http://en.wikichip.org/wiki/mirc/msl_injection

Here I've used a regex which captures a command and its arguments, if the arguments also match the regex I make a recursive call. The regex is designed such that empty or missing brackets are also acceptable: @command@ or @command@[]. Limiting character classes may help guard against invalid input depending on your preference. Reading back over your examples maybe I've misunderstood that subsequent text should be evaluated rather than the arguments in [], but you'll need to provide a more exact syntax if that's the case. I think trying to allow both would create an ambiguous syntax or complicate the code on a per-command basis.
Okay to clear it up some, let's say I make a command called !kick where you specify a name and then the bot does "/me kicks (name)" The command would be "/me kicks @target@[1]" and @target@[1] would be replaced by the first 'token' of input after the command. (Tokens being items separated by spaces.) Commands wouldn't be evaluated items, thus injections wouldn't really be a concern.


Originally Posted By: Loki12583
Code:
alias processcommand {
  var %regex = /^@(\S+?)@(?:\[(.*)\])?(.*)$/iS

  if (!$regex($1-,%regex)) return

  var %command = $regml(1)
  var %args = $regml(2)
  var %text = $regml(3)

  if (%args != $null) && ($regex(%args,%regex)) {
    %args = $recurse($!processcommand( $safe(%args) ))
  }

  if (%command == lower) {
    return $lower(%args)
  }
  if (%command == target) {
    return $gettok(%text,%args,32)
  }
  elseif (%command == bold) {
    return  $+ %args
  }
  elseif (%command == underline) {
    return  $+ %args
  }
  elseif (%command == beep) {
    beep | return beep
  }
  elseif (%command == echo) {
    echo -ag %args | return echo
  }

}

alias recurse { if ($isid) return $($1-,2) | else $1- }
alias safe return $!decode( $encode($1,m) ,m)
For the commands I was using aliases (to add a new command, just make a new alias). The 'trick' is in trying to make it so it parses the deepest first and works its way out, but without parsing stuff that's either meant to be 'as is' (like stuff in quotes) or stuff that is the result of parsing. Of course, maybe that's getting a bit complex.

Here's what I'm using at the moment...
Code:
alias twitch.cmd.process@ {
  if ( !$0 || !$regex($1-,/(@[^@\s]+@)/g ) ) { return $1- }
  var %return = $1-, %tokens, %break = 25
  var %regexes = /(@[^@\s]+@(?:\[("?)[^\[\]]+\2\]))/gą/(@[^@\s]+@)/g

  while ( %regexes ) {
    while ( $regex( %return, $gettok( %regexes, 1, 177 ) ) ) {
      dec %break
      if ( %break < 1 ) { echo -tgs $scriptline BREAK $1- | break }
      %return = $twitch.cmd.@subprocess(%return)
    }
    %regexes = $deltok(%regexes,1,177)
  }
  return %return
}
alias twitch.cmd.@subprocess {
  var %x = $regml(0), %tokens, %return = $1-

  while ( %x > 0 ) {
    if ( $len($regml(%x)) > 2 ) { %tokens = $addtok(%tokens,$+($regml(%x).pos,:,$regml(%x)),1) }
    dec %x
  }

  while ( %tokens ) {
    var %x = $gettok(%tokens,1,1), %var = $deltok(%x,1,58), %tokens = $deltok(%tokens,1,1), %pos = $calc( $gettok(%x,1,58) - 1 )
    var %reg = $regex(%var,/^(@[^@\s]+@)(?:\[("?)([^\[\]]+)\2\])?$/), %cmd = $+(twitch.val.,$regml(1))

    if ( $isalias(%cmd) ) { %cmd $iif($regml(3),$twitch.cmd.process@( $ifmatch ) ) }
    else { twitch.val.catchall@ %var }

    if ( $len($result) ) {
      var %new = $result, %offset = $calc( %pos + $len(%var) + 1 )
      var %return = $+($left(%return,%pos),%new,$mid(%return,%offset))
    }
  }
  return %return
}

alias twitch.val.@target@ { return [ [ $+(%,target,$iif($int($1),$ifmatch)) ] ] }
alias twitch.val.@lower@ { return $lower($1-) }
alias twitch.val.@upper@ { return $upper($1-) }

The original regex's I had weren't working right, what I've pasted above seems to be working SO FAR, but I'm sure it can be fine tuned some and I'm sure there's a better way of accomplishing my goal.


Just to mention this, but twitch doesn't support bold/underline/inverse/color codes, they get stripped (server side, not client). I know your code was just to show an example, but thought I'd mention it as a tidbit of info. Could be useful since on the client side, any codes will still echo properly, just won't make it past the server.

Re: [twitch] nested @commands@ [Re: Wolfie] #255978 09/12/15 12:32 AM
Joined: Jan 2004
Posts: 1,274
L
Loki12583 Offline
Hoopy frood
Offline
Hoopy frood
L
Joined: Jan 2004
Posts: 1,274
Please give examples of some complex lines to test against

Re: [twitch] nested @commands@ [Re: Loki12583] #255979 09/12/15 12:36 AM
Joined: Aug 2003
Posts: 325
W
Wolfie Offline OP
Fjord artisan
OP Offline
Fjord artisan
W
Joined: Aug 2003
Posts: 325
Originally Posted By: Loki12583
Please give examples of some complex lines to test against
Well none that are 'legit' at the moment, but in anticipation of the need, I made one that is a bit of fluff/bloat to purposely toy with it.

Code:
@target@[1] is another streamer and everyone should go give a follow! @profile@[@lower@[@upper@[@target@[1]]]]

Re: [twitch] nested @commands@ [Re: Wolfie] #255981 09/12/15 01:02 AM
Joined: Jan 2004
Posts: 1,274
L
Loki12583 Offline
Hoopy frood
Offline
Hoopy frood
L
Joined: Jan 2004
Posts: 1,274
I don't see what @target@[1] is supposed to be replaced by in that example.

It seems you're trying to replace values multiple times in a single line, I think this may best be done outside of the main replacement regex. Identify all instances of commands and send those each to the replacement alias. This new regex makes use of the recursive pattern (?R) to capture properly matched brackets, which it performs well on. Improperly matched brackets it falls apart, so that could use some work.

Aside from the @target@ command which differs in its syntax I have added to the script below. It now uses $isalias as you have done and I have made a direct semi-recursive call to avoid working around injection.

Code:
alias test {
  tokenize 32 here is $!pi some @upper@[@bold@[@pi@]] right here noop @underline@[@pi@]
  var %regex = /(@.+?@\[(?:[^\[\]]|(?R))*\])/g

  echo -ag $regsubex(recurse,$1-,%regex,$twitch.sub($regml(recurse,\n)))
}

alias twitch.sub {
  var %regex = /^@(\S+?)@(?:\[(.*?)\])?$/iS

  if (!$regex($1-,%regex)) return

  var %command = $regml(1)
  var %args = $regml(2)

  if (%args != $null) && ($regex(%args ,%regex)) {
    %args = $twitch.sub.recurse(%args)
  }

  %command = twitch.val. $+ %command

  if ($isalias(%command)) {
    %command %args
    return $result
  }
  else return $1-
}

alias twitch.sub.recurse return $twitch.sub($1-)

alias twitch.val.upper return $upper($1-)
alias twitch.val.underline return  $+ $1- $+ 
alias twitch.val.bold return  $+ $1- $+ 
alias twitch.val.pi return $pi

Last edited by Loki12583; 09/12/15 03:02 AM.
Re: [twitch] nested @commands@ [Re: Loki12583] #255988 09/12/15 05:00 AM
Joined: Aug 2003
Posts: 325
W
Wolfie Offline OP
Fjord artisan
OP Offline
Fjord artisan
W
Joined: Aug 2003
Posts: 325
@target@ and @target@[#] reflect the user input. Using the example I provided you (which was purposely bloated for testing purposes), let's say the command is named !profile
!profile SomeUserName
should produce
SomeUserName is another streamer and everyone should go give a follow! https://www.twitch.tv/someusername/profile

I've got the part of handling ! based commands from user input, so that's not an issue at all.


As for the example you provided me. With a couple of slight tweaks, that appears to do the trick. The first regex, changed .+? to [^@\s] otherwise it wasn't capturing the last ] in my bloated example. The other one, made it capture the @'s as well, since the replacement aliases include them as the name as well (helps them to stand out). Simply put, fine tuning but what you gave seems to be just what I was looking for. Thank you. smile

Re: [twitch] nested @commands@ [Re: Wolfie] #255989 09/12/15 05:18 AM
Joined: Jan 2004
Posts: 1,274
L
Loki12583 Offline
Hoopy frood
Offline
Hoopy frood
L
Joined: Jan 2004
Posts: 1,274
These @commands@ are not given directly by the user? What's the point of any of this then? Why don't you construct the string wherever you need it or call built in aliases instead of wrapping them like this?

Re: [twitch] nested @commands@ [Re: Loki12583] #255990 09/12/15 05:25 AM
Joined: Aug 2003
Posts: 325
W
Wolfie Offline OP
Fjord artisan
OP Offline
Fjord artisan
W
Joined: Aug 2003
Posts: 325
To make them versatile. After all, why expect the user to type in @target@[1] etc instead of just typing it out? The idea is to make it so someone types in !command (text) and then the script processes it into something useful. The commands are variables that contain the @ strings.

Re: [twitch] nested @commands@ [Re: Wolfie] #255993 09/12/15 12:23 PM
Joined: Jan 2004
Posts: 1,274
L
Loki12583 Offline
Hoopy frood
Offline
Hoopy frood
L
Joined: Jan 2004
Posts: 1,274
If you're the one doing this, how is inventing and parsing your own syntax more versatile than just using mirc's? How is @upper@[text] any more versatile than $upper(text)? What you've invented is actually much less versatile than just using aliases directly.

Re: [twitch] nested @commands@ [Re: Loki12583] #256004 10/12/15 01:54 PM
Joined: Aug 2003
Posts: 325
W
Wolfie Offline OP
Fjord artisan
OP Offline
Fjord artisan
W
Joined: Aug 2003
Posts: 325
Originally Posted By: Loki12583
If you're the one doing this, how is inventing and parsing your own syntax more versatile than just using mirc's? How is @upper@[text] any more versatile than $upper(text)? What you've invented is actually much less versatile than just using aliases directly.
Not really. What I gave you an example of, without the bloat included, is a command that mods can use to promote friends (other streamers) to the viewers. So, add a !command like '!profile' (or something else) and then it handles the work of converting the input to a command. Now, if these were hard coded commands, then it's not an issue to handle things directly. However, part of the versatility is in making it possible to add new commands, but without compromising security. So, instead of allowing a command to have and execute things like $upper/$lower, replace them with alternatives, so that there is control over what functions are available (and just what those functions do). So, without permitting access to mirc's commands, the only available commands are ones I make available. After all, don't want someone with mirc knowledge to find out my password, IP address, contents of my hard drive, etc.

Re: [twitch] nested @commands@ [Re: Wolfie] #256006 10/12/15 03:50 PM
Joined: Jan 2004
Posts: 1,274
L
Loki12583 Offline
Hoopy frood
Offline
Hoopy frood
L
Joined: Jan 2004
Posts: 1,274
So users are the ones giving this input. You seem to have excluded a lot of relevant information from this discussion. I really don't care about a difference between viewers and mods, the only thing that matters is the input to this alias and where it originates (you or elsewhere). To confuse things even more you're referring to three different things all as commands.

This is what I've gotten from your explanation:

1. A user types !profile nick
2. Some other person crafts a user friendly message
3. This message is sent to you, evaluated, and sent to the channel

That doesn't provide a use for @target@ though.

But here's a new thought I've had based on past experience with twitch requests and not at all from what you've explained:

1. A mod can craft a new "command" which consists of a "command" part and "response" part.
2. You expose something like !addcom to privileged users and store these in an ini file or hash table.
3. You have a catch-all text event which checks if a user has said a "command" present in your data structure.
4. The corresponding response is evaluated by the alias we've been discussing and sent to the channel. This does allow a stored @target@[1] to refer to the original text of the !profile command.

I hope that's correct, because I've amended the script to pass the original user text in order to facilitate @target@[]. I've also slightly modified the regex to allow omission of brackets which got messed up before (@pi@).

Edit: Calling %command %args %user.text causes problems with spaces and I've had to use evaluation brackets [ ] to call it as $command(%args,%user.text). Using these brackets introduces the possibility of an injection but I believe I've coded appropriately to avoid them and have tested input and each of the %command %args and %user.text variables with a mixture of text containing $day $!day @$day@ with brackets [ ] and pipes | as well.

Code:
alias test {
  writeini -c test.ini commands !plug @target@[1] @underline@[is another streamer] and everyone should go give a follow! @profile@[@bold@[@lower@[@target@[1]]]]

  tokenize 32 !plug BOBROSS

  var %command = $1, %user.text = $2-

  if ($readini(test.ini,n,commands,%command) != $null) {
    var %message = $v1
  }
  else return

  var %regex = /(@[^@\s]+@(?:\[(?:[^\[\]]|(?R))*\])?)/g

  echo -ag $regsubex(recurse,%message,%regex,$twitch.sub($regml(recurse,\n),%user.text))
}

alias twitch.sub {
  var %message = $1, %user.text = $2
  var %regex = /^@(\S+?)@(?:\[(.*?)\])?$/iS

  if (!$regex(%message,%regex)) return

  var %command = $regml(1)
  var %args = $regml(2)

  if (%args != $null) && ($regex(%args ,%regex)) {
    %args = $twitch.sub.recurse(%args,%user.text)
  }

  %command = twitch.val. $+ %command

  if ($isalias(%command)) {
    return $ [ $+ [ %command ] $+ ] ( $(%args,0) , $(%user.text,0) )
  }
}

alias twitch.sub.recurse return $twitch.sub($1,$2)

alias twitch.val.target return $gettok($2,$1,32)  
alias twitch.val.profile return http://twitch.tv/ $+ $1
alias twitch.val.upper return $upper($1)
alias twitch.val.lower return $lower($1)
alias twitch.val.underline return  $+ $1 $+ 
alias twitch.val.bold return  $+ $1 $+ 
alias twitch.val.pi return $pi

Last edited by Loki12583; 10/12/15 09:01 PM.
Re: [twitch] nested @commands@ [Re: Loki12583] #256018 12/12/15 03:24 AM
Joined: Aug 2003
Posts: 325
W
Wolfie Offline OP
Fjord artisan
OP Offline
Fjord artisan
W
Joined: Aug 2003
Posts: 325
Originally Posted By: Loki12583
So users are the ones giving this input. You seem to have excluded a lot of relevant information from this discussion. I really don't care about a difference between viewers and mods, the only thing that matters is the input to this alias and where it originates (you or elsewhere). To confuse things even more you're referring to three different things all as commands.

This is what I've gotten from your explanation:

1. A user types !profile nick
2. Some other person crafts a user friendly message
3. This message is sent to you, evaluated, and sent to the channel

That doesn't provide a use for @target@ though.

But here's a new thought I've had based on past experience with twitch requests and not at all from what you've explained:

1. A mod can craft a new "command" which consists of a "command" part and "response" part.
2. You expose something like !addcom to privileged users and store these in an ini file or hash table.
3. You have a catch-all text event which checks if a user has said a "command" present in your data structure.
4. The corresponding response is evaluated by the alias we've been discussing and sent to the channel. This does allow a stored @target@[1] to refer to the original text of the !profile command.
My original request contained the necessary information and your regex and sample use of it provided what I needed (thus the thanks in a previous post).

To the people in chat, typing something like "!profile someUserName" is using a command because "!profile" is thought of as a command. The "someUserName" is considered to be a value. The grabbing it as input and using it as a substitution in a predefined string is part of the scripting. So...
1. Predefined %variable contains the crafted response with the replaceable @values@ in it.
2. Someone uses a !command (ex !profile someUserName)
3. Script processes it and responds to the chat, all fully automated.

The @value@ references call upon commands that process it into what it needs to be. @profile@[someUserName] for example returns a link to the specified persons Twitch profile. @user@ gets replaced with $nick (person using the command being processed), @target@[1] ... @target@[99] calls on that token #, etc. It makes it so certain dynamic values can be processed without introducing security issues (ie, not evaluating $'s and such).

The reason I refer to them as @commands@ is because they are, in a manner of speaking, commands. From the user perspective, it's not, but from a scripting perspective, it is, because an alias (command) is called upon to handle it. The 'catch all' is actually for a few select things where the exact same process would be followed to obtained one of many values (which would all be grabbed at the same time no matter what). So instead of having a bunch of aliases to do the exact same thing, have the 'catch all' do it and simply return the desired value from the obtained data.

Also, the IRC on Twitch filters out bold/underline/etc. Only real use for it is client side if you want to have something stand out. Once it's sent to the chat server, it's filtered and not sent out to the other chatters.