aboutsummaryrefslogtreecommitdiffstats
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-11-19Bot(privmsgFromPlugin): Try to use the `Bot` monad instead of `IO`Teddy Wing
Convert this function and `Plugins(performPlugin)` to use a `Bot`-wrapped plugin. Still having a lot of trouble with this, unable to get past monad-related compilation errors. The plan is to leave this as-is for now and attack the problem from a different place, looking at the `main` function next, applying our plugins with `runBot`, and then passing those monad-wrapped plugins to `connectIRC`. Once that's taken care of, we'll see what we need to modify in this middle strata of the stack.
2017-09-19Plugin(matchPlugin): Get our functions to actually work with `Bot`Teddy Wing
Previously we added the `Bot` monad to our signature, but we didn't have a way to filter and match plugins wrapped in the `Bot` monad. This is what we're doing here. It's the same work as before, but we need to account for a wrapped `Plugin` type. There's a lot of extra cruft code here from when I was trying things out to get it working. The short story is that I do some binding and sequencing dances to extract `Plugin`s so that we can actually match against them. I've seen recommendations against `fail`, which is why I tried to use `mzero`, but it just seemed to be too complicated so went with `fail`, which seems to make sense in context.
2017-09-15Plugin: Update plugin list with new wrapped `Bot` typeTeddy Wing
Since our plugins are now wrapped in the `Bot` monad, the plugin list's type needs to change. Also update `matchPlugin` to return a `Bot Plugin`.
2017-09-14Help: Fix type error resulting from new plugin list type with `Bot`Teddy Wing
The plugin list's type has changed from `[Plugin]` to `[Bot Plugin]`. In order for our code here to work, we need to unwrap the `Bot` part so we can get access to the `Plugin` again and do the same work. Fortunately, using `sequence`, we can turn our `[Bot Plugin]` into a `Bot [Plugin]`, which is then easily bound with `<-` and passed to our old code to be handled like before. Yay!
2017-09-14Add `Bot` to rest of pluginsTeddy Wing
Add our `Bot` monad to the rest of the plugins: * Factorial * GitRemoteSetOrigin * Help The only problem is with the Help plugin. Still trying to figure out how to set up my list comprehension so that it works with the `Bot`-wrapped `Plugin` list.
2017-09-13Add ConfigTeddy Wing
An old config object, from before I decided to go with the `Bot` type. Committed here just to show some of the process. We'll be removing it soon.
2017-09-13gitHubCommit: Fix type errorTeddy Wing
Oh my goodness, it works!!!!!!! I've been wrestling with this for the past week and a half, and it finally works! Here's the compiler error I was getting: sorbot/src/Plugin/GitHubCommit.hs:25:12: error: • Couldn't match type ‘Control.Monad.Trans.Reader.ReaderT r0 m1’ with ‘Bot’ Expected type: Bot t0 Actual type: Control.Monad.Trans.Reader.ReaderT r0 m1 t0 • In a stmt of a 'do' block: cfg <- asks language In the expression: do { cfg <- asks language; return (defaultPlugin {matchRegex = "^[0-9a-f]{40}$", perform = gitHubCommitAction, command = "<git_sha>", description = translate (lang cfg) GitHubCommitDescription}) } In an equation for ‘gitHubCommit’: gitHubCommit = do { cfg <- asks language; return (defaultPlugin {matchRegex = "^[0-9a-f]{40}$", perform = gitHubCommitAction, command = "<git_sha>", description = translate (lang cfg) GitHubCommitDescription}) } The problem ended up being that I was using the wrong `ask` & `asks` functions. After a lot of searching online and reading articles and things about how to set up `ReaderT` configuration, I finally started looking at some code, from a "language:Haskell newtype app readert" search on GitHub. Reading different code bits, I tried to compare with what I had already, kept reading. Finally I noticed that the imports were different, and I tried getting `ask` not from the transformer library, and it worked!! OMG! So glad.
2017-09-13GitHubCommit: Use `Cli.language` to get languageTeddy Wing
Since we're trying to get the language from an `Options` type in the reader, we need to use the `language` helper instead of our custom `lang` (which operates on `IO`).
2017-09-13Bot: Try adding a BotConfig typeTeddy Wing
Tried adding this to replicate some code I had been reading, but ultimately it didn't really change anything. In the end I don't need this type, at least not right now, but it was an interesting exercise I suppose.
2017-09-13gitHubCommit: Remove unnecessary `$`Teddy Wing
2017-09-13gitHubCommitAction: Didn't need `liftIO` hereTeddy Wing
Added this for whatever reason trying to get types to line up, but it's not necessary. In fact, I didn't even really notice it as I was bothered by another compiler error, but 'monochrom' on Freenode#haskell pointed it out, thanks.
2017-09-13GitHubCommit: Try to add `Bot` monadTeddy Wing
This is an attempt at wrapping the functions in this plugin with the new `Bot` monad. It didn't go very well. These changes are the result of some initial code and then a lot of fiddling to try to resolve compiler errors, many of which are still present. The ultimate goal is to be able to wrap the functions in `Bot` so that we can use the language configuration option to determine which translation to use. Thanks to 'dmwit' on Freenode#haskell for looking at my code and giving me suggestions for my type errors.
2017-09-11Plugin.Base: Make `PluginAction` return a `Bot` monadTeddy Wing
Replace the `IO` return monad with `Bot`. `Bot` wraps the `IO` monad so it should cover us for that, while additionally providing configuration to plugin action functions. There's also some extra cruft here from when I was experimenting. The commit before this one and the next one or more were all written at once in a jumbled mess. All the extra code is collateral from that.
2017-09-11Create `Bot` typeTeddy Wing
This type will be used as an application-level monad, enabling us to transmit configuration coming from CLI arguments (`Options`) to any function that needs it. The immediate benefit is that we'll be able to easily transmit the specified language to functions to allow them to use the right translation string.
2017-08-27privmsgFromPlugin: Rewrite using MaybeT monad transformerTeddy Wing
OMG it works!!! This took me a week of off-and-on tinkering, learning, procrastinating, and struggling. So awesome that it's finally working! The `liftMaybe` was key, learned that from: https://stackoverflow.com/questions/8684252/how-to-inject-a-maybe-value-into-maybet This article on monad transformers by 'kqr' also helped quite a bit: https://github.com/kqr/gists/blob/master/articles/gentle-introduction-monad-transformers.md A Wikibooks article on the same was somewhat useful: https://en.wikibooks.org/wiki/Haskell/Monad_transformers And this was an interesting look at Alternatives, which I didn't end up using but was interesting nonetheless: http://www.parsonsmatt.org/2016/11/18/clean_alternatives_with_maybet.html When I learned about monad transformers I thought: "Yes! This is what I need to clean up the `privmsgFromPlugin` function.". I wasn't liking the nested `case` statements there, and since everything was a `Maybe`, I thought, why can't we chain the `Maybe`s like we're supposed to be able to with monads? Well, turns out we can. It just involved a lot of tricky `lift`ing. This gets rid of the nesting, resulting in a much cleaner- looking function. So cool!
2017-08-20Use explicit imports when importing pluginsTeddy Wing
Going for readability in explicitness.
2017-08-20Update TODOTeddy Wing
2017-08-20GitHubCommit: Remove the `Id` typeTeddy Wing
We're not using this any more so it can be removed. I had added when I was originally experimenting with getting values from the database and had been selecting the `id` field.
2017-08-20GitHubCommit: Extract language handling to a functionTeddy Wing
Move the CLI option extracting to a new function that moves the option parsing logic outside the plugin.
2017-08-20GitHubCommit: Make localisation workTeddy Wing
Get the language from the command line options and use it to determine which localised string to return on error. Wooo!! it works! So cool. Add French translations for our existing strings. The code to get the language option is pretty messy for the moment. I want to factor it out into its own separate function. But at least it's working now.
2017-08-20CliOptions: Parse the `language` option into a `Locale`Teddy Wing
Instead of parsing `--language` as a string, parse it as a real `Locale` type. This means the parsing is taken care of for us at the CLI option handling stage instead of later on. We can thus use the language value more quickly and easily, passing it to the `translate` function, which takes a `Locale`.
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-20Update TODOTeddy Wing
Not doing plugin-specific support for the "sorbot: " prefix because for now I think all the plugins should be prefixed.
2017-08-20IRC: Only require "sorbot: " prefix in channelsTeddy Wing
When talking with the bot in a private one-to-one query, there's no reason to use the "sorbot: " prefix because you're not talking to anyone else. In that case, just behave like we did before and allow users to send commands with no prefix.
2017-08-20IRC: Only respond to messages if prefixed with "sorbot: "Teddy Wing
Prevent Sorbot from responding to general messages. Messages must be directed at Sorbot to invoke plugins and get a response.
2017-08-20Update TODOTeddy Wing
2017-08-20IRC: Send private messages when plugin is `queryOnly`Teddy Wing
If the plugin is defined as `queryOnly`, the response shouldn't be sent on the channel to everyone, it should instead be sent directly to the user in a private query message. Enable this functionality for the Help plugin so that help output only gets sent to the user requesting help. This ensures other channel participants don't get an annoyingly long section of output that they didn't ask for.
2017-08-20Plugin: Add `queryOnly` fieldTeddy Wing
A new field that says whether this plugin should only respond via a private query message to the user instead of responding on the channel the message was sent from. This is needed for the Help plugin, which shouldn't flood channels with lots of extraneous output. Instead, the Help plugin should send the list of commands directly to the user. Since most of the time we don't want this behaviour, encode a default of `False` on the field so that most plugins don't have to define it manually. This necessitates changing the constructors to use the default `Plugin` instead.
2017-08-20Factorial: Include upper bound in descriptionTeddy Wing
Let users know how much factorial they can calculate.
2017-08-20Update TODOTeddy Wing
2017-08-20IRC: Speed up message transferTeddy Wing
Instead of waiting 1 second between posting messages, pause 0.2 seconds for faster posting. This may cause flood concerns, but improves responsiveness from commands that send multi-line responses, like Help and Factorial.
2017-08-20IRC: Chop messages longer than 400 charactersTeddy Wing
The IRC protocol doesn't permit messages longer than 512 bytes (https://news.ycombinator.com/item?id=7991049). This includes server, channel, hostname, etc. information that's also included in the message. In order to be able to send longer messages, chop them at 400 characters (https://wholok.com/irc/), which seems like a "reasonable" hard-coded value. We already split our messages at newlines, so we need to effectuate a double split here. First we split at 400 characters because we might have a line that runs longer than that before encountering a newline. To do this, we use the `chunksOf` function (https://wiki.haskell.org/Data.List.Split). Then, when splitting on newlines, we take the list produced by splitting at 400 characters, and must split the elements inside that list. This creates a two-dimensional array. In order to flatten the array, we use the `concat` function (https://stackoverflow.com/questions/5994051/is-there-a-function-to-flatten-a-nested-list-of-elements).
2017-08-20Factorial: Use `Integer` instead of `Int` for bigger numbersTeddy Wing
The `Integer` type has arbitrary precision. This is why the function was working fine in GCHi but overflowing when I was testing the bot. Use `Integer` to get the real values we want. https://stackoverflow.com/questions/36355757/large-numbers-become-negative-in-haskell Also add a little extra error handling to deal with unexpected numbers. Now that I think about it, the regex parser isn't going to handle negative numbers, so we're good on that front. Set an upper bound so we don't end up spending a lot of time trying to calculate numbers and potentially segfault if huge numbers are given.
2017-08-20Factorial: Use `Integral`Teddy Wing
Apparently that's short for `Enum a, Num a`.
2017-08-20Add Factorial pluginTeddy Wing
This plugin calculates factorials. It's not super optimised, but works for small inputs. Adds the 'text-show' plugin to convert the resulting integer to a `Data.Text` to be returned by the plugin action. At first I had tried /^\d+!$/ for the regex match, but that produced this error: sorbot/src/Plugin/Factorial.hs:14:24: error: lexical error in string/character literal at character 'd' Tried to fix that by using /^\\d+!$/, but that didn't match anything. Then tried a long-form character class, /^[:digit:]+!$/, which TDFA implies it supports, but a quick look at the code seems to indicate that those parts were removed or commented out. Not sure what was going on with that. So instead went for the remaining solution, a `0-9` character class.
2017-08-20Add TODOTeddy Wing
2017-08-20Move `PluginList` out of `Plugin` directoryTeddy Wing
Asking for `Plugin.Plugin...` is redundant. Take this module out of the `Plugin` directory to eliminate the repetition in naming.
2017-08-20Add TODOsTeddy Wing
2017-08-20Update TODOTeddy Wing
2017-08-20GitRemoteSetOrigin: Use lowercase bracket format in command helpTeddy Wing
Follow the same format used in 08077a854a63884de619034d6762aa5b18ed2f91 with the argument description in lowercase surrounded by angle brackets.
2017-08-20Help: Right-align command namesTeddy Wing
To aid the readability of the help output, align the dashes vertically so the descriptions are in a uniform place. The resulting output now looks like this: <git_sha> – Generate a commit URL based on the given SHA. git remote set origin URL – Set the git remote URL for this channel. help – Show a list of available bot commands.
2017-08-20GitHubCommit: Change `command` formatTeddy Wing
Use lowercase and brackets instead of a shell-style capitalised identifier. In the help output, this looks better to me. Inspired by Hubot.
2017-08-20Reformat Help outputTeddy Wing
Instead of printing all help text on a single line as a result of joining the list of help text, print each plugin's help on a separate line. Also separate commands from descriptions with a dash instead of a tab as the tab character was getting rendered as an `I` in irssi instead of the actual whitespace I had been hoping for. The dash in inspired by Hubot. In order to print multiple lines of output, we needed to change the IRC PRIVMSG handler. This now splits the plugin result at newlines into a list and sends separate PRIVMSGs for each line of output. Before, text with newlines would only show the first line in the resulting IRC message. Assume plugin error messages will always be a single line.
2017-08-20Vertically align record fields in pluginsTeddy Wing
Since the style we're following favours vertical alignment, align these record definitions aroung the `=` signs.
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-19IRC.hs: Reformat record update syntaxTeddy Wing
Previously I had moved the `{` to the previous line because I was getting compilation errors with it on a new line. Turns out whitespace is important here. We just need to indent it an extra time and the compiler understands us. Now this section matches the style of the rest of the project.