Age | Commit message (Collapse) | Author |
|
Check that translation and the `ReaderT` really do work by changing
localisation of the STDOUT "help" output from `EN` to `FR`.
To actually test this, add a french translation of the `GitHubCommit`
plugin description. This string is successfully output by the "help"
plugin.
|
|
A test to try to get _something_ to compile. And it does!
Here we try to run a plugin with `runReaderT` and `runBot` to pass
`Options` to the plugin, using the "help" plugin as an example. It feeds
hard-coded `Options` to the plugin and outputs its response to STDOUT.
Also get an implementation of `initializePlugins` working that will
allow us to build a list of plugins with `Options` applied to the
reader.
This commit comments out the whole of IRC.hs in order to get the code to
compile. That file still has a bunch of `Bot` monad-related type errors.
To build the final plugin list, I think what we'll want to do is use
something like `initializePlugins` in Lib to apply all our plugins with
the `Options` and then feed that to `connectIRC`. In order to build the
"help" plugin, the plan is to re-apply the `Options` after `ask`ing for
them inside the `PluginAction` for "help" to all plugins, thus enabling
us to get a plugin list with the applied reader for proper localisation
of the help output.
|
|
Make the parsed options accessible to the rest of the program.
|
|
Provide a way to switch languages when starting the bot.
|
|
A new plugin that displays a help listing for every plugin in the bot.
Currently the formatting is off in the chat output, but it does work.
This introduces two new record fields on `Plugin`: `command` and
`description`. The command is the text used to invoke the plugin and the
description is a long form explanation of what the plugin does.
Needed to update the `Show` for `Plugin` to match these extra fields.
Didn't change any of the output for now because I'm not really using the
`show` function, so I don't need to see the new fields for now. Also
change the `p` argument to an `_` because we're not using it.
All existing plugins now have the new fields filled. The Help plugin
will go through the list of all plugins and get their help fields for
output.
In order to be able to use the plugin list in both `Plugin.hs` and in
the Help plugin module, I needed to move the list into a new module to
avoid a circular dependency. Previously the `Plugin` module defined the
list, but we can't import `Plugin` from `Help` because `Plugin` needs to
import `Help` in order to build the full list of plugins. The
semi-hackish solution I came up with was to create a new module for the
plugin list that both these modules can use, but leave out the `Help`
plugin from the plugin list there. Then, `Plugin` and `Help` override
the list, appending the `Help` plugin to the list. I want the Help
plugin to appear last, which is why I'm appending. Wasn't comfortable
concatenating the list because of the performance smell, but it's going
to be a small enough list anyway that it shouldn't be a problem.
One thing I don't really like is the fact that we have to return an
`IO a` from `helpAction` even though it doesn't interact with `IO` at
all. Not sure if there's a way to use `IO` when we need it and not when
we don't. Not a huge deal though.
|
|
Use 'optparse-applicative' to parse command line options.
Provide a `--slack-token` option that sets a Slack API token to enable
access to the chat platform.
More options will come as needed (including things like database name,
language, possibly IRC configuration).
Code is based on the example in
https://hackage.haskell.org/package/optparse-applicative
Pretty cool option parser.
|
|
I had a bunch of code commented from earlier and from false starts when
working on integrating my plugins and the IRC message handler. Remove
these commented lines now as they are no longer relevant. The actual
code is functioning.
|
|
Instead of sending a hard-coded string message over IRC, now invoke our
plugin list.
Now, IRC messages get matched against our plugin list, and if any plugin
matches, its return string gets posted to the appropriate channel.
Move the database connection inside the plugin so that we can continue
to access it without having to pass in the DB connection. I didn't want
to connect to the database in the IRC code because it doesn't relate,
and I don't have a good enough grasp of monads to know if I can create
the connection in "Lib.hs" and "pass" it to the IRC message handler to
then pass it into the plugins.
Moving the database connection inside the plugins means that it no
longer takes a database connection as an argument.
The IRC message handler now has a lot of duplication, but it basically
works, which is super exciting! Took a _long_ time to figure out the
proper way to get the types to line up and get this compiling, but
really glad it's working now! The magic function turned out to be
`liftIO` to extract the `Either String String` out of its `IO` monad
wrapper to be fed to the `IRC.send` function.
Not a fan of the type conversion between `String` and `Data.Text`, so
we'll eventually have to convert our plugins to use `Data.Text`.
|
|
Now that we have something sort of working on the IRC side of things, we
can move it out into its own module to keep `Lib` clear and tidy.
|
|
We don't need this information. I was just saving it to find out what
that argument actually was.
|
|
Instead of always responding on a hard-coded channel, the bot now
responds on the channel the PRIVMSG was received on. This can be either
a regular channel or a query message.
The `serv` argument is apparently the message sender's hostname. Wanted
to print it out to see what that argument was.
I had been following this example from "barrucadu"'s 'yukibot':
https://github.com/barrucadu/yukibot/blob/31930b234eb423ed74546b56ada100105c1680ce/yukibot-backend-irc/Yukibot/Backend/IRC.hs#L152-L156
but the code ignored the first `Event` argument. In order to send it as
a chat message, needed to convert it to a `Data.Text`, using the method
courtesy of this tutorial:
https://haskell-lang.org/tutorial/string-types
|
|
Add an event handler that gets called when a message is posted to a
channel the bot is on. When a message is posted, the bot will send a
chat message containing the text "test".
This tests out the message handling and posting mechanism, and gives us
a place to build off of to allow the bot to communicate.
|
|
Make it a bit more obvious where these functions are coming from by
prefixing them with `IRC.`.
|
|
Add the 'irc-client' package to facilitate communication over IRC.
Copy the example from:
http://hackage.haskell.org/package/irc-client-0.4.4.4/docs/Network-IRC-Client.html
which connects to a network. We specify a channel to join, and what do
you know, it works! Pretty cool.
Commented out the code that runs the GitHub Commit plugin for now while
testing this.
Add the `OverloadedStrings` extension so we can write string literals
but have them converted into the appropriate `Data.Text` and
`Data.ByteString` types required by the IRC library.
|
|
It's duplicated so I figured I'd take it out.
|
|
Get rid of the `fail` and use an `Either` for error handling instead.
This allows us to send back the response string like before, but provide
an additional error message when no data comes back from the database
query.
Remove the old error handling code I had tried to set up previously.
|
|
This doesn't really work, but I wanted to hold onto this step. I've been
reading about error handling, notably this article:
http://www.randomhacks.net/2007/03/10/haskell-8-ways-to-report-errors/
I kind of like the idea of just using `fail`, and being able to have
that set up some polymorphism to handle `Maybe`, `Either`, and `IO`
situations, but today read some literature that discouraged using fail,
since not all monads implement it.
Anyway, this code doesn't print out the error string like I intended it
to, so I guess I'll have to use `Either` instead.
|
|
The GitHub Commit plugin needs access to the database in order to work
properly. Include 'sqlite-simple' to give us access to the database and
have it transform rows into Haskell objects.
This change has the unfortunate effect of forcing us to make
`PluginAction` an IO type. This means we'll need to make all our plugin
action functions use IO, even the pure ones. `PluginAction` now takes a
database connection as a second argument, and returns an `IO String`.
I don't like the fact that the database argument is effectively
hard-coded. Thus the TODO note to try to make a type class to replace it
so we can pass a null database connection when it isn't needed.
Eventually, if more things like this need to be passed into the
function, we might consider making a new struct type for the purpose.
In order to be able to use the `query_` function, which takes a
String-like `Query`, we have to declare `OverloadedStrings`. Now in
`gitHubCommitAction` we take the database connection as an argument,
select a row (for now it's always the first one to test this out), and
send back a GitHub commit URL (if a record matched, otherwise return an
empty string). Eventually we'll want to make this more real by selecting
the row corresponding to the channel in `message`. Also, instead of
returning an empty string, we should be returning an `Either`, so the
error state is clear.
Update the `ChannelRepoUrl` data constructor to use record syntax so we
can name its fields.
|
|
Have `PluginAction` functions take a Message type instead of a plain
string. This gives us access to additional fields on the message:
channel and nick.
sorbot.cabal:
Add `Message` to `exposed-modules` in order to be able to use it when
building.
Lib.hs:
Change our test message to be a `Message` data type to conform to the new
`PluginAction` interface.
Plugin.hs:
Use `Message` where appropriate. When calling `perform`, pass it a
`Message` instead of a `String`. This means we have to match the regex
within the plugin in order to get the match data. The benefit of that
change is that now we have access to the full message in the plugin if
we need it, not just the regex-filtered part.
GitHubCommit.hs:
Do a regex match against the Message text in order to get the SHA we
want from the message.
|
|
We previously removed the call to `=~` that we needed this import for in
4bb65c50a2a85404af6d122acc53b6fb1739652b.
|
|
I didn't even need the `matchPlugin` that took `plugins` as an argument,
I can just use the `plugins` function directly in `matchPlugin`. Get rid
of `realMatchPlugin` because that was just a temporary name until I got
things working.
Move `firstPlugin` into the definition of `matchPlugin` because it's not
needed anywhere else and is just used to pattern match for the first
matched plugin in the list.
|
|
I had been using the regex instead of the match result. Do another regex
match to correctly output our SHA instead of the match regex.
I had tried putting the `=~` match in another function and using it from
here and in the list comprehension in `matchPlugins`, but then found out
that, of course, the return types didn't agree (`matchPlugins` needs a
`Bool` and `performPlugin` needs a `String`). Ended up dispensing with
the extra function and doing the match both times instead.
|
|
Take our initial experiment on the regex matcher from before and expand
it into the beginnings of a plugin architecture.
Add a new `Plugin` module. This exports a `Plugin` data type that
collects a regex and a function together. The idea will be to create a
list of plugins, and chat messages will get matched against the regexes
in that list. If the regex matches, the associated function will be run.
If the function produces output, that output should eventually be sent
back to the chat as a message.
Implemented `Show` on `Plugin` manually because the `String -> String`
function can't be derived. Decided to forego that part in the output and
only show the regex when printing.
Ended up with a little redundancy here in the functions for matching
plugins. The `realMatchPlugin` function needs to be renamed, just called
it that for now because I had started with `matchPlugin`, and only later
realised that its interface wasn't what I needed when calling it from
the `Lib` module.
Need to add some doc comments, but figured I'd commit what I have now
since it sort of works.
The `firstPlugin` function is only used by `matchPlugin`, so now I
realise it should probably be a local `where`-defined function.
Also thought the `String -> String` wasn't very descriptive. We'll want
to make a type alias for that that tells people this is a plugin
function.
Added a test plugin inline in this file for now. Eventually our plugins
should be stored in separate files in a "Plugins" directory (or
"Plugin", depending on how the module system works, will have to look
into that). For now, the program outputs the correct string created by
`gitHubCommitAction`, which is actually pretty cool. It's not actually
the really correct string, as the `match` variable is the regex instead
of the matched part of the test string, but close enough for now. We'll
go back and correct that momentarily.
Needed to add `Plugin` to the `exposed-modules` section in the `library`
build metadata in order to properly build the code. Was getting this
error about it:
Warning: The following modules should be added to exposed-modules or other-modules in /Users/tw/Documents/Development/sorbot/sorbot/sorbot.cabal:
- In the library component:
Plugin
Missing modules in the cabal file are likely to cause undefined reference errors from the linker, along with other problems.
Really liking this so far!
|
|
Only match SHAs if they're the full 40 characters long. This will become
important later when we have an actual chat bot and we don't want to
match on partial SHAs.
|
|
* Add the 'regex-tdfa' library for RegEx handling
* Experiment with matching a Git SHA
|
|
$ stack new sorbot new-template
GHC v8.0.2
|