Skip to content

Commit

Permalink
add web-based API
Browse files Browse the repository at this point in the history
  • Loading branch information
niklasb committed May 27, 2012
1 parent 32a4f40 commit 16279f1
Show file tree
Hide file tree
Showing 8 changed files with 198 additions and 0 deletions.
4 changes: 4 additions & 0 deletions Gemfile
Expand Up @@ -6,5 +6,9 @@ gem "mechanize"
gem "sequel"
gem "sqlite3"
gem "sinatra"
gem "sinatra-contrib"
gem "thin"
gem "json"
gem "haml"
gem "eventmachine"
gem "dynamic_binding", :git => "git://github.com/niklasb/ruby-dynamic-binding.git"
20 changes: 20 additions & 0 deletions Gemfile.lock
Expand Up @@ -9,11 +9,14 @@ GEM
specs:
activesupport (3.1.4)
multi_json (~> 1.0)
backports (2.5.3)
builder (3.0.0)
coderay (1.0.6)
curb (0.7.18)
daemons (1.1.8)
domain_name (0.5.3)
unf (~> 0.0.3)
eventmachine (0.12.10)
feedzirra (0.1.3)
activesupport (~> 3.1.1)
builder (>= 2.1.2)
Expand Down Expand Up @@ -51,6 +54,8 @@ GEM
rack (1.3.5)
rack-protection (1.1.4)
rack
rack-test (0.6.1)
rack (>= 1.0)
rake (0.9.2.2)
rdoc (3.12)
json (~> 1.4)
Expand All @@ -61,8 +66,19 @@ GEM
rack (>= 1.3.4, ~> 1.3)
rack-protection (>= 1.1.2, ~> 1.1)
tilt (>= 1.3.3, ~> 1.3)
sinatra-contrib (1.3.1)
backports (>= 2.0)
eventmachine
rack-protection
rack-test
sinatra (~> 1.3.0)
tilt (~> 1.3)
slop (2.4.4)
sqlite3 (1.3.5)
thin (1.3.1)
daemons (>= 1.0.9)
eventmachine (>= 0.12.6)
rack (>= 1.0.0)
tilt (1.3.3)
unf (0.0.5)
unf_ext
Expand All @@ -74,11 +90,15 @@ PLATFORMS

DEPENDENCIES
dynamic_binding!
eventmachine
feedzirra
haml
json
mechanize
nokogiri
pry
sequel
sinatra
sinatra-contrib
sqlite3
thin
2 changes: 2 additions & 0 deletions config.yml
Expand Up @@ -7,4 +7,6 @@ date_format: '%d.%m.%Y'
time_format: '%d.%m.%Y %H:%M'
stats_url: http://bot.kitinfo.de/stats/%s
source_url: https://github.com/niklasb
api_host: 0.0.0.0
api_port: 1337
database: sqlite://db/kitbot.sqlite3
8 changes: 8 additions & 0 deletions db/migrations/003_create_api_users.rb
@@ -0,0 +1,8 @@
Sequel.migration do
change do
create_table(:api_users) do
String :user, :null => false, :primary_key => true
String :password, :null => false
end
end
end
13 changes: 13 additions & 0 deletions db/migrations/004_webhooks.rb
@@ -0,0 +1,13 @@
Sequel.migration do
change do
create_table(:webhooks) do
primary_key :id
String :user, :null => false
String :url, :null => false
String :channel, :null => false
String :hook, :null => false
String :argument, :null => false
Integer :fails, :null => false, :default => 0
end
end
end
11 changes: 11 additions & 0 deletions kitbot.rb
Expand Up @@ -6,11 +6,13 @@
require 'pry'
require 'sequel'
require 'uri'
require 'thin'

$: << File.expand_path('../lib', __FILE__)

require 'feedwatch'
require 'ircbot'
require 'ircbot/api'
require 'mensa'

unless ARGV.size == 1
Expand Down Expand Up @@ -179,6 +181,9 @@ def format_time(datetime)
end
end

# add webhooks
IrcBot::Webhooks.new(bot, db).register

# start feed watchers in background
$feeds.each do |config|
Thread.new do
Expand All @@ -200,5 +205,11 @@ def format_time(datetime)
bot.start($config['server'])
$config['channels'].each { |chan| bot.join(chan) }

# start API server in background
Thread.new do
Thin::Server.start($config['api_host'], $config['api_port'],
IrcBot::WebRPC.new(bot, db))
end

# start an interactive shell in the main thread :)
binding.pry
105 changes: 105 additions & 0 deletions lib/ircbot/api.rb
@@ -0,0 +1,105 @@
require 'sinatra'
require 'sinatra/namespace'
require 'sinatra-miniauth'
require 'json'
require 'digest'
require 'eventmachine'
require 'net/http'
require 'uri'

module IrcBot

class Webhooks
MAX_FAILS = 5

def initialize(bot, db)
@bot = bot
@hooks = db[:webhooks]
@queue = []
end

def register
hooks = @hooks
@bot.add_msg_hook // do
hooks.where(hook: 'message').all
.select { |hook| where == hook[:channel] &&
!query &&
msg =~ Regexp.new(hook[:argument]) }.each do |hook|
rec = hooks.where(id: hook[:id])
EventMachine.defer do
begin
Net::HTTP.post_form(URI.parse(hook[:url]), 'message' => msg,
'channel' => where)
rec.update(fails: 0)
rescue
if hook[:fails] > MAX_FAILS
rec.delete
else
rec.update(:fails => :fails + 1)
end
end
end
end
end
end
end

class WebRPC < Sinatra::Base
include Sinatra::MinimalAuthentication
register Sinatra::Namespace

def initialize(bot, db)
super()
@bot = bot
@api_users = db[:api_users]
@webhooks = db[:webhooks]

init_auth "Web API" do |user, password|
hash = Digest::SHA1.digest(password)
@api_users.where(user: user, password: hash).first
end
end

namespace %r{/channel/(?:[^/]+)} do
before do
@channel = '#' + request.fullpath.split('/')[2]
authorize!
end

get '/users' do
puts "Channel: " + @channel
json @bot.list_users(@channel)
end

post '/messages' do
@bot.say params[:text], @channel
json true
end

get '/hooks/message' do
json @webhooks.where(user: user)
end

post '/hooks/message' do
id = @webhooks.insert(url: params[:url], channel: @channel,
hook: 'message', argument: params[:pattern],
user: user)
json id
end

delete '/hooks/message/:id' do |id|
rec = @webhooks.where(id: id.to_i)
halt 403, "Access denied" if rec.first && user != rec.first[:user]
json (rec.delete > 0 ? true : false)
end
end

protected

def json(obj)
content_type :json
obj.to_json
end
end

end
35 changes: 35 additions & 0 deletions lib/sinatra-miniauth.rb
@@ -0,0 +1,35 @@
require 'sinatra'

module Sinatra
module MinimalAuthentication
def init_auth(realm, &check)
@realm, @check = realm, check
end

def unauthorized!
response.headers['WWW-Authenticate'] = 'Basic realm="%s"' % @realm
throw :halt, [ 401, 'Authorization Required' ]
end

def bad_request!
throw :halt, [ 400, 'Bad Request' ]
end

def user
request.env['REMOTE_USER']
end

def authorized?
user
end

def authorize!
return if authorized?
auth = Rack::Auth::Basic::Request.new(request.env)
unauthorized! unless auth.provided?
bad_request! unless auth.basic?
unauthorized! unless @check.call(*auth.credentials)
request.env['REMOTE_USER'] = auth.username
end
end
end

0 comments on commit 16279f1

Please sign in to comment.