home *** CD-ROM | disk | FTP | other *** search
Wrap
**************************** * Writing your own modules * **************************** Intro ===== To write your own modules you need two things : a) a rudimentary knowledge of BASIC b) something you want to write Whilst most people have a), very few people come up with anything original to fulfil b). However, to save people the bother of writing this (and to save myself getting thousands of different versions of the same thing) I'm going to talk you through the development of the simplest of functions - AutoGreet. It's one that most people play with at some time or another and so most will know how it's done and why it's a Bad Thing. However, for those that don't know, it's annoying, makes you look 'like an automaton' and it's pointless as you'll get about 15 people all on autogreet when nobody is actually paying attention. Anyhow... I'm not going to teach you the quickest way to do it. Nor am I going to teach you the best way to do it. The former of these is because the quickest wouldn't teach you anything, and the latter because I'm not sure there is a 'best' way. Whilst I'm describing what we are doing I'll be describing the programs in this directory, however you should write your own routines if you wish to actually learn anything. Most people will ignore this. I don't care. You'll only make it harder on yourself. Overview ======== What I want is a script which says "Hi <nick>" when they join the channel. I'll also want a way of turning the thing on and off. So the first thing we need is a variable to mark whether the feature is on or off. I'll call this 'autogreet' because it's nice and simple and it won't clas with anything else. When writing your own modules you should prefix all your variables with the module name if you can so that they don't clash with other modules. To start with I want this on, so : autogreet=TRUE However, we've got to initialise this when IRClient starts up so we need to stick it in a PROCedure. PROCInitialiseModule is an overloaded routine which is called when IRClient starts up. You don't need to worry about the fact that it's 'overloaded'; all it means is that the routine can be hooked onto and replace or change the behaviour of the original. So we need to use : DEFPROCInitialiseModule autogreet=TRUE ENDPROC However, this still won't work properly. Because of the 'overloading' you need allow everything else to add their 'hooks' and if we exit straight away then nobody else will be able to do anything and we will have replaced the function of the routine. To call the original routine we call a special procedure called PROC@ which is basically the next module with a 'hook' into this routine. The initialisation is therefore : DEFPROCInitialiseModule autogreet=TRUE PROC@ ENDPROC Having got the initialisation over and done with we might as well write the code to add the greetings. To say something on a channel or to a user you just use PROCSay(chan$,message$) where chan$ is the channel or user name, and message is the message to use. Similarly, PROCAction(chan$,message) will do the equivilent of /me. What we need therefore will be : IF autogreet THEN PROCSay(chan$,"Hi "+nick$) ENDIF This needs to go in a routine which 'hooks' onto people joining a channel. This is called PROCOverload_On_Join(nick$,chan$,uid$) where : nick$ is the nickname of the person who joined chan$ is the channel they have just joined uid$ is the user id of the user. This is the response from the server which tells you where they logged in from and who they claim to be in a similar form as an email address. Most people can be mailed at this address, but remember to cut off any initial +, - or ~ as some servers add this to indicate either validated users, invalidated users, or unvalidated users. We will also want to pass the call on to the original routine so that we still get the default message about the user joining. DEFPROCOverload_On_Join(nick$,chan$,uid$) IF autogreet THEN PROCSay(chan$,"Hi "+nick$) ENDIF PROC@(nick$,chan$,uid$) ENDPROC As before we are passing the call on to the original routine, but we are also passing the parameters on - we could quite easily have changed these but I'm not sure why we'd want to do that... Although the above routine looks right there is something very wrong with it. Unfortunately, I didn't spot this one for quite some time, but when I thought about it it was quite obvious. What was happening was that our routine gets called and says 'hi' to the new person, but then the call is passed on to the original routine which then tells you that they have joined... It's only a timing thing, but the call to the original routine (which tells you they have joined) should be called before you say 'hi' to them. I should have written : DEFPROCOverload_On_Join(nick$,chan$,uid$) PROC@(nick$,chan$,uid$) IF autogreet THEN PROCSay(chan$,"Hi "+nick$) ENDIF ENDPROC That's it really. You can test that and you'll see that it works perfectly (if I've described it right, of course !) However, if you try /ctcp <nick> scripts you'll see that your masterpiece isn't in the list. This only needs a simple bit of code to remedy, and I think I'll give you it and then describe it as it's tiresome doing it the other way :-) DEFFNOverload_ScriptInfo(num) LOCAL ret$ IF num=0 THEN ret$="AutoGreet v1.00 (Gerph)" ELSE ret$=FN@(num-1) ENDIF =ret$ As you can see, this is another overloaded routine - most Magrathea scripts are just overloads - to return the name of the script. Basically when we receive a 'SCRIPTS' request, Magrathea starts counting from 0, calling this routine. The first overloaded script receives it and, spotting that the number is 0 it returns it's name which Magrathea happily sends out as the first script name. Then it calls the routine again with 1. The first routine then passes it on to the next one, but with 0 so it returns the name and is again sent out. I think you get the picture anyhow... This is probably the quickest way of collating information from a group of scripts. * At this point the file '1' contains the script with some annotation * This is annoying ================ If you've actually tried this live you'll know how annoying it can be. So what we'll do is add a command to turn the autogreet feature off. The easiest way to do this is to add a command /autogreet to control it's use. To do this, we need to hook on to the 'unknown' command routine and spot when the user uses the command they /autogreet. Surprisingly, this is called PROCOverload_UnknownCommand(com$,rest$) where : com$ is the command they issued without the leading / in capitals rest$ is the rest of the command line with leading spaces stripped off Built into the configuration routines are two functions which will be very useful to us - FNboolean(str$) and FNbooltext(val). These, respectively, convert from a boolean (eg, on, true, 1, etc) to a number and from a number to 'On' or 'Off'. There is also a third state which we need to know about - Invalid (-2). This is returned when the string could not be decoded, or if the number is not -1, 0 or 1. Once we've set the value we had better tell the user what we have done otherwise it's likely they will think that something has gone wrong. To print things on the screen I'm going to use two calls which are specially designed for this type of use, PROCDisplayInfo and PROCDisplayWarning. There is a third more serious routine called PROCDisplayError but I shalln't use this as I don't think it's appropriate for a simple syntax error. All these take two strings as parameters, the message and the display to send it to. If the display is null (ie "") then the text will be sent to the current display preceeded by "*** " so that it is obvious that it is a message and not part of IRC. If you give a display name then the message will never be preceeded by these *'s and you will have to include them yourself if you want them. So the final routine I've come up with is : DEFPROCOverload_UnknownCommand(com$,str$) LOCAL val CASE com$ OF WHEN "AUTOGREET" val=FNboolean(str$) CASE val OF WHEN TRUE,FALSE autogreet=val PROCDisplayInfo("AutoGreet turned "+FNbooltext(autogreet),"") WHEN -2 PROCDisplayWarning("Syntax: AutoGreet <boolean>","") ENDCASE OTHERWISE PROC@(com$,str$) ENDCASE ENDPROC A couple of things to note about this; firstly, the use of local variables in your routines is strongly recommended as this will stop scripts as yet unwritten from clashing with you (hopefully). Secondly, I have used a CASE statement to check the command but it would have been quite acceptable to have used an IF as there is only a single entry here. CASE's however are slightly faster if many comparisons are to be made and also look neater than a row of IF's. Thirdly, notice the OTHERWISE clause is a call to the other overloaded routines - it is VERY important to pass the call on unless you are explicitly servicing it as otherwise things can get very confusing and some modules (and notably the aliases) will fail to work. * At this point the file '2' contains the script with some annotation * This really IS annoying ======================= After testing all that log out you'll either be fed up with programming Magrathea or you'll want to kill me for suggesting such an irritating feature as an example. It would be useful therefore to be able to configure the greetings off on start up. There are three simple ways you could do this : 1) you could exclude the 'AutoGreet' script using the dependency configuration - this will prevent it ever being loaded. 2) you could add "/autogreet off" into the 'Initial' script in the User directory - this will always start with it off or 3) you could set autogreet to FALSE in the PROCInitialiseModule routine. Obviously 3 is the simplest, 2 the most 'user friendly' and 1 the most drastic. However, I don't like any of these. Why not ? Because you're not learnig anything. And learning is fun. Or can be if you work at it. So what we're going to do is create a little configuration section just like the dependency module and all the others have. This involves quite a bit of code so it's probably not really practical for such a simple module. We're going to do it anyway though. I don't care if you have a note from you Mum saying you've had a tooth out and you'd like to be exempt from programming. You'll do it. And you'll enjoy it. Ok ? Firstly, before we get down to the wonderful world of the practical we've got to do some theory. Just a few paragraphs, nothing more I promise. Right, all the configuration details are handled by a single module which provides two calls to read and write configuration details. These are called FNDB_ReadConfig(tag$) and PROCDB_WriteConfig(tag$,val$) and they work (have you guessed it yet ?) on a system of tags. Each configuration is given a tag name and these are used to look up the data that you need. Simple, isn't it ? Each of the tags should be prefixed by the modules name so that it can't get confused with any other modules data. The data can be anything at all you like so long as it's a string. The other important thing which I'll need to explain is the configuration structure. There are only three routines you need to write - FNOverload_ConfigModName : Declares your module to the user in the same way as FNOverload_ScriptInfo did PROCOverload_ConfigOptions : Tells the user what your options are PROCOverload_ConfigCommand : Allows the user to do the actual configuring in as similar way to PROCOverload_UnknownCommand Each of these is progressively longer so we'll write them in that order (no running now, the computer won't go away...). FNOverload_ConfigModName ------------------------ Basically this is identical to the ScriptInfo routine, except that the name you give will be the name you will go by subsequently. If you do not recognise the name the user will be shown an error and they'll know how bad a programmer you are, so you don't want to do that now do you ? DEFFNOverload_ConfigModName(count) LOCAL ret$ IF count=0 THEN ret$="AutoGreet" ELSE ret$=FN@(count-1) ENDIF =ret$ Identical wasn't it ? It makes my life easier if I know that one routine is like another, so it should make yours easier too... PROCOverload_ConfigOptions -------------------------- This is a relatively simple module. This will be called when the user selects your module for configuring so you should display a little message about the module and give the names of the options. To display things on the configuration window we simply use PROCDisplayConfig(message$). This will also handle if the user starts being clever and writes /config <module> <option> <value> by sending it to the current window so you needn't bother with any extra code for that. You should also check that your module is the one being selected for configuration otherwise things could get very strange.... The code therefore is simply : DEFPROCOverload_ConfigOptions(module$) IF module$="AutoGreet" THEN PROCDisplayConfig("") PROCDisplayConfig("-- AutoGreet configuration --") PROCDisplayConfig("You can configure :") PROCDisplayConfig(" Active : Whether greet on join is active") ELSE PROC@(module$) ENDIF ENDPROC Notice that I've output a blank line before the main banner; this is so that things look a little neater and we don't get too confused between what belongs to what. DEFPROCOverload_ConfigCommand ----------------------------- This is the workhorse of the configuration. You get passed three strings : module$ : the module name so that you can spot yours com$ : the first word they used in capitals str$ : the rest of the line they entered You should be also keep to the following guidelines : 1) LIST should always show the current status. How you do this is unspecified, but it is recommended that you use a <option> : <value> format as applicable to the function 2) HELP should give you more information about the module. Most of the time the easiest thing to do is call PROCOverload_ConfigOptions with your module name to give the same message as the greeting. 3) You need not recognise QUIT and should never receive it - the configuration module will take care of that for you. We'll start with the LIST routine as this is quite simple. We need to show a) the current state and b) the configured state as these may not be the same. The code I've come up with is : PROCDisplayConfig("") PROCDisplayConfig("Active : "+FNbooltext(autogreet)) a$=FNbooltext(VAL(FNDB_ReadConfig("AutoGreet_On"))) PROCDisplayConfig(" (Configured "+a$+")") which again outputs a blank line before hand to tidy the display up. Next, the code to actually alter the state can be written as : val=FNboolean(str$) IF val=-2 THEN PROCDisplayConfig("Syntax: Active <boolean>") ELSE autogreet=val PROCDB_WriteConfig("AutoGreet_On",STR$autogreet) PROCDisplayConfig("Set Active to "+FNbooltext(autogreet)) ENDIF This is almost identical to the UnknownCommand routine and could possible have been put in a seperate routine, however because we are calling different display routines it's easier to keep them seperate. I seem to have used IF's here rather than a CASE. This is probably because when I wrote this routine I just 'stole' one of the options out of another module and replaced a few bits. This is the easiest way of doing things and is much more recommended than writing things out from scratch each time. Finally this needs to be surrounded by checks for the command, a HELP message and an unknown command message : DEFPROCOverload_ConfigCommand(module$,com$,str$) IF module$="AutoGreet" THEN CASE com$ OF WHEN "ACTIVE" [ routine above ] WHEN "LIST" [ routine above ] WHEN "HELP" PROCOverload_ConfigOptions(module$) OTHERWISE PROCDisplayConfig("Command not recognised") ENDCASE ELSE PROC@(module$,com$,str$) ENDIF ENDPROC Once that's done the configuration module will be able to interact almost instantly with you. Dead easy, huh ? * At this point the file '3' contains the script with some annotation * Hang on, I look stupid doing this ================================= Yes you do... I said you would, but my intention was to make you look stupid and I seem to have done that. So, to set the matter straight we'd better find 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 that people don't suspect that it's automatic. Under Magrathea this is done by 'callback' routine. This will be familiar to assembler programmers but for the rest of us this is like an alarm clock and when it goes off it 'calls us back'. The routine we need to use is called PROCAddCallBack(name$,delay,private) and it's parameters are : name$ : the name of the call back so that you can recognise it delay : the delay after which you wish to be called private : Some private value which can contain anything you like. Callbacks are NOT guarenteed to go off at a particular time. The are guarenteed not to go off /before/ a particular time, but the time at which they do get triggered is indterminate. For most purposes this is adequate and if it isn't then you'll just have to lump it. The easiest way to do this is to set a callback when you see the person join 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 people join at once. This is accomplished by the use of the private value. What we will do is use it to store the name of the nick who joined. Since we can only store a single integer we have to be cunning and cunning people do it this way : a) claim a block of memory as long as the string (+1 for the terminator) b) store the string in that block c) use the pointer to that block of memory as the private word Fortunately a) and b) can be done in a single function - FNStrdup(str$) - standing for String Duplicate and named after it's C counterpart. So instead of calling PROCSay we must call PROCAddCallBack : PROCAddCallBack("AutoGreet",200,FNStrdup(nick$+" "+chan$)) which will install a delay of at least 2 seconds before greeting them. Since the nick name may not contain a space it is safe to do it this way. Since a channel name begins with a # we could use that as a seperator, but there is also the possibility that you were on a 'local' channel prefixed by an & which would mean two compares instead of one slowing the callback down. Therefore, to be on the safe side I've opted for a space. We also need to 'catch' the callback and this is done by means of PROCOverload_CallBack. After we've said Hi, however it is also very important that we release the memory we claimed with PROCRelease otherwise it will remain trapped until IRClient exits. Before we say anything though we need to seperate out the nick and the channel name from the private word. Finding the nick can be done by reading up to the first space : nick$=LEFT$(private),INSTR$(private)," ")-1) and the channel name can be cunningly read out of the string by starting reading only /after/ the space : chan$=$(private+LEN(nick$)+1) So the entire routine is : DEFPROCOverload_CallBack(name$,private) LOCAL nick$,chan$ IF name$="AutoGreet" THEN nick$=LEFT$($(private),INSTR($(private)," ")-1) chan$=$(private+LEN(nick$)+1) PROCSay(chan$,"Hi "+nick$) PROCRelease(private) ELSE PROC@(name$,private) ENDIF ENDPROC * At this point the file '4' contains the script with some annotation * Outro ===== That pretty much completes this example. Exercises for you to work on can be collected further down the page. I hope you've enjoyed this little lesson more than I have giving it; you're a horrible bunch of students and you should all be shot - the lot of you... Go on, get on with you... Move it, move it ! Exercises ========= It is not expected that you will be able to do these and the lecturer does not wish to see examples of any of these except 5 for curiousity value. 1. Add a configuration option to set the delay to something other than 2 seconds. 2. Add a secondary configuration option (or subsiduary of the first) that causes the RND function to be used to generate a random delay of a sensible order. (RND produces a random number between -2^31-1 and 2^31 - I think) 3. Make greetings particular to the person or the channel. 4. Make a parting addition which says how sorry you are that the person left. 5. Add logging of who joined and left the channel and tell those people who have joined when they last left.