cogbot 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e507f5555aea13239fa0c558da83b0adaff0769c
4
- data.tar.gz: bcb41ab67f0dec2678ab884d5eff810907afbf94
3
+ metadata.gz: ed9500a2174f92bcc12ab3cae3e2654a3607267a
4
+ data.tar.gz: f45b95e057fb6c7610fe6093f9e473b7d138566f
5
5
  SHA512:
6
- metadata.gz: ad328acddaf1f81bed239d03e5c1f7d052a820ec1569ca2092ff0f6512b7201bba96eb35c5c27a8e207fff2e7ddae05bf9a3846961ac04dff8da5df1a1360d02
7
- data.tar.gz: a8604ada50f361692af7a5f097f2564e0f4f4cf552e2a018f1213acb63411aa6d4c852033e373626e1d21c9acd865e80412a90d5e172cbf825c379d496d5a1db
6
+ metadata.gz: 8ce87667fc15508303457c29a78dee2070166f1373128737c1ec98b34a8fdae4829db43befe5b22fc5a380853aefa07921ea999fc71d1279e7e2e04a2f75c915
7
+ data.tar.gz: 2e695e32ea44f41fa127fe5d24772b0dad73784779f928cc7a37ac35c1de9b7b7f9e3ca2d8be8ecbbc5b36a802bd04c312e980d01949f48b70139dc47532b909
data/.gitignore CHANGED
@@ -2,3 +2,5 @@
2
2
  .bundle
3
3
  config/cogbot.yml
4
4
  vendor/
5
+ *.swp
6
+ *.swo
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 2.2
1
+ 2.2.1
data/CHANGELOG.md CHANGED
@@ -1,17 +1,26 @@
1
1
  Cogbot Changelog
2
2
  ===================
3
3
 
4
- ## v0.1.1 - 2015-01-28
4
+ ### v0.1.2 - 2015-03-16
5
+ - add link to status on twitter plugin search results
6
+ - add age of status in twitter plugin
7
+ - fix bug on tweet search results that are more than one day old or less than one minute
8
+ - add a weak protection for the manager plugin (which anyways should only be used in development mode)
9
+ - update dependencies on cinch and eventmachine (fixing a mem leak issue)
10
+ - add a trello listener plugin, for receiving hooks from Trello
11
+ - change the git listener to listen only on http://host:ip/gitlistener (to avoid confuse with the trello listener)
12
+
13
+ ### v0.1.1 - 2015-01-28
5
14
  - avoid disclose local path in nmanager plugin when plugin not found
6
15
  - fix setup message config.yaml to cogbot.yaml
7
16
 
8
- ## v0.1.0 - 2015-01-08
17
+ ### v0.1.0 - 2015-01-08
9
18
  - upgrade dependency to cinch 2.0.6 to 2.2.2
10
19
  - upgrade other gems as well
11
20
  - fix all plugins for upgrade
12
21
  - handle compat with ruby 2.2
13
22
 
14
- ## v0.0.3 - 2013-07-29
23
+ ### v0.0.3 - 2013-07-29
15
24
 
16
- ## v0.0.2 - 2013-04-03
25
+ ### v0.0.2 - 2013-04-03
17
26
 
data/Gemfile.lock CHANGED
@@ -1,14 +1,14 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- cogbot (0.1.1)
5
- cinch (~> 2.2.2)
4
+ cogbot (0.1.2)
5
+ cinch (= 2.2.4)
6
6
  daemons (~> 1.1.9)
7
- eventmachine (~> 1.0.4)
7
+ eventmachine (= 1.0.7)
8
8
  eventmachine_httpserver (~> 0.2.1)
9
9
  fortune_gem (~> 0.0.8)
10
- json (~> 1.8.1)
11
- nokogiri (~> 1.6.5)
10
+ json (~> 1.8.2)
11
+ nokogiri (~> 1.6.6)
12
12
  thor (~> 0.19.1)
13
13
  twitter (~> 5.13.0)
14
14
  yajl-ruby (~> 1.2.1)
@@ -16,19 +16,19 @@ PATH
16
16
  GEM
17
17
  remote: https://rubygems.org/
18
18
  specs:
19
- addressable (2.3.6)
19
+ addressable (2.3.7)
20
20
  awesome_print (1.6.1)
21
21
  buftok (0.2.0)
22
- cinch (2.2.3)
22
+ cinch (2.2.4)
23
23
  coderay (1.1.0)
24
24
  daemons (1.1.9)
25
25
  docile (1.1.5)
26
26
  equalizer (0.0.9)
27
- eventmachine (1.0.4)
27
+ eventmachine (1.0.7)
28
28
  eventmachine_httpserver (0.2.1)
29
29
  faraday (0.9.1)
30
30
  multipart-post (>= 1.2, < 3)
31
- flog (4.3.1)
31
+ flog (4.3.2)
32
32
  ruby_parser (~> 3.1, > 3.1.0)
33
33
  sexp_processor (~> 4.4)
34
34
  fortune_gem (0.0.8)
@@ -66,7 +66,7 @@ GEM
66
66
  simplecov (>= 0.4.1)
67
67
  slop (3.6.0)
68
68
  thor (0.19.1)
69
- thread_safe (0.3.4)
69
+ thread_safe (0.3.5)
70
70
  twitter (5.13.0)
71
71
  addressable (~> 2.3)
72
72
  buftok (~> 0.2.0)
data/README.md CHANGED
@@ -1,6 +1,9 @@
1
1
  # Cogbot
2
2
 
3
- [![Gem Version](https://badge.fury.io/rb/cogbot.svg)](http://badge.fury.io/rb/cogbot)
3
+ [![Gem Version](http://img.shields.io/gem/v/cogbot.svg)](http://rubygems.org/gems/cogbot)
4
+ [![Downloads](http://img.shields.io/gem/dt/cogbot.svg)](https://rubygems.org/gems/cogbot)
5
+ [![Dependency Status](https://img.shields.io/gemnasium/mose/cogbot.svg)](https://gemnasium.com/mose/cogbot)
6
+ [![Code Climate](http://img.shields.io/codeclimate/github/mose/cogbot.svg)](https://codeclimate.com/github/mose/cogbot)
4
7
 
5
8
  Cogbot is an irc bot written in ruby based on [Cinch bot framework](https://github.com/cinchrb/cinch).
6
9
 
@@ -10,10 +13,13 @@ team that uses irc as a main shared communication space:
10
13
 
11
14
  * git notifications pushed on the channel
12
15
  * redmine issues polled from redmine and announced
13
- * commands to ask rubygems or stack overflow
16
+ * commands to ask google, rubygems or stack overflow
14
17
  * the urban dictionary to make us laugh
18
+ * a twitter search plugin
19
+ * a trello webhooks listener
15
20
  * and some other more or less used features
16
21
 
22
+
17
23
  ## Installation
18
24
 
19
25
  gem install cogbot
@@ -27,6 +33,39 @@ At first launch:
27
33
  you will be prompted to create a configuration file in ~/.cogbot/cogbot.yml
28
34
  When this is done you can launch again and it will just run according to your configuration.
29
35
 
36
+ ## Configuration
37
+
38
+ Some plugins require extra config parameters:
39
+
40
+ Git and trello webhook listeners use a small eventmachine http server, which is only launched if the configuration is present:
41
+
42
+ server:
43
+ ip: x.x.x.x
44
+ port: xxxxx
45
+
46
+ Twitter plugin requires to have credentials set:
47
+
48
+ tweet:
49
+ consumer_key: "xxx"
50
+ consumer_secret: "xxx"
51
+ access_token: "xxx"
52
+ access_token_secret: "xxx"
53
+
54
+ Trello plugin has some config too, for knowing where to announce the trello changes. The webhook has to be setup independantly, it's quite easy to declare by using postman.
55
+
56
+ trello:
57
+ announce:
58
+ - "#trello-announces"
59
+
60
+ Then in Trello, using the API, you can set a hook to send events to http://ip:port/trellolistener
61
+
62
+ ## Todo
63
+
64
+ - document each plugin
65
+ - add multi-entrypoints system for webhooks listener
66
+ - add a users database
67
+ - add a credentials system
68
+
30
69
  ## Development
31
70
 
32
71
  git clone git@github.com:mose/cogbot.git
data/cogbot.gemspec CHANGED
@@ -16,15 +16,15 @@ Gem::Specification.new do |gem|
16
16
  gem.require_paths = ["lib"]
17
17
  gem.version = Cogbot::VERSION
18
18
 
19
- gem.add_dependency 'cinch', '~> 2.2.2'
19
+ gem.add_dependency 'cinch', '2.2.4'
20
20
  gem.add_dependency "thor", '~> 0.19.1'
21
- gem.add_dependency "eventmachine", '~> 1.0.4'
21
+ gem.add_dependency "eventmachine", '1.0.7'
22
22
  gem.add_dependency "eventmachine_httpserver", '~> 0.2.1'
23
- gem.add_dependency 'nokogiri', '~> 1.6.5'
23
+ gem.add_dependency 'nokogiri', '~> 1.6.6'
24
24
  gem.add_dependency "daemons", '~> 1.1.9'
25
25
 
26
26
  gem.add_dependency 'twitter', '~> 5.13.0' # twitter plugin
27
- gem.add_dependency 'json', '~> 1.8.1' # stackoverflow plugin
27
+ gem.add_dependency 'json', '~> 1.8.2' # stackoverflow plugin
28
28
  gem.add_dependency 'yajl-ruby', '~> 1.2.1' # rubygems plugin
29
29
  gem.add_dependency 'fortune_gem', '~> 0.0.8' # fortune plugin
30
30
 
@@ -12,6 +12,9 @@ main:
12
12
  - 'dice'
13
13
  - 'urban'
14
14
  - 'tweet'
15
+ manager:
16
+ admin:
17
+ - 'mose'
15
18
  server:
16
19
  ip: 127.0.0.1
17
20
  port: 9090
@@ -24,3 +27,11 @@ redmine:
24
27
  api_key: ''
25
28
  url: ''
26
29
  project: ''
30
+ trello:
31
+ announce:
32
+ -
33
+ channel: "#trello"
34
+ board: "General"
35
+ -
36
+ channel: "#trello-dev"
37
+ board: "Dev"
data/lib/cogbot/server.rb CHANGED
@@ -12,7 +12,11 @@ class Server < EM::Connection
12
12
 
13
13
  def process_http_request
14
14
  if @http_request_method == "POST"
15
- @bot.handlers.dispatch(:api_callback, nil, @http_post_content)
15
+ pluginlist = @bot.plugins.map { |e| e.class.name.split('::').last.downcase }
16
+ query = @http_request_uri[1..-1]
17
+ if pluginlist.include? query
18
+ @bot.handlers.dispatch("http_#{query}".to_sym, nil, @http_post_content)
19
+ end
16
20
  end
17
21
 
18
22
  response = EM::DelegatedHttpResponse.new(self)
@@ -1,3 +1,3 @@
1
1
  module Cogbot
2
- VERSION = "0.1.1"
2
+ VERSION = "0.1.2"
3
3
  end
@@ -7,7 +7,7 @@ module Cinch
7
7
 
8
8
  set :plugin_name, 'gitlistener'
9
9
 
10
- listen_to :api_callback
10
+ listen_to :http_gitlistener
11
11
 
12
12
  def listen(m, json)
13
13
  hash = Yajl::Parser.parse(URI.unescape(json[8..-1]))
data/plugins/manager.rb CHANGED
@@ -10,6 +10,14 @@ module Cinch
10
10
  match(/m reload (\S+)/, method: :reload_plugin)
11
11
  match(/m set (\S+) (\S+) (.+)$/, method: :set_option)
12
12
 
13
+ def authorized(m, &block)
14
+ if @bot.config.options['cogconf']['manager']['admin'].include? m.user.nick
15
+ instance_eval(&block)
16
+ else
17
+ m.reply "Sorry, you don't have the right."
18
+ end
19
+ end
20
+
13
21
  def list_plugins(m)
14
22
  back = ''
15
23
  @bot.plugins.each { |p| back += "#{p.class.name.split('::').last.downcase} " }
@@ -17,85 +25,94 @@ module Cinch
17
25
  end
18
26
 
19
27
  def load_plugin(m, mapping)
20
- plugin = mapping.downcase.camelize
21
- p plugin
22
- p mapping
23
-
24
- file_name = "#{ROOT_DIR}/plugins/#{mapping.downcase}.rb"
25
- unless File.exist?(file_name)
26
- m.reply "Could not load #{plugin} because #{File.basename(file_name)} does not exist."
27
- return
28
- end
28
+ authorized m do
29
+ plugin = mapping.downcase.camelize
30
+ p plugin
31
+ p mapping
29
32
 
30
- begin
31
- load(file_name)
32
- rescue Exception
33
- m.reply "Could not load #{plugin}."
34
- raise
35
- end
33
+ file_name = "#{ROOT_DIR}/plugins/#{mapping.downcase}.rb"
34
+ unless File.exist?(file_name)
35
+ m.reply "Could not load #{plugin} because #{File.basename(file_name)} does not exist."
36
+ return
37
+ end
36
38
 
37
- begin
38
- const = Cinch::Plugins.const_get(plugin)
39
- rescue NameError
40
- m.reply "Could not load #{plugin} because no matching class was found."
41
- return
42
- end
39
+ begin
40
+ load(file_name)
41
+ rescue Exception
42
+ m.reply "Could not load #{plugin}."
43
+ raise
44
+ end
43
45
 
44
- @bot.plugins.register_plugin(const)
45
- m.reply "Successfully loaded #{plugin}"
46
+ begin
47
+ const = Cinch::Plugins.const_get(plugin)
48
+ rescue NameError
49
+ m.reply "Could not load #{plugin} because no matching class was found."
50
+ return
51
+ end
52
+
53
+ @bot.plugins.register_plugin(const)
54
+ m.reply "Successfully loaded #{plugin}"
55
+ end
46
56
  end
47
57
 
48
58
  def unload_plugin(m, plugin)
49
- begin
50
- plugin_class = Cinch::Plugins.const_get(plugin.downcase.camelize)
51
- rescue NameError
52
- m.reply "Could not unload #{plugin} because no matching class was found."
53
- return
54
- end
59
+ authorized m do
60
+ begin
61
+ plugin_class = Cinch::Plugins.const_get(plugin.downcase.camelize)
62
+ rescue NameError
63
+ m.reply "Could not unload #{plugin} because no matching class was found."
64
+ return
65
+ end
55
66
 
56
- @bot.plugins.select {|p| p.class == plugin_class}.each do |p|
57
- @bot.plugins.unregister_plugin(p)
58
- end
67
+ @bot.plugins.select {|p| p.class == plugin_class}.each do |p|
68
+ @bot.plugins.unregister_plugin(p)
69
+ end
70
+
71
+ ## FIXME not doing this at the moment because it'll break
72
+ ## plugin options. This means, however, that reloading a
73
+ ## plugin is relatively dirty: old methods will not be removed
74
+ ## but only overwritten by new ones. You will also not be able
75
+ ## to change a classes superclass this way.
76
+ # Cinch::Plugins.__send__(:remove_const, plugin)
77
+
78
+ # Because we're not completely removing the plugin class,
79
+ # reset everything to the starting values.
80
+ plugin_class.hooks.clear
81
+ plugin_class.matchers.clear
82
+ plugin_class.listeners.clear
83
+ plugin_class.timers.clear
84
+ plugin_class.ctcps.clear
85
+ plugin_class.react_on = :message
86
+ plugin_class.plugin_name = nil
87
+ plugin_class.help = nil
88
+ plugin_class.prefix = nil
89
+ plugin_class.suffix = nil
90
+ plugin_class.required_options.clear
59
91
 
60
- ## FIXME not doing this at the moment because it'll break
61
- ## plugin options. This means, however, that reloading a
62
- ## plugin is relatively dirty: old methods will not be removed
63
- ## but only overwritten by new ones. You will also not be able
64
- ## to change a classes superclass this way.
65
- # Cinch::Plugins.__send__(:remove_const, plugin)
66
-
67
- # Because we're not completely removing the plugin class,
68
- # reset everything to the starting values.
69
- plugin_class.hooks.clear
70
- plugin_class.matchers.clear
71
- plugin_class.listeners.clear
72
- plugin_class.timers.clear
73
- plugin_class.ctcps.clear
74
- plugin_class.react_on = :message
75
- plugin_class.plugin_name = nil
76
- plugin_class.help = nil
77
- plugin_class.prefix = nil
78
- plugin_class.suffix = nil
79
- plugin_class.required_options.clear
80
-
81
- m.reply "Successfully unloaded #{plugin}"
92
+ m.reply "Successfully unloaded #{plugin}"
93
+ end
82
94
  end
83
95
 
84
96
  def reload_plugin(m, plugin)
85
- unload_plugin(m, plugin)
86
- load_plugin(m, plugin)
97
+ authorized m do
98
+ unload_plugin(m, plugin)
99
+ load_plugin(m, plugin)
100
+ end
87
101
  end
88
102
 
89
103
  def set_option(m, plugin, option, value)
90
- begin
91
- const = Cinch::Plugins.const_get(plugin.downcase.camelize)
92
- rescue NameError
93
- m.reply "Could not set plugin option for #{plugin} because no matching class was found."
94
- return
104
+ authorized m do
105
+ begin
106
+ const = Cinch::Plugins.const_get(plugin.downcase.camelize)
107
+ rescue NameError
108
+ m.reply "Could not set plugin option for #{plugin} because no matching class was found."
109
+ return
110
+ end
111
+ @bot.config.plugins.options[const][option.to_sym] = eval(value)
112
+
113
+ m.reply "Successfuly set option."
95
114
  end
96
- @bot.config.plugins.options[const][option.to_sym] = eval(value)
97
115
 
98
- m.reply "Successfuly set option."
99
116
  end
100
117
  end
101
118
  end
@@ -0,0 +1,211 @@
1
+ require "cgi"
2
+
3
+ module Cinch
4
+ module Plugins
5
+ class Trellolistener
6
+ include Cinch::Plugin
7
+
8
+ set :plugin_name, 'trellolistener'
9
+
10
+ listen_to :http_trellolistener
11
+
12
+ def listen(m, json)
13
+ hash = Yajl::Parser.parse(URI.unescape(json))
14
+ #bot.loggers.debug(hash.inspect)
15
+ if @bot.config.options['cogconf']['trello']
16
+ @bot.config.options['cogconf']['trello']['announce'].each do |announce|
17
+ if hash['action']['data']['board']['name'] == announce['board']
18
+ channel = announce['channel']
19
+ action = hash['action']['type']
20
+ case action
21
+ when 'updateBoard'
22
+ # not implemented yet
23
+ # "prefs":{"cardAging":"pirate"}
24
+ # "prefs":{"voting":"members"}
25
+ when 'addMemberToBoard'
26
+ message(channel, hash, "added %s to board" % [
27
+ Format(:aqua, hash['action']['member']['username'])
28
+ ])
29
+ when 'removeMemberFromBoard'
30
+ message(channel, hash, "removed %s from board" % [
31
+ Format(:aqua, hash['action']['member']['username'])
32
+ ])
33
+ when 'updateList'
34
+ message(channel, hash, "changed column name %s to %s" % [
35
+ Format(:orange, hash['action']['data']['old']['name']),
36
+ Format(:orange, hash['action']['data']['list']['name'])
37
+ ])
38
+ when 'createCard'
39
+ message(channel, hash, "created \"%s\" in %s" % [
40
+ truncate(hash['action']['data']['card']['name']),
41
+ Format(:orange, hash['action']['data']['list']['name'])
42
+ ])
43
+ when 'addMemberToCard'
44
+ message(channel, hash, "added %s to \"%s\"" % [
45
+ Format(:aqua, hash['action']['member']['username']),
46
+ truncate(hash['action']['data']['card']['name'])
47
+ ]) or puts json
48
+ when 'removeMemberFromCard'
49
+ message(channel, hash, "removed %s from \"%s\"" % [
50
+ Format(:aqua, hash['action']['member']['username']),
51
+ truncate(hash['action']['data']['card']['name'])
52
+ ]) or puts json
53
+ when 'updateCard'
54
+ if hash['action']['data']['old']
55
+ if hash['action']['data']['listAfter']
56
+ message(channel, hash, "moved \"%s\" from %s to %s" % [
57
+ truncate(hash['action']['data']['card']['name']),
58
+ Format(:orange, hash['action']['data']['listBefore']['name']),
59
+ Format(:orange, hash['action']['data']['listAfter']['name'])
60
+ ])
61
+ elsif hash['action']['data']['old']['desc']
62
+ message(channel, hash, "changed desc on \"%s\" in %s to \"%s\"" % [
63
+ truncate(hash['action']['data']['card']['name']),
64
+ Format(:orange, hash['action']['data']['list']['name']),
65
+ truncate(hash['action']['data']['card']['desc'])
66
+ ])
67
+ elsif hash['action']['data']['old']['closed'] != nil
68
+ if hash['action']['data']['card']['closed']
69
+ message(channel, hash, "archived \"%s\" from %s" % [
70
+ truncate(hash['action']['data']['card']['name']),
71
+ Format(:orange, hash['action']['data']['list']['name'])
72
+ ])
73
+ else
74
+ message(channel, hash, "restored \"%s\" in %s" % [
75
+ truncate(hash['action']['data']['card']['name']),
76
+ Format(:orange, hash['action']['data']['list']['name'])
77
+ ])
78
+ end
79
+ elsif hash['action']['data']['old'].has_key? 'due'
80
+ if hash['action']['data']['card']['due'] && hash['action']['data']['old']['due']
81
+ date_new = Date.parse(hash['action']['data']['card']['due']).strftime("%a %-d %b")
82
+ date_old = Date.parse(hash['action']['data']['old']['due']).strftime("%a %-d %b")
83
+ message(channel, hash, "changed date on \"%s\" in %s, from %s to %s" % [
84
+ truncate(hash['action']['data']['card']['name']),
85
+ Format(:orange, hash['action']['data']['list']['name']),
86
+ Format(:yellow, date_old),
87
+ Format(:yellow, date_new)
88
+ ])
89
+ elsif hash['action']['data']['card']['due']
90
+ date_new = Date.parse(hash['action']['data']['card']['due']).strftime("%a %-d %b")
91
+ message(channel, hash, "set date on \"%s\" in %s, to %s" % [
92
+ truncate(hash['action']['data']['card']['name']),
93
+ Format(:orange, hash['action']['data']['list']['name']),
94
+ Format(:yellow, date_new)
95
+ ])
96
+ else
97
+ date_old = Date.parse(hash['action']['data']['old']['due']).strftime("%a %-d %b")
98
+ message(channel, hash, "removed date on \"%s\" in %s, was \"%s\"" % [
99
+ truncate(hash['action']['data']['card']['name']),
100
+ Format(:orange, hash['action']['data']['list']['name']),
101
+ Format(:yellow, date_old)
102
+ ])
103
+ end
104
+ else
105
+ puts "---- no known old ----"
106
+ bot.loggers.debug(json)
107
+ puts "---- / no known old ----"
108
+ end
109
+ else
110
+ puts "---- no old ----"
111
+ bot.loggers.debug(json)
112
+ puts "---- / no old ----"
113
+ end
114
+ when 'voteOnCard'
115
+ message(channel, hash, "voted on card \"%s\"" % [
116
+ truncate(hash['action']['data']['card']['name'])
117
+ ])
118
+ when 'addLabelToCard'
119
+ message(channel, hash, "labelled \"%s\" as %s" % [
120
+ truncate(hash['action']['data']['card']['name']),
121
+ Format(:green, hash['action']['data']['label']['name'])
122
+ ])
123
+ when 'removeLabelFromCard'
124
+ message(channel, hash, "unlabelled \"%s\" as %s" % [
125
+ truncate(hash['action']['data']['card']['name']),
126
+ Format(:grey, hash['action']['data']['label']['name'])
127
+ ])
128
+ when 'commentCard'
129
+ message(channel, hash, "commented on \"%s\" in %s: %s" % [
130
+ truncate(hash['action']['data']['card']['name']),
131
+ Format(:orange, hash['action']['data']['list']['name']),
132
+ truncate(hash['action']['data']['text'])
133
+ ])
134
+ when 'addChecklistToCard'
135
+ message(channel, hash, "added checklist \"%s\" on \"%s\"" % [
136
+ truncate(hash['action']['data']['checklist']['name']),
137
+ truncate(hash['action']['data']['card']['name'])
138
+ ])
139
+ when 'updateChecklist'
140
+ message(channel, hash, "changed checklist \"%s\" to \"%s\"" % [
141
+ truncate(hash['action']['data']['checkItem']['name']),
142
+ truncate(hash['action']['data']['old']['name'])
143
+ ])
144
+ when 'createCheckItem'
145
+ message(channel, hash, "added \"%s\" in checklist \"%s\" on \"%s\"" % [
146
+ truncate(hash['action']['data']['checkItem']['name']),
147
+ truncate(hash['action']['data']['checklist']['name']),
148
+ truncate(hash['action']['data']['card']['name'])
149
+ ])
150
+ when 'updateCheckItem'
151
+ message(channel, hash, "changed \"%s\" in checklist \"%s\" to \"%s\" on \"%s\"" % [
152
+ truncate(hash['action']['data']['old']['name']),
153
+ truncate(hash['action']['data']['checklist']['name']),
154
+ truncate(hash['action']['data']['checkItem']['name']),
155
+ truncate(hash['action']['data']['card']['name'])
156
+ ])
157
+ when 'updateCheckItemStateOnCard'
158
+ message(channel, hash, "changed state of \"%s\" in \"%s\" to %s on \"%s\"" % [
159
+ truncate(hash['action']['data']['checkItem']['name']),
160
+ truncate(hash['action']['data']['checklist']['name']),
161
+ Format(:yellow, hash['action']['data']['checkItem']['state']),
162
+ truncate(hash['action']['data']['card']['name'])
163
+ ])
164
+ when 'moveCardFromBoard'
165
+ message(channel, hash, "moved card \"%s\" to board %s" % [
166
+ truncate(hash['action']['data']['card']['name']),
167
+ Format(:yellow, "[%s]" % hash['action']['data']['boardTarget']['name'])
168
+ ])
169
+ else
170
+ puts "------------- not yet implemented: #{action} ------"
171
+ bot.loggers.debug(json)
172
+ puts "---------------------------------------------------"
173
+ end
174
+ end
175
+ end
176
+ end
177
+ end
178
+
179
+ private
180
+
181
+ def link(x)
182
+ "https://trello.com/c/#{x}"
183
+ end
184
+
185
+ def truncate(content, limit=50)
186
+ message = ''
187
+ words = content.gsub(/[\s\n]+/," ").split(" ")
188
+ while words.count > 0
189
+ word = words.shift
190
+ if (message.size + word.size + 2) > limit
191
+ message << ' …'
192
+ words.clear
193
+ else
194
+ message << " #{word}"
195
+ end
196
+ end
197
+ message.strip
198
+ end
199
+
200
+ def message(channel, hash, msg)
201
+ Channel(channel).send("%s %s %s %s" % [
202
+ Format(:yellow, "[%s]" % hash['action']['data']['board']['name']),
203
+ Format(:aqua, hash['action']['memberCreator']['username']),
204
+ msg,
205
+ Format(:grey, "(%s)" % link(hash['action']['data']['card']['shortLink']))
206
+ ])
207
+ end
208
+
209
+ end
210
+ end
211
+ end
data/plugins/tweet.rb CHANGED
@@ -26,6 +26,25 @@ EOT
26
26
  end
27
27
  end
28
28
 
29
+ def ago(timestamp)
30
+ now = Time.now.utc
31
+ timespent = now - timestamp
32
+ case timespent
33
+ when 0..60
34
+ "#{timespent.round}s ago"
35
+ when 61..3600
36
+ "#{(timespent/60).floor}m ago"
37
+ when 3601..86400
38
+ "#{(timespent/3600).floor}h ago"
39
+ when 86401..2592000
40
+ "#{(timespent/86400).floor}d ago"
41
+ when 2592001..31536000
42
+ "#{(timespent/2592000).floor} month ago"
43
+ else
44
+ "more than one year ago"
45
+ end
46
+ end
47
+
29
48
  def new(bot)
30
49
  @bot = bot
31
50
  end
@@ -37,7 +56,11 @@ EOT
37
56
  case command
38
57
  when 'search'
39
58
  client.search(args).take(3).each do |status|
40
- back += Format(:bold, :underline, :yellow, "@#{status.user.screen_name}") + " " + status.text + "\n"
59
+ back += Format(:bold, :underline, :yellow, "@#{status.user.screen_name}")
60
+ back += " #{status.text.gsub(/\n/,' ')}"
61
+ back += " (#{ago(status.created_at)}"
62
+ back += " https://twitter.com/#{status.user.screen_name}/status/#{status.id})"
63
+ back += "\n"
41
64
  end
42
65
  else
43
66
  back += "Usage: .t search <term> : searches the public timelines"
metadata CHANGED
@@ -1,29 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cogbot
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - mose
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-01-28 00:00:00.000000000 Z
11
+ date: 2015-03-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: cinch
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - '='
18
18
  - !ruby/object:Gem::Version
19
- version: 2.2.2
19
+ version: 2.2.4
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - '='
25
25
  - !ruby/object:Gem::Version
26
- version: 2.2.2
26
+ version: 2.2.4
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: thor
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -42,16 +42,16 @@ dependencies:
42
42
  name: eventmachine
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - "~>"
45
+ - - '='
46
46
  - !ruby/object:Gem::Version
47
- version: 1.0.4
47
+ version: 1.0.7
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - "~>"
52
+ - - '='
53
53
  - !ruby/object:Gem::Version
54
- version: 1.0.4
54
+ version: 1.0.7
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: eventmachine_httpserver
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -72,14 +72,14 @@ dependencies:
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: 1.6.5
75
+ version: 1.6.6
76
76
  type: :runtime
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: 1.6.5
82
+ version: 1.6.6
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: daemons
85
85
  requirement: !ruby/object:Gem::Requirement
@@ -114,14 +114,14 @@ dependencies:
114
114
  requirements:
115
115
  - - "~>"
116
116
  - !ruby/object:Gem::Version
117
- version: 1.8.1
117
+ version: 1.8.2
118
118
  type: :runtime
119
119
  prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
121
121
  requirements:
122
122
  - - "~>"
123
123
  - !ruby/object:Gem::Version
124
- version: 1.8.1
124
+ version: 1.8.2
125
125
  - !ruby/object:Gem::Dependency
126
126
  name: yajl-ruby
127
127
  requirement: !ruby/object:Gem::Requirement
@@ -297,6 +297,7 @@ files:
297
297
  - plugins/shorturl.rb
298
298
  - plugins/stackoverflow.rb
299
299
  - plugins/tests.rb
300
+ - plugins/trellolistener.rb
300
301
  - plugins/tweet.rb
301
302
  - plugins/urban.rb
302
303
  - plugins/users.rb