aboutsummaryrefslogtreecommitdiffstats
path: root/src/Plugin
AgeCommit message (Collapse)Author
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-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-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-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-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-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-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-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-19I18n.hs: Implement custom i18n using Haskell data typesTeddy Wing
Follow the example https://wiki.haskell.org/Internationalization_of_Haskell_programs_using_Haskell_data_types to get i18n. Get rid of the Shakespeare-I18N code. The idea of using text files to define translation strings was nice, but since I couldn't get it working with ease and couldn't find examples outside of a Yesod context, I decided to chuck it and go with the alternative, simpler approach. Really liking this system. Simplified things a bit from the example since we only need to show one language at a time. Will need to figure out how to use the same language throughout the program.
2017-08-19Try to get I18n workingTeddy Wing
An unsuccessful attempt at getting i18n working through Shakespeare. Had a little help from: https://gist.github.com/nicolashery/f87467fb37da2b00cec1eed028f51e60 Unfortunately, I can't figure out how to get this working. Even before, without the locale stuff and defining a custom `translate` function, when I tried using Shakespeare's `_{}` function, I was, and still am, getting this error: src/Plugin/GitHubCommit.hs:38:29: error: Data constructor not in scope: MsgGitHubCommitRepoURLNotFound Yes, obviously, that makes sense. But how do I get it to be in scope if it isn't even in my code? What? I'm giving up on Shakespeare for i18n. It was a nice idea, I liked the fact that strings were stored in external files, but I don't know how to do this, I can't find the right resources online to use this in a non-web non-template context, and it's becoming a pain. Going to get rid of all this code and just use regular Haskell data types to do it as described in https://wiki.haskell.org/Internationalization_of_Haskell_programs
2017-08-19Set up i18nTeddy Wing
en.msg: * Remove comment lines because apparently comments aren't allowed by the parser * Change data type to `T.Text` to match the import from the `I18n` module GitHubCommit.hs: Import the `I18n` module because we'll be testing it in context in a bit, but for now just want to get the module code to compile. I18n.hs: Follow the example on http://www.yesodweb.com/book/internationalization to make a function for proper internationalisation.
2017-08-17gitRemoteSetOriginAction: Make response more descriptiveTeddy Wing
Instead of just responding with the given URL, include a message in English to let users know that the value was saved.
2017-08-17gitRemoteSetOriginAction: Upsert instead of insertTeddy Wing
I had used `INSERT` as a placeholder while trying to get the plugin working properly. This would create a row every time the "git remote set origin" command was invoked. What I really wanted was an upsert. Looked through a number of different ways of accomplishing that in SQLite: - https://stackoverflow.com/questions/418898/sqlite-upsert-not-insert-or-replace/7511635#7511635 - https://stackoverflow.com/questions/15277373/sqlite-upsert-update-or-insert Ended up settling on this solution from CL.: https://stackoverflow.com/questions/20323174/upsert-in-sqlite/20326705#20326705 It seemed to be pretty clean and understandable, so I leveraged that approach.
2017-08-17gitRemoteSetOriginAction: Use regex capture groupTeddy Wing
Finally figured out how to get a capture group out of the regex match. Needed to coerce as a two-dimensional `String` list. Thanks to: - https://stackoverflow.com/questions/24699279/cant-capture-a-group-in-a-string - https://stackoverflow.com/questions/6729158/find-all-capturing-groups-of-a-regular-expression Get the captured group and set it to the URL to insert into the database. It lives in the second element of the first list: Prelude Text.Regex.TDFA> "git remote set origin https://example.new" =~ "^git remote set origin ([^ ]+)$" :: [[String]] [["git remote set origin https://example.new","https://example.new"]] Prelude Text.Regex.TDFA> "" =~ "^git remote set origin ([^ ]+)$" :: [[String]] []
2017-08-17Add GitRemoteSetOrigin pluginTeddy Wing
This plugin provides a command to set a git commit repo URL for use with the `GitHubCommit` plugin. Typing git remote set origin URL in chat will set that URL to the current channel. Problems: * Can't figure out how to use capture groups, so the entire matched message string comes back, not just the URL * Need to upsert instead of insert into the database
2017-08-17Message: Add a function to get the `text` field as a stringTeddy Wing
Clean up some of the calls to get the `text` field by adding a function that abstracts the call to `Data.Text(unpack)`.
2017-08-17Change `Plugin` and `Message` from `String` to `Data.Text`Teddy Wing
Use the `Data.Text` type instead of `String` in most of the places we use it in `Plugin` and `Message`. This allows us to more easily pass data between the IRC package. No more kludgy `pack`s and `unpack`s in our IRC message handler. The one thing we couldn't convert was our regex. From what I understand (https://stackoverflow.com/questions/14922579/haskell-regular-expressions-and-data-text#14922626), the regex library I'm using doesn't support `Data.Text`, so use `String`s for that instead.
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-11gitHubCommitAction: Add missing period to error messageTeddy Wing
2017-08-11gitHubCommitAction: Fix whitespaceTeddy Wing
The `=` was previously aligned with the function definition below, but since the pattern match of the lower function definition changed, the `=`s were no longer aligned. Seems like it would be weird to move the `=` out and create a bunch of blank space to align the two `=`s, so moving the function body to the next line.
2017-08-11gitHubCommitAction: Ignore unused variableTeddy Wing
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-03GitHubCommit.hs: Only select `repo_url` column from databaseTeddy Wing
We don't need to select the channel because we don't do anything with it. All we need is the repo URL. Create a new data type for use with the database query that allows us to select only the URL. Remove the `ChannelRepoUrl` type as it's no longer needed.
2017-08-03GitHubCommit.hs: Select row corresponding to correct channelTeddy Wing
Ask for the row corresponding to the channel received from the chat message. This gets rid of the previously hard-coded SQL and allows us to dynamically get the right URL value.
2017-08-02GitHubCommit.hs: Remove progress development commentsTeddy Wing
Added these while developing ff00355ade021d3e06d55017c5337f488474e5cb to test out different theories while trying to get the project to compile. These are no longer relevant any more, so delete them.
2017-08-02ChannelRepoUrl: Get rid of the `id` fieldTeddy Wing
We're not using it, and originally I didn't even put it in my SELECT query (which actually messed me up for a couple hours, as two columns in the SELECT does not match three columns in the data type). Added it later as a fix for that problem. But since I don't really want or need to select the id, we shouldn't bother, and in order to do so, we need to update the Haskell data type to only include two fields, so sqlite-simple can correctly convert the data.
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-02GitHubCommit.hs: Make types of `ChannelRepoUrl` more specificTeddy Wing
Define some extra types in `ChannelRepoUrl` because I didn't like the generic ones.
2017-08-02GitHubCommit.hs: Add `ChannelRepoUrl` database typeTeddy Wing
A type that will be used in conjunction with 'sqlite-simple' to read values from the plugin's database table.
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.