Building an IRC Bot with Cinch

Tell Your API

At its core, the plugin communicates with the GitHub API. It retrieves the data via HTTP and translates JSON into Ruby structures. Since the standard libraries of Ruby provide everything you need, the implementation is easy.

Lines 7-10 in Listing 3 handle the configuration of the plugin. BaseURL is the base address of the GitHub API and requires some adjustment for in-house installations. This entry is followed by the Username and Password for the bot on GitHub. The account must be authorized to access tickets in repositories, but it should not have more rights than necessary.

Listing 3

issues.rb

01 require "json"
02 require "net/http"
03
04 class GithubIssues
05     include Cinch::Plugin
06
07     BaseURL      = "api.github.com"
08     Organization = "<Organization>"
09     Username     = "<username>"
10     Password     = "<password>"
11
12     private
13     def request(uri, method, data = nil)
14         uri = URI("https://#{BaseURL}#{uri}")
15         Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
16             req = method.new(uri.request_uri)
17             req.basic_auth(Username, Password)
18             req.body = data
19             resp = http.request(req)
20             return JSON.parse(resp.body)
21         end
22     end
23 end

To simplify the interaction with the bot, the assumption is that all the repositories at GitHub belong to a single Organization. The request() method in line 13 is used by all of the plugin's functions to make requests to the API. request() issues an HTTP request and converts the response, which is formatted in JSON, to a structure that Ruby understands. The method argument is used to specify the HTTP command. GitHub uses, for example, GET to retrieve data and PATCH to edit data.

The plugin's functions are restricted to calling this method along with the right arguments. You then extend the GithubIssues class with the methods from Listings 4 through 6 in the file issues.rb. The first two functions, which open and close tickets, essentially consist of the call to the correct request() method.

Listing 4

Supplement 1 for issues.rb

01 # !gh issue open <Repository> <Ticket_number>
02 match(/gh issue open ([^ ]+) (\d+)$/, method: :open_issue)
03 def open_issue(m, repo, id)
04     uri = "/repos/#{Organization}/#{repo}/issues/#{id}"
05     request(uri, Net::HTTP::Patch, '{"state": "open"}')
06     m.reply "Opened issue %s/gh-%d" % [repo, id]
07 end
08
09 # !gh issue close <Repository> <Ticket_number>
10 match(/gh issue close ([^ ]+) (\d+)$/, method: :close_issue)
11 def close_issue(m, repo, id)
12     uri = "/repos/#{Organization}/#{repo}/issues/#{id}"
13     request(uri, Net::HTTP::Patch, '{"state": "closed"}')
14     m.reply "Closed issue %s/gh-%d" % [repo, id]
15 end

Listing 5

Supplement 2 for issues.rb

01 # !gh issue search <Repository> <search_string>
02 match(/gh issue search ([^ ]+) (.+)/, method: :search_issue)
03 def search_issue(m, repo, query)
04     uri = "/search/issues?q=%s+repo:%s+user:%s" %
05         [URI.escape(query), repo, Organization]
06
07     res = request(uri, Net::HTTP::Get)
08     n = 3
09     total = res["total_count"]
10     if n > total
11         n = total
12     end
13
14     m.reply "Showing %d of %d tickets for '%s'" % [n, total, query]
15     res["items"][0...n].each_with_index do |issue, i|
16         # [123] Ticket title <https://github.com/...> (open)
17         m.reply "[%d] %s <%s> (%s)" %
18             [issue["number"], issue["title"], issue["html_url"], issue["state"]]
19     end
20 end
21

To search for tickets (Figure 3), the developer needs to do some more work. Although the actual search consists of one simple call to the request() method, the code needs to present the search results appropriately. The program should not show all the results – there could be up to 100 – and it should show only the relevant information for each ticket, such as the ticket number, the title, the link to the ticket, and the status (open or closed).

Figure 3: The bot scanning the GitHub ticket system for specific tickets.

The final feature (Listing 6) finds all references of the type Repository/gh-Ticket_number in chat messages and outputs the title and status of the referenced tickets. Ruby's scan() method finds all references to a message so that a single message can also cover several tickets (Figure 4). The special use_prefix: false option ensures that the bot does not look for an exclamation mark at the start of the command.

Listing 6

Supplement 3 for issues.rb

01 # <Repository>/gh-<Ticket-number>
02 match(/[^ ]+\/gh-\d+/, method: :display_issue, use_prefix: false)
03 def display_issue(m)
04     m.message.scan(/([^ ]+)\/gh-(\d+)/) do |repo, id|
05         uri = "/repos/#{Organization}/#{repo}/issues/#{id}"
06         issue = request(uri, Net::HTTP::Get)
07         m.reply "[%s/gh-%d] %s (%s)" % [repo, id, issue["title"], issue["state"]]
08     end
09 end
Figure 4: When you enter a ticket number, the bot returns the matching titles.

Caveats

The plugin developed here makes it much easier for developers and admins to work through IRC chatrooms by performing targeted searches for information about certain tickets. However, many improvements are possible. For example, the plugin assumes that the repositories and tickets exist and that GitHub's API never has any problems. In short, the plugin does not provide error handling. Additionally, the number of requests to GitHub's API is limited to 5,000 per hour. Very active projects and large companies would have to cache requests or otherwise comply with the limits.

What the Bot Knows

Of course, logs of the conversations on IRC can be referenced in case of questions. It is also possible to collect FAQ knowledge on IRC and retrieve this information as needed. Bots can manage the FAQ. Inspired by Infobot [6] the electronic aids connect snippets of text – such as links or references – with retrievable keywords. If the developer saves these questions and answers in a compact SQLite database, other tools can access the data.

The implementation of the knowledgebase function is similar to that of the GitHub link. The task starts with establishing a database connection, and the actual functions follow. Install the database with:

gem install sqlite3

Listing 7 opens a connection to the SQLite database, which is located in the home directory of the user. The bot developer's plugin creates a table with three columns for the ID, the keyword, and the associated information, assuming this table does not yet exist. The plugin is then used without any manual intervention.

Listing 7

infobot.rb

01 require "sqlite3"
02
03 class Infobot
04     include Cinch::Plugin
05
06     DB = ENV["HOME"] + "/infobot.db"
07
08     def initialize(*args)
09         @db = SQLite3::Database.new(DB)
10         @db.execute(
11             "CREATE TABLE IF NOT EXISTS infobot(
12             id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
13             term TEXT NOT NULL,
14             value TEXT NOT NULL
15         )")
16         super
17     end
18 end

Listing 8 implements two methods for creating and retrieving information. The first method, remember(), is always used when somebody uses !rem keyword = information to feed new snippets of information to the bot – or to overwrite existing information and thus correct an error.

Listing 8

Supplement for infobot.rb

01 # !rem keyword = information
02 match(/rem ([^ ]+) = (.+)$/, method: :remember)
03 def remember(m, term, val)
04     @db.execute("INSERT OR REPLACE INTO infobot (term, value) VALUES (?, ?)", term, val)
05     m.reply("Okay, #{term} now means #{val}")
06 rescue => e
07     exception(e)
08     m.reply("Error remembering '#{term}'")
09 end
10
11 # !<keyword>
12 match(/([^ ]+)$/, method: :lookup)
13 def lookup(m, term)
14     res = @db.get_first_value("SELECT value FROM infobot WHERE term = ?", term)
15     if res
16         m.reply(res)
17     end
18 end

The second method, lookup(), activates the bot when it sees a message of the !keyword type. If lookup() recognizes the keyword, it outputs its meaning; otherwise it remains silent. A typical interaction with the bot will look something like Listing 9, which demonstrates how IRC users store and retrieve information. Because the data also resides in a schematically simple SQLite database, access to knowledge is not limited to the bot. A console client could list the keywords, and data could be imported from existing sources, such as wikis. However, whole paragraphs tend to get in the way on IRC. Infobots are intended to output short sentences but not novels.

Listing 9

Knowledgebase on IRC

01 <Max> !rem deploy = Pay attention to X and Y when deploying
02 <Susanne> !rem github_api = http://developer.github.com/v3/
03 [a few days later]
04 <Max> Hmm, where was the GitHub API documentation ...
05 <Max> !github_api
06 <Bot> http://developer.github.com/v3/

Buy this article as PDF

Express-Checkout as PDF
Price $2.95
(incl. VAT)

Buy Linux Magazine

SINGLE ISSUES
 
SUBSCRIPTIONS
 
TABLET & SMARTPHONE APPS
Get it on Google Play

US / Canada

Get it on Google Play

UK / Australia

Related content

  • Trouble Ticket Software

    If your help line serves outside users, keeping track of support requests can mean the difference between a repeat customer and a lost customer. If the line serves inside employees, an efficient response means better productivity. Fortunately,several Linux-based applications offer help for your help desk or hotline.

  • Perl: CMS with GitHub

    With its easy-to-use web interface, GitHub can be put to totally different uses than archiving code. For example, Perlmeister Mike Schilli used GitHub to deploy a content management system for simple websites.

  • GLPI

    Anyone working in information technology knows how hard it is to keep track of inventory, maintenance history, and user support requests. GLPI puts the details in reach.

  • Perl: Travis CI

    A new service on travis-ci.org picks up GitHub projects, runs new code through test suites, and notifies the owners if the build fails. Its API enables Perl scripts to gather historical build data, including who-broke-the-build tabulations.

  • Linux with Active Directory

    Microsoft's Active Directory system provides centralized user management and single sign-on. If you're ready for a few manual steps, Linux can leverage this potential.

comments powered by Disqus

Direct Download

Read full article as PDF:

Price $2.95

News