This shows you the differences between two versions of the page.
ii:labs:04:tasks:02 [2024/11/29 09:12] alexandru.bala [02. [40p] Discord bot setup] |
ii:labs:04:tasks:02 [2024/11/29 09:16] (current) alexandru.bala [02. [40p] Discord bot setup] |
||
---|---|---|---|
Line 133: | Line 133: | ||
Let's take a closer look at the code skeleton and see what each thing does: | Let's take a closer look at the code skeleton and see what each thing does: | ||
- | * ''from discord.ext import commands'': as you guessed, this works pretty much like the regular ''import module''. The difference here is that we don't import it as a whole, but bits and pieces such as submodules, functions, etc. Additionally, we don't have to specify the whole module chain (i.e.: ''discord.ext.commands'') every time we use it. In stead, we can just say ''commands'' and the interpreter will know what we're referring to. | + | * ''from discord.ext import commands'': as you guessed, this works pretty much like the regular ''import module''. The difference here is that we don't import it as a whole, but bits and pieces such as submodules, functions, etc. Additionally, we don't have to specify the whole module chain (i.e.: ''discord.ext.commands'') every time we use it. Instead, we can just say ''commands'' and the interpreter will know what we're referring to. |
* ''log_msg()'': this is a helper function that colors your message according to its urgency and also displays the line where it was invoked. Use it if you want, but it's not mandatory. Note however the arguments: ''msg: str, level: str''. Since //Python 3.5// you can use [[https://docs.python.org/3/library/typing.html|typing hints]] for variables and function return values. When possible, use these hints for clarity if nothing else. | * ''log_msg()'': this is a helper function that colors your message according to its urgency and also displays the line where it was invoked. Use it if you want, but it's not mandatory. Note however the arguments: ''msg: str, level: str''. Since //Python 3.5// you can use [[https://docs.python.org/3/library/typing.html|typing hints]] for variables and function return values. When possible, use these hints for clarity if nothing else. | ||
* ''@bot.event'': this is called a decorator. Most likely, you've never seen this before and we can't blame you. Things like this belong on [[https://www.reddit.com/r/iamverysmart/|r/iamverysmart]] and not in programming language specifications. A decorator is basically a function processing function. As the name implies, by specifying a decorator before the declaration of that function, the declared function will be passed to the decorator function for "some processing". In this case, our **on_ready()** function is passed to the [[https://docs.pycord.dev/en/master/api/clients.html|event]] decorator belonging to our global bot instance (line 58). The "processing" that the **event** decorator does is to assign **on_ready()** as the bot's callback method for the event with the same name. So when the bot finishes its initialization and connects to the server, it raises an //on_ready// event and our method will intercept it and resolve it. | * ''@bot.event'': this is called a decorator. Most likely, you've never seen this before and we can't blame you. Things like this belong on [[https://www.reddit.com/r/iamverysmart/|r/iamverysmart]] and not in programming language specifications. A decorator is basically a function processing function. As the name implies, by specifying a decorator before the declaration of that function, the declared function will be passed to the decorator function for "some processing". In this case, our **on_ready()** function is passed to the [[https://docs.pycord.dev/en/master/api/clients.html|event]] decorator belonging to our global bot instance (line 58). The "processing" that the **event** decorator does is to assign **on_ready()** as the bot's callback method for the event with the same name. So when the bot finishes its initialization and connects to the server, it raises an //on_ready// event and our method will intercept it and resolve it. | ||
* ''async'' & ''await'': in //Python// there's these things called [[https://docs.python.org/3/library/asyncio-task.html|coroutines]] (also quickly explained in [[https://docs.pycord.dev/en/stable/faq.html#coroutines|PyCord FAQ]]). Coroutines are functions that can be entered, exited and resumed at different points in their execution. Diving into this subject may prove too difficult right now, so we'll try to keep it simple. Thus, please excuse the hand waving: basically, any event handler can be triggered at any time (even when other handlers are already executing), so we mark them as //asynchronous// with the **async** keyword. When we try to call on asynchronous functions, we need to **await** their execution to finish before continuing on our merry way. | * ''async'' & ''await'': in //Python// there's these things called [[https://docs.python.org/3/library/asyncio-task.html|coroutines]] (also quickly explained in [[https://docs.pycord.dev/en/stable/faq.html#coroutines|PyCord FAQ]]). Coroutines are functions that can be entered, exited and resumed at different points in their execution. Diving into this subject may prove too difficult right now, so we'll try to keep it simple. Thus, please excuse the hand waving: basically, any event handler can be triggered at any time (even when other handlers are already executing), so we mark them as //asynchronous// with the **async** keyword. When we try to call on asynchronous functions, we need to **await** their execution to finish before continuing on our merry way. | ||
* ''@bot.command'': yet another decorator, but this one registers the following function as a __command__. Commands are identified by the prefix established during the bot's instantiation (in this case, ''!''). So, by writing //"!roll 100"// in the **discord** chat, the bot will read the message, recognize **roll** as a command, parse the argument and reply with a random number between 1 and 100. | * ''@bot.command'': yet another decorator, but this one registers the following function as a __command__. Commands are identified by the prefix established during the bot's instantiation (in this case, ''!''). So, by writing //"!roll 100"// in the **discord** chat, the bot will read the message, recognize **roll** as a command, parse the argument and reply with a random number between 1 and 100. | ||
- | * ''@roll.error'': this decorator defines an error callback routine for the **roll** command. Without this, if the user forgets to specify the argument let's say, the **roll()** function will fail with a nasty error printed to //stdout// but the script will not crash! By specifying the error handler, we can output its message to the discord user in stead of the random number. This way, he isn't greeted by silence, but in stead learns what he did wrong. | + | * ''@roll.error'': this decorator defines an error callback routine for the **roll** command. Without this, if the user forgets to specify the argument let's say, the **roll()** function will fail with a nasty error printed to //stdout// but the script will not crash! By specifying the error handler, we can output its message to the discord user instead of the random number. This way, he isn't greeted by silence, but instead learns what he did wrong. |
* ''os.environ['BOT_TOKEN']'': generally, it's a bad idea to hardcode IDs and passwords into the source code. As such, we save our bot's token inside an environment variable in **bash** (or **zsh**). When running a program from your shell, it inherits all your variables. So before you run the skeleton, export the token under the name //BOT_TOKEN//. | * ''os.environ['BOT_TOKEN']'': generally, it's a bad idea to hardcode IDs and passwords into the source code. As such, we save our bot's token inside an environment variable in **bash** (or **zsh**). When running a program from your shell, it inherits all your variables. So before you run the skeleton, export the token under the name //BOT_TOKEN//. | ||
Line 151: | Line 151: | ||
</code> | </code> | ||
- | When executed, this line of code will pause the script and open a **python** shell for you to investigate the state of both the local and global variables. The syntax might seem a bit weird. The **interact()** function takes a dictionary as the //local// argument. The //local// argument is used to initialize the variables available in the newly opened shell. Because we want to see both local __and__ global variables, we need to combine them in a single dictionary. **dict** is the dictionary type. When using it to create a new dictionary (in stead of the basic ''{ }'' syntax), we can specify another dictionary for initialization: **dict(globals())**. But we want to combine __two__ dictionaries. However, we can't just add them with the ''+'' operator since it doesn't work that way. Also, we don't want to __change__ any of the dictionaries returned by **globals()** and **locals()** since it might get us into trouble with the interpreter later. But wait! [[https://docs.python.org/3/library/stdtypes.html#dict|dict]] can also be initialized from optional keyword arguments, represented by key-value pairs. So **%%**%%locals()** can be used to break down the second dictionary in optional keyword arguments for the resulting dictionary's constructor! | + | When executed, this line of code will pause the script and open a **python** shell for you to investigate the state of both the local and global variables. The syntax might seem a bit weird. The **interact()** function takes a dictionary as the //local// argument. The //local// argument is used to initialize the variables available in the newly opened shell. Because we want to see both local __and__ global variables, we need to combine them in a single dictionary. **dict** is the dictionary type. When using it to create a new dictionary (instead of the basic ''{ }'' syntax), we can specify another dictionary for initialization: **dict(globals())**. But we want to combine __two__ dictionaries. However, we can't just add them with the ''+'' operator since it doesn't work that way. Also, we don't want to __change__ any of the dictionaries returned by **globals()** and **locals()** since it might get us into trouble with the interpreter later. But wait! [[https://docs.python.org/3/library/stdtypes.html#dict|dict]] can also be initialized from optional keyword arguments, represented by key-value pairs. So **%%**%%locals()** can be used to break down the second dictionary in optional keyword arguments for the resulting dictionary's constructor! |
Here's an easier example where ''x'' is basically **globals()** and ''c=3, d=4'' is **%%**%%locals()**. | Here's an easier example where ''x'' is basically **globals()** and ''c=3, d=4'' is **%%**%%locals()**. |