aboutsummaryrefslogtreecommitdiffstats
path: root/src/Lib.hs
AgeCommit message (Collapse)Author
2017-11-19Lib: Test help output with FR localeHEADmasterTeddy Wing
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.
2017-11-19Lib: Test application of Bot readerTeddy Wing
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.
2017-08-20CliOptions(parseOptions): Return optionsTeddy Wing
Make the parsed options accessible to the rest of the program.
2017-08-20CliOptions: Add command line option for languageTeddy Wing
Provide a way to switch languages when starting the bot.
2017-08-20Add Help pluginTeddy Wing
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.
2017-08-19Add command line option handlingTeddy Wing
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.
2017-08-16Remove commented code from a0d21d9aed3a38e98e622fbae5fcab3e91c5ddeeTeddy Wing
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.
2017-08-16Connect plugins to IRCTeddy Wing
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`.
2017-08-13Lib.hs: Move IRC-related code to a new "IRC" moduleTeddy Wing
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.
2017-08-13dispatchEvent: Ignore user's hostname argumentTeddy Wing
We don't need this information. I was just saving it to find out what that argument actually was.
2017-08-13Lib.hs: Bot now responds on the channel that message came fromTeddy Wing
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
2017-08-13Lib.hs: Test chat message listening and postingTeddy Wing
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.
2017-08-12Lib.hs: Make `Network.IRC.Client` a qualified importTeddy Wing
Make it a bit more obvious where these functions are coming from by prefixing them with `IRC.`.
2017-08-12Set up basic IRC supportTeddy Wing
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.
2017-08-11Lib.hs: Move `putStrLn` outside the `case` statementTeddy Wing
It's duplicated so I figured I'd take it out.
2017-08-10gitHubCommitAction: Get error handling to work using `Either`Teddy Wing
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.
2017-08-10Try to set up error handling for GitHub Commit pluginTeddy Wing
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.
2017-08-02Connect plugins to the databaseTeddy Wing
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.
2017-08-02Change `PluginAction` type to `Message -> String`Teddy Wing
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.
2017-08-02Lib.hs: Remove unused `Text.Regex.TDFA` importTeddy Wing
We previously removed the call to `=~` that we needed this import for in 4bb65c50a2a85404af6d122acc53b6fb1739652b.
2017-07-30Get rid of `realMatchPlugin`Teddy Wing
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.
2017-07-30performPlugin: Use the correct match stringTeddy Wing
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.
2017-07-30Transform regex match into the beginning of a plugin architectureTeddy Wing
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!
2017-07-30Restrict Git SHA regex to full SHAsTeddy Wing
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.
2017-07-30Experiment with 'regex-tdfa'Teddy Wing
* Add the 'regex-tdfa' library for RegEx handling * Experiment with matching a Git SHA
2017-07-16New Haskell Stack projectTeddy Wing
$ stack new sorbot new-template GHC v8.0.2