Age | Commit message (Collapse) | Author |
|
|
|
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!
|
|
Going for readability in explicitness.
|
|
|
|
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.
|
|
Move the CLI option extracting to a new function that moves the option
parsing logic outside the plugin.
|
|
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.
|
|
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`.
|
|
Make the parsed options accessible to the rest of the program.
|
|
Provide a way to switch languages when starting the bot.
|
|
Not doing plugin-specific support for the "sorbot: " prefix because for
now I think all the plugins should be prefixed.
|
|
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.
|
|
Prevent Sorbot from responding to general messages. Messages must be
directed at Sorbot to invoke plugins and get a response.
|
|
|
|
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.
|
|
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.
|
|
Let users know how much factorial they can calculate.
|
|
|
|
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.
|
|
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).
|
|
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.
|
|
Apparently that's short for `Enum a, Num a`.
|
|
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.
|
|
|
|
Asking for `Plugin.Plugin...` is redundant. Take this module out of the
`Plugin` directory to eliminate the repetition in naming.
|
|
|
|
|
|
Follow the same format used in 08077a854a63884de619034d6762aa5b18ed2f91
with the argument description in lowercase surrounded by angle brackets.
|
|
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.
|
|
Use lowercase and brackets instead of a shell-style capitalised
identifier. In the help output, this looks better to me. Inspired by
Hubot.
|
|
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.
|
|
Since the style we're following favours vertical alignment, align these
record definitions aroung the `=` signs.
|
|
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.
|
|
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.
|
|
Get rid of this package because we're no longer using it. Instead we're
going it alone for i18n (dde184a6e24b88f8d6628f4652d5c42ed0f016b2).
Also delete the translation .msg file.
|
|
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.
|
|
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
|
|
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.
|
|
To allow us to take advantage of its `Text.Shakespeare.I18N` feature.
|
|
This will enable us to translate our messages into multiple languages
using Yesod's Shakespeare package.
|
|
|
|
|
|
Instead of just responding with the given URL, include a message in
English to let users know that the value was saved.
|
|
|
|
|
|
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.
|
|
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]]
[]
|
|
|
|
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
|