home *** CD-ROM | disk | FTP | other *** search
/ ARM Club 3 / TheARMClub_PDCD3.iso / programs / comms_networking / irclient / !IRClient / Scripts / Examples / AutoGreet / !ReadMe next >
Encoding:
Text File  |  1997-01-01  |  20.6 KB  |  489 lines

  1.                             ****************************
  2.                             * Writing your own modules *
  3.                             ****************************
  4.  
  5. Intro
  6. =====
  7. To write your own modules you need two things :
  8.   a) a rudimentary knowledge of BASIC
  9.   b) something you want to write
  10.  
  11. Whilst most people have a), very few people come up with anything original to
  12. fulfil b). However, to save people the bother of writing this (and to save
  13. myself getting thousands of different versions of the same thing) I'm going
  14. to talk you through the development of the simplest of functions -
  15. AutoGreet.
  16.  
  17. It's one that most people play with at some time or another and so most will
  18. know how it's done and why it's a Bad Thing. However, for those that don't
  19. know, it's annoying, makes you look 'like an automaton' and it's pointless as
  20. you'll get about 15 people all on autogreet when nobody is actually paying
  21. attention.
  22.  
  23. Anyhow... I'm not going to teach you the quickest way to do it. Nor am I
  24. going to teach you the best way to do it. The former of these is because the
  25. quickest wouldn't teach you anything, and the latter because I'm not sure
  26. there is a 'best' way.
  27.  
  28. Whilst I'm describing what we are doing I'll be describing the programs in
  29. this directory, however you should write your own routines if you wish to
  30. actually learn anything. Most people will ignore this. I don't care. You'll
  31. only make it harder on yourself.
  32.  
  33.  
  34. Overview
  35. ========
  36. What I want is a script which says "Hi <nick>" when they join the channel.
  37. I'll also want a way of turning the thing on and off.
  38.  
  39. So the first thing we need is a variable to mark whether the feature is on or
  40. off. I'll call this 'autogreet' because it's nice and simple and it won't
  41. clas with anything else. When writing your own modules you should prefix all
  42. your variables with the module name if you can so that they don't clash with
  43. other modules. To start with I want this on, so :
  44.  
  45.   autogreet=TRUE
  46.  
  47. However, we've got to initialise this when IRClient starts up so we need to
  48. stick it in a PROCedure. PROCInitialiseModule is an overloaded routine which
  49. is called when IRClient starts up. You don't need to worry about the fact
  50. that it's 'overloaded'; all it means is that the routine can be hooked onto
  51. and replace or change the behaviour of the original.
  52.  
  53. So we need to use :
  54.  
  55.   DEFPROCInitialiseModule
  56.   autogreet=TRUE
  57.   ENDPROC
  58.  
  59. However, this still won't work properly. Because of the 'overloading' you
  60. need allow everything else to add their 'hooks' and if we exit straight away
  61. then nobody else will be able to do anything and we will have replaced the
  62. function of the routine. To call the original routine we call a special
  63. procedure called PROC@ which is basically the next module with a 'hook' into
  64. this routine. The initialisation is therefore :
  65.  
  66.   DEFPROCInitialiseModule
  67.   autogreet=TRUE
  68.   PROC@
  69.   ENDPROC
  70.  
  71. Having got the initialisation over and done with we might as well write the
  72. code to add the greetings. To say something on a channel or to a user you
  73. just use PROCSay(chan$,message$) where chan$ is the channel or user name, and
  74. message is the message to use. Similarly, PROCAction(chan$,message) will do
  75. the equivilent of /me. What we need therefore will be :
  76.  
  77.   IF autogreet THEN
  78.     PROCSay(chan$,"Hi "+nick$)
  79.   ENDIF
  80.  
  81. This needs to go in a routine which 'hooks' onto people joining a channel.
  82. This is called PROCOverload_On_Join(nick$,chan$,uid$) where :
  83.  nick$ is the nickname of the person who joined
  84.  chan$ is the channel they have just joined
  85.  uid$ is the user id of the user. This is the response from the server which
  86.       tells you where they logged in from and who they claim to be in a
  87.       similar form as an email address. Most people can be mailed at this
  88.       address, but remember to cut off any initial +, - or ~ as some servers
  89.       add this to indicate either validated users, invalidated users, or
  90.       unvalidated users.
  91.  
  92. We will also want to pass the call on to the original routine so that we
  93. still get the default message about the user joining.
  94.  
  95.   DEFPROCOverload_On_Join(nick$,chan$,uid$)
  96.   IF autogreet THEN
  97.     PROCSay(chan$,"Hi "+nick$)
  98.   ENDIF
  99.   PROC@(nick$,chan$,uid$)
  100.   ENDPROC
  101.  
  102. As before we are passing the call on to the original routine, but we are also
  103. passing the parameters on - we could quite easily have changed these but I'm
  104. not sure why we'd want to do that...
  105.  
  106. Although the above routine looks right there is something very wrong with it.
  107. Unfortunately, I didn't spot this one for quite some time, but when I thought
  108. about it it was quite obvious. What was happening was that our routine gets
  109. called and says 'hi' to the new person, but then the call is passed on to the
  110. original routine which then tells you that they have joined... It's only a
  111. timing thing, but the call to the original routine (which tells you they have
  112. joined) should be called before you say 'hi' to them. I should have written :
  113.  
  114.   DEFPROCOverload_On_Join(nick$,chan$,uid$)
  115.   PROC@(nick$,chan$,uid$)
  116.   IF autogreet THEN
  117.     PROCSay(chan$,"Hi "+nick$)
  118.   ENDIF
  119.   ENDPROC
  120.  
  121. That's it really. You can test that and you'll see that it works perfectly
  122. (if I've described it right, of course !)
  123.  
  124. However, if you try /ctcp <nick> scripts you'll see that your masterpiece
  125. isn't in the list. This only needs a simple bit of code to remedy, and I
  126. think I'll give you it and then describe it as it's tiresome doing it the
  127. other way :-)
  128.  
  129.   DEFFNOverload_ScriptInfo(num)
  130.   LOCAL ret$
  131.   IF num=0 THEN
  132.    ret$="AutoGreet v1.00 (Gerph)"
  133.   ELSE
  134.    ret$=FN@(num-1)
  135.   ENDIF
  136.   =ret$
  137.  
  138. As you can see, this is another overloaded routine - most Magrathea scripts
  139. are just overloads - to return the name of the script. Basically when we
  140. receive a 'SCRIPTS' request, Magrathea starts counting from 0, calling this
  141. routine. The first overloaded script receives it and, spotting that the
  142. number is 0 it returns it's name which Magrathea happily sends out as the
  143. first script name. Then it calls the routine again with 1. The first routine
  144. then passes it on to the next one, but with 0 so it returns the name and is
  145. again sent out.
  146.  
  147. I think you get the picture anyhow... This is probably the quickest way of
  148. collating information from a group of scripts.
  149.  
  150.   * At this point the file '1' contains the script with some annotation *
  151.  
  152.  
  153. This is annoying
  154. ================
  155. If you've actually tried this live you'll know how annoying it can be. So
  156. what we'll do is add a command to turn the autogreet feature off. The easiest
  157. way to do this is to add a command /autogreet to control it's use. To do
  158. this, we need to hook on to the 'unknown' command routine and spot when the
  159. user uses the command they /autogreet. Surprisingly, this is called
  160. PROCOverload_UnknownCommand(com$,rest$) where :
  161.  com$ is the command they issued without the leading / in capitals
  162.  rest$ is the rest of the command line with leading spaces stripped off
  163.  
  164. Built into the configuration routines are two functions which will be very
  165. useful to us - FNboolean(str$) and FNbooltext(val). These, respectively,
  166. convert from a boolean (eg, on, true, 1, etc) to a number and from a number
  167. to 'On' or 'Off'. There is also a third state which we need to know about -
  168. Invalid (-2). This is returned when the string could not be decoded, or if
  169. the number is not -1, 0 or 1.
  170.  
  171. Once we've set the value we had better tell the user what we have done
  172. otherwise it's likely they will think that something has gone wrong. To print
  173. things on the screen I'm going to use two calls which are specially designed
  174. for this type of use, PROCDisplayInfo and PROCDisplayWarning. There is a
  175. third more serious routine called PROCDisplayError but I shalln't use this as
  176. I don't think it's appropriate for a simple syntax error. All these take two
  177. strings as parameters, the message and the display to send it to. If the
  178. display is null (ie "") then the text will be sent to the current display
  179. preceeded by "*** " so that it is obvious that it is a message and not part
  180. of IRC. If you give a display name then the message will never be preceeded
  181. by these *'s and you will have to include them yourself if you want them.
  182.  
  183. So the final routine I've come up with is :
  184.  
  185.   DEFPROCOverload_UnknownCommand(com$,str$)
  186.   LOCAL val
  187.   CASE com$ OF
  188.    WHEN "AUTOGREET"
  189.     val=FNboolean(str$)
  190.     CASE val OF
  191.      WHEN TRUE,FALSE
  192.       autogreet=val
  193.       PROCDisplayInfo("AutoGreet turned "+FNbooltext(autogreet),"")
  194.      WHEN -2
  195.       PROCDisplayWarning("Syntax: AutoGreet <boolean>","")
  196.     ENDCASE
  197.    OTHERWISE
  198.     PROC@(com$,str$)
  199.   ENDCASE
  200.   ENDPROC
  201.  
  202. A couple of things to note about this; firstly, the use of local variables in
  203. your routines is strongly recommended as this will stop scripts as yet
  204. unwritten from clashing with you (hopefully). Secondly, I have used a CASE
  205. statement to check the command but it would have been quite acceptable to
  206. have used an IF as there is only a single entry here. CASE's however are
  207. slightly faster if many comparisons are to be made and also look neater than
  208. a row of IF's. Thirdly, notice the OTHERWISE clause is a call to the other
  209. overloaded routines - it is VERY important to pass the call on unless you are
  210. explicitly servicing it as otherwise things can get very confusing and some
  211. modules (and notably the aliases) will fail to work.
  212.  
  213.   * At this point the file '2' contains the script with some annotation *
  214.  
  215.  
  216. This really IS annoying
  217. =======================
  218. After testing all that log out you'll either be fed up with programming
  219. Magrathea or you'll want to kill me for suggesting such an irritating feature
  220. as an example. It would be useful therefore to be able to configure the
  221. greetings off on start up. There are three simple ways you could do this :
  222.   1) you could exclude the 'AutoGreet' script using the dependency
  223.      configuration - this will prevent it ever being loaded.
  224.   2) you could add "/autogreet off" into the 'Initial' script in the User
  225.      directory - this will always start with it off
  226. or
  227.   3) you could set autogreet to FALSE in the PROCInitialiseModule routine.
  228.  
  229. Obviously 3 is the simplest, 2 the most 'user friendly' and 1 the most
  230. drastic. However, I don't like any of these. Why not ? Because you're not
  231. learnig anything. And learning is fun. Or can be if you work at it. So what
  232. we're going to do is create a little configuration section just like the
  233. dependency module and all the others have.
  234.  
  235. This involves quite a bit of code so it's probably not really practical for
  236. such a simple module. We're going to do it anyway though. I don't care if you
  237. have a note from you Mum saying you've had a tooth out and you'd like to be
  238. exempt from programming. You'll do it. And you'll enjoy it. Ok ?
  239.  
  240. Firstly, before we get down to the wonderful world of the practical we've got
  241. to do some theory. Just a few paragraphs, nothing more I promise. Right, all
  242. the configuration details are handled by a single module which provides two
  243. calls to read and write configuration details. These are called
  244. FNDB_ReadConfig(tag$) and PROCDB_WriteConfig(tag$,val$) and they work (have
  245. you guessed it yet ?) on a system of tags. Each configuration is given a tag
  246. name and these are used to look up the data that you need. Simple, isn't it ?
  247. Each of the tags should be prefixed by the modules name so that it can't get
  248. confused with any other modules data. The data can be anything at all you
  249. like so long as it's a string.
  250.  
  251. The other important thing which I'll need to explain is the configuration
  252. structure. There are only three routines you need to write -
  253.   FNOverload_ConfigModName : Declares your module to the user in the same way
  254.                              as FNOverload_ScriptInfo did
  255.   PROCOverload_ConfigOptions : Tells the user what your options are
  256.   PROCOverload_ConfigCommand : Allows the user to do the actual configuring in
  257.                                as similar way to PROCOverload_UnknownCommand
  258.  
  259. Each of these is progressively longer so we'll write them in that order (no
  260. running now, the computer won't go away...).
  261.  
  262.  
  263. FNOverload_ConfigModName
  264. ------------------------
  265. Basically this is identical to the ScriptInfo routine, except that the name
  266. you give will be the name you will go by subsequently. If you do not
  267. recognise the name the user will be shown an error and they'll know how bad a
  268. programmer you are, so you don't want to do that now do you ?
  269.  
  270.   DEFFNOverload_ConfigModName(count)
  271.   LOCAL ret$
  272.   IF count=0 THEN
  273.    ret$="AutoGreet"
  274.   ELSE
  275.    ret$=FN@(count-1)
  276.   ENDIF
  277.   =ret$
  278.  
  279. Identical wasn't it ? It makes my life easier if I know that one routine is
  280. like another, so it should make yours easier too...
  281.  
  282. PROCOverload_ConfigOptions
  283. --------------------------
  284. This is a relatively simple module. This will be called when the user selects
  285. your module for configuring so you should display a little message about the
  286. module and give the names of the options. To display things on the
  287. configuration window we simply use PROCDisplayConfig(message$). This will
  288. also handle if the user starts being clever and writes /config <module>
  289. <option> <value> by sending it to the current window so you needn't bother with any extra code for that.
  290.  
  291. You should also check that your module is the one being selected for
  292. configuration otherwise things could get very strange....
  293.  
  294. The code therefore is simply :
  295.   DEFPROCOverload_ConfigOptions(module$)
  296.   IF module$="AutoGreet" THEN
  297.    PROCDisplayConfig("")
  298.    PROCDisplayConfig("-- AutoGreet configuration --")
  299.    PROCDisplayConfig("You can configure :")
  300.    PROCDisplayConfig("  Active : Whether greet on join is active")
  301.   ELSE
  302.    PROC@(module$)
  303.   ENDIF
  304.   ENDPROC
  305.  
  306. Notice that I've output a blank line before the main banner; this is so that
  307. things look a little neater and we don't get too confused between what
  308. belongs to what.
  309.  
  310. DEFPROCOverload_ConfigCommand
  311. -----------------------------
  312. This is the workhorse of the configuration. You get passed three strings :
  313.  module$ : the module name so that you can spot yours
  314.  com$    : the first word they used in capitals
  315.  str$    : the rest of the line they entered
  316.  
  317. You should be also keep to the following guidelines :
  318.  1) LIST should always show the current status. How you do this is
  319.     unspecified, but it is recommended that you use a <option> : <value> format
  320.     as applicable to the function
  321.  2) HELP should give you more information about the module. Most of the time
  322.     the easiest thing to do is call PROCOverload_ConfigOptions with your
  323.     module name to give the same message as the greeting.
  324.  3) You need not recognise QUIT and should never receive it - the configuration
  325.     module will take care of that for you.
  326.  
  327. We'll start with the LIST routine as this is quite simple. We need to show a)
  328. the current state and b) the configured state as these may not be the same.
  329. The code I've come up with is :
  330.  
  331.   PROCDisplayConfig("")
  332.   PROCDisplayConfig("Active : "+FNbooltext(autogreet))
  333.   a$=FNbooltext(VAL(FNDB_ReadConfig("AutoGreet_On")))
  334.   PROCDisplayConfig("  (Configured "+a$+")")
  335.  
  336. which again outputs a blank line before hand to tidy the display up.
  337.  
  338. Next, the code to actually alter the state can be written as :
  339.  
  340.   val=FNboolean(str$)
  341.   IF val=-2 THEN
  342.    PROCDisplayConfig("Syntax: Active <boolean>")
  343.   ELSE
  344.    autogreet=val
  345.    PROCDB_WriteConfig("AutoGreet_On",STR$autogreet)
  346.    PROCDisplayConfig("Set Active to "+FNbooltext(autogreet))
  347.   ENDIF
  348.  
  349. This is almost identical to the UnknownCommand routine and could possible
  350. have been put in a seperate routine, however because we are calling different
  351. display routines it's easier to keep them seperate. I seem to have used IF's
  352. here rather than a CASE. This is probably because when I wrote this routine I
  353. just 'stole' one of the options out of another module and replaced a few
  354. bits. This is the easiest way of doing things and is much more recommended
  355. than writing things out from scratch each time.
  356.  
  357. Finally this needs to be surrounded by checks for the command, a HELP message
  358. and an unknown command message :
  359.  
  360.   DEFPROCOverload_ConfigCommand(module$,com$,str$)
  361.   IF module$="AutoGreet" THEN
  362.    CASE com$ OF
  363.     WHEN "ACTIVE"
  364.      [ routine above ]
  365.   
  366.     WHEN "LIST"
  367.      [ routine above ]
  368.   
  369.     WHEN "HELP"
  370.      PROCOverload_ConfigOptions(module$)
  371.   
  372.     OTHERWISE
  373.      PROCDisplayConfig("Command not recognised")
  374.    ENDCASE
  375.   ELSE
  376.    PROC@(module$,com$,str$)
  377.   ENDIF
  378.   ENDPROC
  379.  
  380. Once that's done the configuration module will be able to interact almost
  381. instantly with you. Dead easy, huh ?
  382.  
  383.   * At this point the file '3' contains the script with some annotation *
  384.  
  385.  
  386. Hang on, I look stupid doing this
  387. =================================
  388. Yes you do... I said you would, but my intention was to make you look stupid
  389. and I seem to have done that. So, to set the matter straight we'd better find
  390. a way to stop you looking quite so stupid. The simplest was to do this is to ensure that the message to the newly joined user is delayed by a little while so
  391. that people don't suspect that it's automatic.
  392.  
  393. Under Magrathea this is done by 'callback' routine. This will be familiar to
  394. assembler programmers but for the rest of us this is like an alarm clock and
  395. when it goes off it 'calls us back'. The routine we need to use is called
  396. PROCAddCallBack(name$,delay,private) and it's parameters are :
  397.   name$ : the name of the call back so that you can recognise it
  398.   delay : the delay after which you wish to be called
  399.   private : Some private value which can contain anything you like.
  400.  
  401. Callbacks are NOT guarenteed to go off at a particular time. The are
  402. guarenteed not to go off /before/ a particular time, but the time at which
  403. they do get triggered is indterminate. For most purposes this is adequate and
  404. if it isn't then you'll just have to lump it.
  405.  
  406. The easiest way to do this is to set a callback when you see the person join
  407. the channel and when you are called back say the greeting. However, you need to know who it is you are greeting (and which channel they were joining) if many
  408. people join at once. This is accomplished by the use of the private value.
  409. What we will do is use it to store the name of the nick who joined. Since we
  410. can only store a single integer we have to be cunning and cunning people do
  411. it this way :
  412.  a) claim a block of memory as long as the string (+1 for the terminator)
  413.  b) store the string in that block
  414.  c) use the pointer to that block of memory as the private word
  415.  
  416. Fortunately a) and b) can be done in a single function - FNStrdup(str$) -
  417. standing for String Duplicate and named after it's C counterpart.
  418.  
  419. So instead of calling PROCSay we must call PROCAddCallBack :
  420.  
  421.   PROCAddCallBack("AutoGreet",200,FNStrdup(nick$+" "+chan$))
  422.  
  423. which will install a delay of at least 2 seconds before greeting them. Since
  424. the nick name may not contain a space it is safe to do it this way. Since a
  425. channel name begins with a # we could use that as a seperator, but there is
  426. also the possibility that you were on a 'local' channel prefixed by an &
  427. which would mean two compares instead of one slowing the callback down.
  428. Therefore, to be on the safe side I've opted for a space.
  429.  
  430. We also need to 'catch' the callback and this is done by means of
  431. PROCOverload_CallBack. After we've said Hi, however it is also very important
  432. that we release the memory we claimed with PROCRelease otherwise it will
  433. remain trapped until IRClient exits.
  434.  
  435. Before we say anything though we need to seperate out the nick and the
  436. channel name from the private word. Finding the nick can be done by reading
  437. up to the first space :
  438.   nick$=LEFT$(private),INSTR$(private)," ")-1)
  439.  
  440. and the channel name can be cunningly read out of the string by starting
  441. reading only /after/ the space :
  442.   chan$=$(private+LEN(nick$)+1)
  443.  
  444. So the entire routine is :
  445.  
  446.   DEFPROCOverload_CallBack(name$,private)
  447.   LOCAL nick$,chan$
  448.   IF name$="AutoGreet" THEN
  449.    nick$=LEFT$($(private),INSTR($(private)," ")-1)
  450.    chan$=$(private+LEN(nick$)+1)
  451.    PROCSay(chan$,"Hi "+nick$)
  452.    PROCRelease(private)
  453.   ELSE
  454.    PROC@(name$,private)
  455.   ENDIF
  456.   ENDPROC
  457.  
  458.   * At this point the file '4' contains the script with some annotation *
  459.  
  460.  
  461. Outro
  462. =====
  463. That pretty much completes this example. Exercises for you to work on can be
  464. collected further down the page. I hope you've enjoyed this little lesson
  465. more than I have giving it; you're a horrible bunch of students and you
  466. should all be shot - the lot of you... Go on, get on with you... Move it,
  467. move it !
  468.  
  469.  
  470. Exercises
  471. =========
  472. It is not expected that you will be able to do these and the lecturer does
  473. not wish to see examples of any of these except 5 for curiousity value.
  474.  
  475. 1. Add a configuration option to set the delay to something other than 2
  476.    seconds.
  477.  
  478. 2. Add a secondary configuration option (or subsiduary of the first) that
  479.    causes the RND function to be used to generate a random delay of a
  480.    sensible order. (RND produces a random number between -2^31-1 and 2^31 - I
  481.    think)
  482.  
  483. 3. Make greetings particular to the person or the channel.
  484.  
  485. 4. Make a parting addition which says how sorry you are that the person left.
  486.  
  487. 5. Add logging of who joined and left the channel and tell those people who
  488.    have joined when they last left.
  489.