├── TODO ├── gems ├── ruby_gntp-0.1.0.gem ├── ruby_gntp-0.1.1.gem ├── ruby_gntp-0.1.3.1.gem ├── ruby_gntp-0.2.0.gem ├── ruby_gntp-0.2.1.gem ├── ruby_gntp-0.2.2.gem ├── ruby_gntp-0.2.3.gem ├── ruby_gntp-0.2.4.gem ├── ruby_gntp-0.3.0.gem ├── ruby_gntp-0.3.1.gem ├── ruby_gntp-0.3.2.gem ├── ruby_gntp-0.3.3.gem └── ruby_gntp-0.3.4.gem ├── example ├── gntp-notify ├── NOTE └── twitter_notifier.rb ├── test ├── many_notifications.rb ├── ruby_gntp_spec_helper.rb └── ruby_gntp_spec.rb ├── ruby_gntp.gemspec ├── README ├── LICENSE ├── ChangeLog └── lib └── ruby_gntp.rb /TODO: -------------------------------------------------------------------------------- 1 | = TODO 2 | * Include test code into gem 3 | * Write RDoc comment 4 | 5 | -------------------------------------------------------------------------------- /gems/ruby_gntp-0.1.0.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snaka/ruby_gntp/HEAD/gems/ruby_gntp-0.1.0.gem -------------------------------------------------------------------------------- /gems/ruby_gntp-0.1.1.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snaka/ruby_gntp/HEAD/gems/ruby_gntp-0.1.1.gem -------------------------------------------------------------------------------- /gems/ruby_gntp-0.1.3.1.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snaka/ruby_gntp/HEAD/gems/ruby_gntp-0.1.3.1.gem -------------------------------------------------------------------------------- /gems/ruby_gntp-0.2.0.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snaka/ruby_gntp/HEAD/gems/ruby_gntp-0.2.0.gem -------------------------------------------------------------------------------- /gems/ruby_gntp-0.2.1.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snaka/ruby_gntp/HEAD/gems/ruby_gntp-0.2.1.gem -------------------------------------------------------------------------------- /gems/ruby_gntp-0.2.2.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snaka/ruby_gntp/HEAD/gems/ruby_gntp-0.2.2.gem -------------------------------------------------------------------------------- /gems/ruby_gntp-0.2.3.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snaka/ruby_gntp/HEAD/gems/ruby_gntp-0.2.3.gem -------------------------------------------------------------------------------- /gems/ruby_gntp-0.2.4.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snaka/ruby_gntp/HEAD/gems/ruby_gntp-0.2.4.gem -------------------------------------------------------------------------------- /gems/ruby_gntp-0.3.0.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snaka/ruby_gntp/HEAD/gems/ruby_gntp-0.3.0.gem -------------------------------------------------------------------------------- /gems/ruby_gntp-0.3.1.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snaka/ruby_gntp/HEAD/gems/ruby_gntp-0.3.1.gem -------------------------------------------------------------------------------- /gems/ruby_gntp-0.3.2.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snaka/ruby_gntp/HEAD/gems/ruby_gntp-0.3.2.gem -------------------------------------------------------------------------------- /gems/ruby_gntp-0.3.3.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snaka/ruby_gntp/HEAD/gems/ruby_gntp-0.3.3.gem -------------------------------------------------------------------------------- /gems/ruby_gntp-0.3.4.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snaka/ruby_gntp/HEAD/gems/ruby_gntp-0.3.4.gem -------------------------------------------------------------------------------- /example/gntp-notify: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | # Growl notifier command 3 | # Usage: 4 | # gntp-notify [message] 5 | # License: 6 | # public domain 7 | 8 | require "rubygems" 9 | require "ruby_gntp" # >= ver.0.1.0 10 | 11 | msg = ARGV[0] || "Alert!!" 12 | 13 | GNTP.notify({ 14 | :app_name => "gntp-notify", 15 | :title => "from commandline", 16 | :text => msg, 17 | :sticky=> true, 18 | }) 19 | 20 | -------------------------------------------------------------------------------- /example/NOTE: -------------------------------------------------------------------------------- 1 | Attention when twitter_notifier.rb is used 2 | Following gem is necessary to operate this script. 3 | - json_pure 4 | - pit 5 | 6 | Please ensure that an appropriate value to environment variable EDITOR is set if you start and use Pit. 7 | Pit starts executing the editor command set to EDITOR for the edit of account information, and the script will not operate correctly when this variable is not appropriately set. 8 | 9 | -------------------------------------------------------------------------------- /test/many_notifications.rb: -------------------------------------------------------------------------------- 1 | require '../lib/ruby_gntp' 2 | 3 | g = GNTP.new("test") 4 | 5 | g.register( :notifications => [ 6 | { :name => "notify", :enabled => true }, 7 | { :name => "warning", :enabled => true }, 8 | { :name => "error", :enabled => true }, 9 | ]) 10 | 11 | g.notify( 12 | :name => "notify", 13 | :title => "Test", 14 | :text => "hoge fuga" 15 | ) 16 | 17 | g.notify( 18 | :name => "warning", 19 | :title => "warning", 20 | :text => "Warn!" 21 | ) 22 | 23 | g.notify( 24 | :name => "error", 25 | :title => "error", 26 | :text => "ERROR" 27 | ) 28 | 29 | 30 | -------------------------------------------------------------------------------- /ruby_gntp.gemspec: -------------------------------------------------------------------------------- 1 | require 'lib/ruby_gntp' 2 | 3 | Gem::Specification.new do |s| 4 | s.name = GNTP::RUBY_GNTP_NAME 5 | s.version = GNTP::RUBY_GNTP_VERSION 6 | s.summary = "Ruby library for GNTP(Growl Notification Transport Protocol) client" 7 | s.authors = ["snaka", "David Hayward (spidah)"] 8 | s.email = ["snaka.gml@gmail.com", "spidahman@gmail.com"] 9 | s.homepage = "http://snaka.github.com/ruby_gntp/" 10 | s.files = [ 11 | "lib/ruby_gntp.rb", 12 | "example/twitter_notifier.rb", 13 | "example/gntp-notify", 14 | "test/ruby_gntp_spec.rb", 15 | "test/ruby_gntp_spec_helper.rb", 16 | "README", 17 | "TODO", 18 | "ChangeLog" 19 | ] 20 | s.has_rdoc = false 21 | end 22 | -------------------------------------------------------------------------------- /test/ruby_gntp_spec_helper.rb: -------------------------------------------------------------------------------- 1 | module GNTPExampleHelperMethods 2 | 3 | def create_stub_socket(ok_response, sended_messages) 4 | 5 | sock = {} 6 | 7 | stub(sock).write do |string| 8 | lines = [] 9 | buf = StringIO.new(string) 10 | 11 | while line = buf.gets 12 | lines << line 13 | end 14 | 15 | sended_messages << lines 16 | ok_response.rewind 17 | end 18 | 19 | stub(sock).add do |msg| 20 | ok_response.seek(0, IO::SEEK_END) 21 | ok_response.write msg 22 | ok_response.rewind 23 | end 24 | 25 | stub(sock).gets do 26 | ok_response.gets 27 | end 28 | 29 | stub(sock).close 30 | 31 | stub(TCPSocket).open do |host, port| 32 | sock[:host] = host 33 | sock[:port] = port 34 | sock 35 | end 36 | 37 | sock 38 | end 39 | 40 | end 41 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Ruby library for GNTP(Growl Notification Transport Protocol) 2 | 3 | Sorry, document is not available now. 4 | 5 | Usage example: 6 | 7 | require 'rubygems' 8 | require 'ruby_gntp' 9 | 10 | # -- Standard way 11 | growl = GNTP.new("Ruby/GNTP self test") 12 | growl.register({:notifications => [{ 13 | :name => "notify", 14 | :enabled => true, 15 | }]}) 16 | 17 | growl.notify({ 18 | :name => "notify", 19 | :title => "Congratulation", 20 | :text => "Congratulation! You have successfully installed ruby_gntp.", 21 | :icon => "http://www.hatena.ne.jp/users/sn/snaka72/profile.gif", 22 | :sticky=> true, 23 | }) 24 | 25 | # -- Instant notification 26 | GNTP.notify({ 27 | :app_name => "Instant notify", 28 | :title => "Instant notification", 29 | :text => "Instant notification available now.", 30 | :icon => "http://www.hatena.ne.jp/users/sn/snaka72/profile.gif", 31 | }) 32 | 33 | 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | = ruby_gntp change log 2 | 3 | == Version 0.3.4 - 2010/02/02 4 | * FIXED: Register doesn't work with multiple notifications. 5 | * Modify ChangeLog order. 6 | 7 | == Version 0.3.3 - 2010/01/26 8 | * Temporary patchwork fix - The code was commented out that recognize version 9 | of the OS causes some defects - 10 | 11 | == Version 0.3.1 - yyyy/mm/dd 12 | 13 | == Version 0.3.0 - yyyy/mm/dd 14 | 15 | == Version 0.2.0 - 2009/11/03 16 | * Merge spidahman's commits. Lot of thanks, spidahman! 17 | * Add some test(spec). 18 | 19 | == Version 0.1.3 - 2009/08/19 20 | * Added notification icon sending. 21 | * Now sends out Origin-X headers. 22 | * Use \r\n instead of \n in the header lines. 23 | 24 | == Version 0.1.2 - 2009/08/15 25 | * Enabled password authentication mainly for sending notifications to a network machine. 26 | 27 | == Version 0.1.1 - 2009/4/17 28 | * Add NOTE to example directory. 29 | 30 | == Version 0.1.0 - 2009/4/17 31 | * Add GNTP.notify method for instant notification. 32 | * Add ruby script examples. 33 | 34 | == Version 0.0.2 35 | * Fixed Japanese translated MIT License page URL. 36 | 37 | == Version 0.0.1 38 | * Initial version 39 | 40 | == Version 0.0.0 41 | * Create public Git repository 42 | -------------------------------------------------------------------------------- /example/twitter_notifier.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | # 3 | # Ruby/GNTP example : twitter notifier 4 | # 5 | # Usage: {{{ 6 | # Please type the following command from command line, 7 | # and then, this script gets your time-line every 30 seconds. 8 | # 9 | # > ruby twitter_notifyer.rb 10 | # 11 | # If you want *STOP* this script, so press Ctrl + 'C'. 12 | # 13 | # Require environment variables: 14 | # - EDITOR : For Pit uses this variable to edit account information. 15 | # ex) set EDITOR = c:\Progra~1\vim71\vim.exe 16 | # 17 | # - HTTP_PROXY : If you access the Internet via proxy server, you need 18 | # this variable setting. 19 | # If variable's value icludes protcol scheme('http://' etc.) 20 | # would ignore that. 21 | # ex) set HTTP_PROXY = http://proxy.host.name:8080 22 | # or 23 | # set HTTP_PROXY = proxy.host.name:8080 24 | # 25 | # Web page: 26 | # http://d.hatena.ne.jp/snaka72/ 27 | # http://sumimasen2.blogspot.com/ 28 | # 29 | # License: public domain 30 | # }}} 31 | 32 | require 'net/http' 33 | 34 | require 'rubygems' 35 | require 'json' 36 | require 'pit' 37 | require 'ruby_gntp' 38 | 39 | $tweeted = {} 40 | 41 | $growl = GNTP.new 42 | $growl.register({ 43 | :app_name => "Twitter", 44 | :notifications => [{ :name => "Tweet", :enabled => true }, 45 | { :name => "Error", :enabled => true }] 46 | }) 47 | 48 | def get_timeline 49 | 50 | max_count = 20 51 | 52 | config = Pit.get("twitter", :require => { 53 | "username" => "your twittername", 54 | "password" => "your password" 55 | }) 56 | 57 | Net::HTTP.version_1_2 58 | req = Net::HTTP::Get.new('/statuses/friends_timeline.json') 59 | req.basic_auth config["username"], config["password"] 60 | 61 | proxy_host, proxy_port = (ENV["HTTP_PROXY"] || '').sub(/http:\/\//, '').split(':') 62 | 63 | Net::HTTP::Proxy(proxy_host, proxy_port).start('twitter.com') {|http| 64 | res = http.request(req) 65 | 66 | if res.code != '200' 67 | $growl.notify({ 68 | :name => "Error", 69 | :title => "Error occurd", 70 | :test => "Can not get messages" 71 | }) 72 | puts res if $DEBUG 73 | return 74 | end 75 | 76 | results = JSON.parser.new(res.body).parse() 77 | results.reverse! 78 | results.length.times do |i| 79 | break if i >= max_count 80 | 81 | id = results[i]["id"] 82 | next if $tweeted.include?(id) 83 | 84 | puts screen_name = results[i]["user"]["screen_name"] 85 | puts text = results[i]["text"] 86 | puts icon = results[i]["user"]["profile_image_url"] 87 | 88 | $growl.notify({ 89 | :name => "Tweet", 90 | :title => screen_name, 91 | :text => text, 92 | :icon => icon 93 | }) 94 | $tweeted[id] = true 95 | 96 | sleep 1 97 | end 98 | } 99 | end 100 | 101 | # Check timeline evry 30 seconds. 102 | while true do 103 | get_timeline 104 | sleep 30 105 | end 106 | 107 | # vim: ts=2 sw=2 et fdm=marker 108 | -------------------------------------------------------------------------------- /test/ruby_gntp_spec.rb: -------------------------------------------------------------------------------- 1 | require '../lib/ruby_gntp' 2 | require 'ruby_gntp_spec_helper' 3 | 4 | # use Double Ruby for mock/stub framework. 5 | Spec::Runner.configure do |conf| 6 | conf.mock_with :rr 7 | end 8 | 9 | # describe GNTP behavior 10 | describe GNTP do 11 | include GNTPExampleHelperMethods 12 | 13 | DEFAULT_APP_NAME = "Ruby/GNTP" 14 | NOTIFICATION_NAME = "Notify" 15 | NOTIFICATION_NAME2 = "Notify2" 16 | NOTIFICATION_NAME3 = "Notify3" 17 | 18 | before do 19 | @sended_messages = [] 20 | @ok_response = StringIO.new(["GNTP/1.0 -OK NONE\r\n", "\r\n"].join) 21 | @opened_socket = create_stub_socket(@ok_response, @sended_messages) 22 | end 23 | 24 | it "can register notification with minimum params" do 25 | @gntp = GNTP.new 26 | @gntp.register :notifications => [{:name => NOTIFICATION_NAME}] 27 | 28 | [ 29 | "GNTP/1.0 REGISTER NONE\r\n", 30 | "Application-Name: #{DEFAULT_APP_NAME}\r\n", 31 | "Notifications-Count: 1\r\n", 32 | "\r\n", 33 | "Notification-Name: #{NOTIFICATION_NAME}\r\n", 34 | "Notification-Display-Name: #{NOTIFICATION_NAME}\r\n", 35 | "Notification-Enabled: True\r\n" 36 | ].each {|expected_text| 37 | @sended_messages.last.should include(expected_text) 38 | } 39 | 40 | end 41 | 42 | # 43 | it "can register many notifications" do 44 | @gntp = GNTP.new 45 | @gntp.register :notifications => [ 46 | {:name => NOTIFICATION_NAME}, 47 | {:name => NOTIFICATION_NAME2}, 48 | ] 49 | 50 | @sended_messages.first.should == [ 51 | "GNTP/1.0 REGISTER NONE\r\n", 52 | "Application-Name: #{DEFAULT_APP_NAME}\r\n", 53 | "Origin-Machine-Name: #{Socket.gethostname}\r\n", 54 | "Origin-Software-Name: #{GNTP::RUBY_GNTP_NAME}\r\n", 55 | "Origin-Software-Version: #{GNTP::RUBY_GNTP_VERSION}\r\n", 56 | "Origin-Platform-Name: Windows\r\n", 57 | "Origin-Platform-Version: 0.0\r\n", 58 | "Notifications-Count: 2\r\n", 59 | "\r\n", 60 | "Notification-Name: #{NOTIFICATION_NAME}\r\n", 61 | "Notification-Display-Name: #{NOTIFICATION_NAME}\r\n", 62 | "Notification-Enabled: True\r\n", 63 | "\r\n", 64 | "Notification-Name: #{NOTIFICATION_NAME2}\r\n", 65 | "Notification-Display-Name: #{NOTIFICATION_NAME2}\r\n", 66 | "Notification-Enabled: True\r\n", 67 | "\r\n", 68 | ] 69 | 70 | end 71 | 72 | it "can register notifications to remote host" do 73 | @gntp = GNTP.new "TestApp", "1.2.3.4", "password", 12345 74 | @gntp.register :notifications => [{:name => NOTIFICATION_NAME}] 75 | 76 | @opened_socket[:host].should == "1.2.3.4" 77 | @opened_socket[:port].should == 12345 78 | 79 | @sended_messages.last.first.should match(/GNTP\/1\.0 REGISTER NONE MD5:\S+\r\n/) 80 | [ 81 | "Application-Name: TestApp\r\n", 82 | "Notifications-Count: 1\r\n", 83 | "\r\n", 84 | "Notification-Name: #{NOTIFICATION_NAME}\r\n", 85 | "Notification-Display-Name: #{NOTIFICATION_NAME}\r\n", 86 | "Notification-Enabled: True\r\n" 87 | ].each {|expected_text| 88 | @sended_messages.last.should include(expected_text) 89 | } 90 | 91 | end 92 | 93 | it "can notify with minimum params" do 94 | @gntp = GNTP.new 95 | @gntp.register :notifications => [{:name => NOTIFICATION_NAME}] 96 | @gntp.notify :name => NOTIFICATION_NAME 97 | 98 | [ 99 | "GNTP/1.0 NOTIFY NONE\r\n", 100 | "Application-Name: #{DEFAULT_APP_NAME}\r\n", 101 | "Notification-Name: #{NOTIFICATION_NAME}\r\n", 102 | "Notification-Title: \r\n" 103 | ].each {|expected_text| 104 | @sended_messages.last.should include(expected_text) 105 | } 106 | end 107 | 108 | it "should callback when notify clicked" do 109 | # prepare callback msg 110 | @opened_socket.add [ 111 | "GNTP/1.0 -CALLBACK NONE\r\n", 112 | "Notification-Callback-Result: CLICKED\r\n", 113 | "Notification-Callback-Context: hoge\r\n", 114 | "Notification-Callback-Context-Type: fuga\r\n", 115 | "\r\n", 116 | ].join 117 | 118 | callback_called = false 119 | msg = {} 120 | 121 | @gntp = GNTP.new 122 | @gntp.register :notifications => [{:name => NOTIFICATION_NAME}] 123 | @gntp.notify(:name => NOTIFICATION_NAME) do |response| 124 | sleep 1 125 | callback_called = true 126 | msg = response 127 | end 128 | 129 | [ 130 | "Notification-Callback-Context: (none)\r\n", 131 | "Notification-Callback-Context-Type: (none)\r\n" 132 | ].each {|expected_text| 133 | @sended_messages.last.should include(expected_text) 134 | } 135 | 136 | # wait for callback called 137 | sleep 3 138 | callback_called.should be_true 139 | msg[:callback_result].should == 'CLICKED' 140 | msg[:callback_context].should == 'hoge' 141 | msg[:callback_context_type].should == 'fuga' 142 | end 143 | 144 | it "should not send 'Notification-Callback-*' header when block parameter has not given" do 145 | @gntp = GNTP.new 146 | @gntp.register :notifications => [{:name => NOTIFICATION_NAME}] 147 | @gntp.notify :name => NOTIFICATION_NAME 148 | 149 | [ 150 | "Notification-Callback-Context: (none)\r\n", 151 | "Notification-Callback-Context-Type: (none)\r\n" 152 | ].each {|expected_text| 153 | @sended_messages.last.should_not include(expected_text) 154 | } 155 | end 156 | 157 | it "should send 'Notification-Callback-*' header when block parameter has not given, but supply :callback_* parameter given" do 158 | @gntp = GNTP.new 159 | @gntp.register :notifications => [{:name => NOTIFICATION_NAME}] 160 | @gntp.notify :name => NOTIFICATION_NAME, 161 | :callback_context => 'hoge', 162 | :callback_context_type => 'text' 163 | 164 | [ 165 | "Notification-Callback-Context: hoge\r\n", 166 | "Notification-Callback-Context-Type: text\r\n" 167 | ].each {|expected_text| 168 | @sended_messages.last.should include(expected_text) 169 | } 170 | end 171 | 172 | it "should send instantly" do 173 | GNTP.notify :app_name => "App", :title => "title", :text => "text message" 174 | end 175 | 176 | end 177 | -------------------------------------------------------------------------------- /lib/ruby_gntp.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | # 3 | # Ruby library for GNTP/1.0 4 | # 5 | # LICENSE:{{{ 6 | # Copyright (c) 2009 snaka 7 | # 8 | # The MIT License 9 | # Permission is hereby granted, free of charge, to any person obtaining a copy 10 | # of this software and associated documentation files (the "Software"), to deal 11 | # in the Software without restriction, including without limitation the rights 12 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | # copies of the Software, and to permit persons to whom the Software is 14 | # furnished to do so, subject to the following conditions: 15 | # 16 | # The above copyright notice and this permission notice shall be included in 17 | # all copies or substantial portions of the Software. 18 | # 19 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | # THE SOFTWARE. 26 | # 27 | # Japanese: 28 | # http://sourceforge.jp/projects/opensource/wiki/licenses%2FMIT_license 29 | # 30 | #}}} 31 | require 'socket' 32 | require 'digest/md5' 33 | require 'rbconfig' 34 | 35 | #$DEBUG = true 36 | 37 | class TooFewParametersError < Exception 38 | end 39 | 40 | class GNTP 41 | attr_reader :app_name, :target_host, :target_port 42 | attr_reader :message if $DEBUG 43 | 44 | RUBY_GNTP_NAME = 'ruby_gntp' 45 | RUBY_GNTP_VERSION = '0.3.4' 46 | 47 | def initialize(app_name = 'Ruby/GNTP', host = 'localhost', password = '', port = 23053) 48 | @app_name = app_name 49 | @target_host = host 50 | @target_port = port 51 | @password = password 52 | end 53 | 54 | # 55 | # register 56 | # 57 | def register(params) 58 | @notifications = params[:notifications] 59 | @app_icon = params[:app_icon] 60 | 61 | raise TooFewParametersError, "Need least one 'notification' for register" unless @notifications 62 | 63 | @binaries = [] 64 | 65 | message = register_header(@app_name, @app_icon) 66 | message << output_origin_headers 67 | 68 | message << "Notifications-Count: #{@notifications.size}\r\n" 69 | message << "\r\n" 70 | 71 | @notifications.each do |notification| 72 | name = notification[:name] 73 | disp_name = notification[:disp_name] || name 74 | enabled = notification[:enabled] || true 75 | icon = notification[:icon] 76 | 77 | message << "Notification-Name: #{name}\r\n" 78 | message << "Notification-Display-Name: #{disp_name}\r\n" 79 | message << "Notification-Enabled: #{enabled ? 'True' : 'False'}\r\n" 80 | message << "#{handle_icon(icon, 'Notification')}\r\n" if icon 81 | message << "\r\n" 82 | end 83 | 84 | @binaries.each {|binary| 85 | message << output_binary(binary) 86 | message << "\r\n" 87 | } 88 | 89 | 90 | unless (ret = send_and_recieve(message)) 91 | raise "Register failed" 92 | end 93 | end 94 | 95 | # 96 | # notify 97 | # 98 | def notify(params, &callback) 99 | name = params[:name] 100 | title = params[:title] 101 | text = params[:text] 102 | icon = params[:icon] || get_notification_icon(name) 103 | sticky = params[:sticky] 104 | callback_context = params[:callback_context] 105 | callback_context_type = params[:callback_context_type] 106 | 107 | raise TooFewParametersError, "Notification need 'name', 'title' parameters" unless name || title 108 | 109 | @binaries = [] 110 | 111 | message = notify_header(app_name, name, title, text, sticky, icon) 112 | message << output_origin_headers 113 | if callback || callback_context 114 | message << "Notification-Callback-Context: #{callback_context || '(none)'}\r\n" 115 | message << "Notification-Callback-Context-Type: #{callback_context_type || '(none)'}\r\n" 116 | end 117 | 118 | @binaries.each {|binary| 119 | message << output_binary(binary) 120 | } 121 | 122 | message << "\r\n" 123 | 124 | unless (ret = send_and_recieve(message, callback)) 125 | raise "Notify failed" 126 | end 127 | end 128 | 129 | 130 | # 131 | # instant notification 132 | # 133 | def self.notify(params, &callback) 134 | host = params[:host] 135 | passwd = params[:passwd] 136 | 137 | growl = GNTP.new(params[:app_name], host, passwd) 138 | 139 | notification = params 140 | notification[:name] = params[:app_name] || "Ruby/GNTP notification" 141 | growl.register(:notifications => [ 142 | :name => notification[:name] 143 | ]) 144 | growl.notify(notification, &callback) 145 | end 146 | 147 | private 148 | 149 | # 150 | # send and recieve 151 | # 152 | def send_and_recieve(msg, callback=nil) 153 | print msg if $DEBUG 154 | 155 | sock = TCPSocket.open(@target_host, @target_port) 156 | sock.write msg 157 | 158 | ret = nil 159 | while rcv = sock.gets 160 | break if rcv == "\r\n" 161 | print ">#{rcv}" if $DEBUG 162 | ret = $1 if /GNTP\/1.0\s+-(\S+)/ =~ rcv 163 | end 164 | 165 | if callback 166 | Thread.new do 167 | response = {} 168 | while rcv = sock.gets 169 | break if rcv == "\r\n" 170 | print ">>#{rcv}" if $DEBUG 171 | response[:callback_result] = $1 if /Notification-Callback-Result:\s+(\S*)\r\n/ =~ rcv 172 | response[:callback_context] = $1 if /Notification-Callback-Context:\s+(\S*)\r\n/ =~ rcv 173 | response[:callback_context_type] = $1 if /Notification-Callback-Context-Type:\s+(\S*)\r\n/ =~ rcv 174 | end 175 | callback.call(response) 176 | sock.close 177 | end 178 | return true 179 | end 180 | 181 | sock.close 182 | return 'OK' == ret 183 | end 184 | 185 | # 186 | # get notification icon 187 | # 188 | def get_notification_icon(name) 189 | notification = @notifications.find {|n| n[:name] == name} 190 | return nil unless notification 191 | return notification[:icon] 192 | end 193 | 194 | # 195 | # outputs the registration header 196 | # 197 | def register_header(app_name, app_icon) 198 | message = "#{get_gntp_header_start('REGISTER')}\r\n" 199 | message << "Application-Name: #{app_name}\r\n" 200 | message << "#{handle_icon(@app_icon, 'Application')}\r\n" if app_icon 201 | message 202 | end 203 | 204 | # 205 | # outputs the notification header 206 | # 207 | def notify_header(app_name, name, title, text, sticky, icon) 208 | message = "#{get_gntp_header_start('NOTIFY')}\r\n" 209 | message << "Application-Name: #{@app_name}\r\n" 210 | message << "Notification-Name: #{name}\r\n" 211 | message << "Notification-Title: #{title}\r\n" 212 | message << "Notification-Text: #{text}\r\n" if text 213 | message << "Notification-Sticky: #{sticky}\r\n" if sticky 214 | message << "#{handle_icon(icon, 'Notification')}\r\n" if icon 215 | message 216 | end 217 | 218 | def output_origin_headers 219 | message = "Origin-Machine-Name: #{Socket.gethostname}\r\n" 220 | message << "Origin-Software-Name: #{RUBY_GNTP_NAME}\r\n" 221 | message << "Origin-Software-Version: #{RUBY_GNTP_VERSION}\r\n" 222 | 223 | platformname = platformversion = '' 224 | 225 | # These causes a problem... temporary patchwork fix 226 | # 227 | # see Proper way to detect Windows platform in Ruby - The Empty Way 228 | # http://blog.emptyway.com/2009/11/03/proper-way-to-detect-windows-platform-in-ruby/ 229 | # 230 | #if Config::CONFIG['host_os'] =~ /mswin/ 231 | # ver = `ver` 232 | # if ver.index('[') 233 | # matches = ver.scan(/(.*)\[+(.*)\]+/)[0] 234 | # platformname, platformversion = matches[0], matches[1] 235 | # else 236 | # platformname, platformversion = 'Microsoft Windows', ver 237 | # end 238 | #else 239 | # platformname, platformversion = `uname -s`, `uname -r` 240 | #end 241 | platformname = "Windows" 242 | platformversion = "0.0" 243 | 244 | message << "Origin-Platform-Name: #{platformname.strip}\r\n" 245 | message << "Origin-Platform-Version: #{platformversion.strip}\r\n" 246 | end 247 | 248 | # 249 | # get start of the GNTP header 250 | # 251 | def get_gntp_header_start(type) 252 | if !@password || @password.empty? 253 | "GNTP/1.0 #{type} NONE" 254 | else 255 | saltvar = Time.now.to_s 256 | salt = Digest::MD5.digest(saltvar) 257 | salthash = Digest::MD5.hexdigest(saltvar) 258 | key = Digest::MD5.digest("#{@password}#{salt}") 259 | keyhash = Digest::MD5.hexdigest(key) 260 | "GNTP/1.0 #{type} NONE MD5:#{keyhash}.#{salthash}" 261 | end 262 | end 263 | 264 | # 265 | # figure out how to handle the icon 266 | # a URL icon just gets put into the header 267 | # a file icon gets read and stored, ready to be appended to the end of the request 268 | # 269 | def handle_icon(icon, type) 270 | if File.exists?(icon) && @target_host != 'localhost' 271 | file = File.new(icon) 272 | data = file.read 273 | size = data.length 274 | if size > 0 275 | binary = { 276 | :size => size, 277 | :data => data, 278 | :uniqueid => Digest::MD5.hexdigest(data) 279 | } 280 | @binaries << binary 281 | "#{type}-Icon: x-growl-resource://#{binary[:uniqueid]}" 282 | end 283 | else 284 | "#{type}-Icon: #{icon}" 285 | end 286 | end 287 | 288 | # 289 | # outputs any binary data to be sent 290 | # 291 | def output_binary(binary) 292 | message = "\r\n" 293 | message << "Identifier: #{binary[:uniqueid]}\r\n" 294 | message << "Length: #{binary[:size]}\r\n" 295 | message << "\r\n" 296 | message << "#{binary[:data]}\r\n" 297 | end 298 | end 299 | 300 | #---------------------------- 301 | # self test code 302 | if __FILE__ == $0 303 | host = ARGV[0] || 'localhost' 304 | passwd = ARGV[1] || '' 305 | 306 | #--- Use standard notification method ('register' first then 'notify') 307 | growl = GNTP.new("Ruby/GNTP self test", host, passwd) 308 | growl.register(:notifications => [{ 309 | :name => "notify", 310 | :enabled => true 311 | }]) 312 | 313 | growl.notify( 314 | :name => "notify", 315 | :title => "Congraturation", 316 | :text => "Congraturation! You are successful install ruby_gntp.", 317 | :icon => "http://www.hatena.ne.jp/users/sn/snaka72/profile.gif", 318 | :sticky=> true 319 | ) do |response| 320 | p response 321 | end 322 | 323 | #--- Use instant notification method (just 'notify') 324 | GNTP.notify({ 325 | :app_name => "Instant notify", 326 | :host => host, 327 | :passwd => passwd, 328 | :title => "Instant notification", 329 | :text => "Instant notification available now.", 330 | :icon => "http://www.hatena.ne.jp/users/sn/snaka72/profile.gif", 331 | }) do |response| 332 | p response 333 | end 334 | 335 | #--- wait 336 | puts 337 | puts "press enter key to finish." 338 | a = STDIN.gets 339 | end 340 | 341 | # vim: ts=2 sw=2 expandtab fdm=marker 342 | --------------------------------------------------------------------------------