{"id":20460962,"url":"https://github.com/ericgj/groem","last_synced_at":"2025-10-29T03:42:12.420Z","repository":{"id":1109364,"uuid":"977213","full_name":"ericgj/groem","owner":"ericgj","description":"Eventmachine-based Ruby Growl (GNTP) client","archived":false,"fork":false,"pushed_at":"2010-10-13T16:07:23.000Z","size":215,"stargazers_count":14,"open_issues_count":1,"forks_count":2,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-26T22:51:14.703Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ericgj.png","metadata":{"files":{"readme":"README.markdown","changelog":"HISTORY.markdown","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2010-10-10T22:11:18.000Z","updated_at":"2019-05-16T14:15:40.000Z","dependencies_parsed_at":"2022-07-17T12:16:20.669Z","dependency_job_id":null,"html_url":"https://github.com/ericgj/groem","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ericgj%2Fgroem","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ericgj%2Fgroem/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ericgj%2Fgroem/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ericgj%2Fgroem/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ericgj","download_url":"https://codeload.github.com/ericgj/groem/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248661722,"owners_count":21141450,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-15T12:22:38.520Z","updated_at":"2025-10-29T03:42:12.366Z","avatar_url":"https://github.com/ericgj.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"## Groem\r\n### An Eventmachine-based Growl Notification Transport Protocol (GNTP) client \r\n\r\nFor documentation of the GNTP protocol, see:\r\n[http://www.growlforwindows.com/gfw/help/gntp.aspx](http://www.growlforwindows.com/gfw/help/gntp.aspx)\r\n\r\nand the [Growl for Windows mailing list](http://groups.google.com/group/growl-for-windows/topics?start=).\r\n\r\nTo install as a gem: `gem install groem`\r\n\r\nNote this is very much beta.  The core functionality is fairly well tested, but the following features are not yet implemented:\r\n\r\n- Binary resources (`x-growl-resource://`) in requests, including for icons\r\n- Encryption\r\n- Subscribe requests\r\n\r\nSee other limitations below.\r\n\r\n## Platforms supported\r\n\r\nGroem does not rely on any OS-specific libraries per se. It's simply an implementation of a TCP protocol.  It doesn't define any UI components, part of Growl's design is that's left to the user to decide.  But of course: you need a Growl server running that speaks GNTP... and so far as I know, to this point it's only been implemented by Growl for Windows. \r\n\r\n## Motivation\r\n\r\nI wanted to be able to send desktop notifications on my Windows box from ruby running in cygwin. Call me crazy but I seem to enjoy hacking at Windows from cygwin...!  (But no fear, if you are running ruby on Windows it should work exactly the same.)\r\n\r\nAlso, I wanted the experience of implementing a protocol in Eventmachine. \r\n\r\nGroem is a spin-off of sorts from my project for the free and wonderful [Ruby Mendicant University (RMU)](http://blog.majesticseacreature.com/).\r\n\r\n## Note about Eventmachine\r\n\r\nAlthough Groem deals with network communication within an EM reactor, **you do not have to have a reactor running already to use it**.  If you do, it will run within it, but if you don't, it will start one up and close it down after it's done sending and receiving.\r\n\r\n## Examples of usage\r\n\r\n### Registration\r\n\r\nGrowl needs a 'register' request to define application options and what notifications to expect from your app.\r\n\r\nBesides what Growl needs for registration, you can also define a **default callback** for a given notification.  This simplifies cases where *you don't care about anything except the user action* -- i.e. whether the user closed the box, clicked it, or ignored it (timed out).  Every time you send that type of notification, the same callback context, context-type and/or target URL will be used. \r\n\r\n\r\n      app = Groem::App.new('Downloader', :host =\u003e 'localhost')\r\n      \r\n      app.register do\r\n        icon 'http://www.example.com/icon.png'\r\n        header 'X-Custom-Header', 'default value'\r\n\r\n        # notification with callback expected\r\n        notification :finished, 'Your download has finished!' do |n|\r\n          n.sticky = 'True'\r\n          n.text = 'Run it!'\r\n          n.icon 'path/to/local/icon.png'  #=\u003e generate x-growl-resource (future)\r\n          n.callback 'process', :type =\u003e 'run'\r\n        end\r\n        \r\n        # notification with no callback\r\n        notification :started, 'Your download is starting!', \r\n                     :display_name =\u003e 'Downloader working'\r\n        \r\n      end\r\n\r\n    \r\n### Notification\r\n    \r\nNotify returns the initial response from the server (whether request was OK or error).  Callbacks (the second response from the server, based on what the user did) are handled through a routing scheme, see below.\r\n\r\n    \r\n      #  trigger notify and callbacks\r\n      app.notify(:started, 'XYZ has started!')\r\n      \r\n      # trigger notify with 'ad-hoc' callback\r\n      # == with different settings than defined in app.register\r\n      app.notify(:started, 'ABC has started!', \r\n                 :callback =\u003e {:type =\u003e 'ad-hoc', \r\n                               :target =\u003e 'www.my-callback-url.com'}\r\n                )\r\n                \r\n      #  trigger notify and handle responses\r\n      app.notify(:finished, 'ABC has finished!') do |response|\r\n        response.ok? { # handle OK response }\r\n        response.error?  { # handle any ERROR response }\r\n        response.error?(400) { # handle ERROR 400 (not authorized) response }\r\n      end\r\n\r\n      # you could also do this outside of the block\r\n      response = app.notify(:finished, 'ABC has finished!') \r\n      response.ok? { # handle OK response }\r\n      response.error?  { # handle any ERROR response }\r\n      response.error?(400) { # handle ERROR 400 (not authorized) response }\r\n    \r\n    \r\n### Callbacks\r\n\r\nCallback procs allow you to capture responses from the user based on what they did (close, click, timeout), plus the two standard Growl data fields that the UI can return data in -- the context and the context-type.  \r\n\r\nA given response will be routed to *all matching callback procs*, starting with the most specific.\r\n\r\n(Of course, if a callback target (URL) is specified in the request, you won't get back a second response -- Growl will open up your browser instead.)\r\n\r\n\r\n      app.when_close 'process' do |response|\r\n        # do something with close responses to process notifications\r\n      end\r\n        \r\n      app.when_click :context =\u003e 'process', :type =\u003e 'run' do |response|\r\n        # do something with click responses that have 'process' contexts of type 'run'\r\n      end\r\n        \r\n      app.when_click :type =\u003e 'integer' do |response|\r\n        # do something with click responses that have type 'integer' regardless of context\r\n      end\r\n        \r\n      app.when_timedout do |response|  \r\n        # do something with any timeout response regardless of context or type\r\n      end\r\n\r\n    \r\n## Lower-level interface\r\n\r\nIf you prefer a more direct interface you can use Groem::Client, the EM connection which Groem::App is built on.  It expects the request to be passed as a hash, and will throw OK, error, and callback responses back as a three-element array roughly modeled on Rack's interface (_status_, _headers_, and _body_ -- body in this case being a hash of GNTP callback headers).  \r\n\r\nFor instance,\r\n\r\n\r\n    regist = {'headers' =\u003e {\r\n                  'Application-Name' =\u003e 'SurfWriter',\r\n                  'Application-Icon' =\u003e 'http://www.site.org/image.jpg'\r\n                  },\r\n               'notifications' =\u003e {\r\n                  'Download Complete' =\u003e {\r\n                    'Notification-Display-Name' =\u003e 'Download completed',\r\n                    'Notification-Enabled' =\u003e 'True',\r\n                    'X-Language' =\u003e 'English',\r\n                    'X-Timezone' =\u003e 'PST'\r\n                    }\r\n                  }\r\n              }\r\n              \r\n    connect = Groem::Client.register(regist)\r\n    connect.when_ok { |resp|  # ... }\r\n    connect.errback { |resp|  # ... }\r\n    \r\n    notif = {'Application-Name' =\u003e 'SurfWriter',\r\n             'Notification-Name' =\u003e 'Download Complete',\r\n             'Notification-ID' =\u003e some_unique_id,\r\n             'Notification-Callback-Context' =\u003e 'myfile',\r\n             'Notification-Callback-Context-Type' =\u003e 'confirm'\r\n            }\r\n    \r\n    connect2 = Groem::Client.notify(notif)\r\n    connect2.when_ok { |resp|  # ... }\r\n    connect2.errback { |resp|  # ... }\r\n    connect2.when_callback { |resp| #... }\r\n    \r\n    \r\nFor more details see `lib/groem/marshal`, and `spec/functional/client_spec`.\r\n\r\n\r\n## Limitations\r\n\r\n- No casting or uncasting of GNTP headers to or from ruby types is done -- everything is a string (or a symbol which gets converted to a string).  So the interface is a little clunky.\r\n\r\n- If a Growl server is not running, the client's EM loop will not exit and has to be interrupted.  In the future it would be good to timeout, perhaps after several reconnect attempts.\r\n\r\n- It has only been tested on Ruby 1.8.7 running on cygwin on WinXP.  I am planning to test on 1.9.2.\r\n\r\n\r\n## License\r\n\r\nCopyright (c) 2010 Eric Gjertsen\r\n\r\nPermission is hereby granted, free of charge, to any person obtaining a copy\r\nof this software and associated documentation files (the \"Software\"), to deal\r\nin the Software without restriction, including without limitation the rights\r\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\ncopies of the Software, and to permit persons to whom the Software is\r\nfurnished to do so, subject to the following conditions:\r\n\r\nThe above copyright notice and this permission notice shall be included in\r\nall copies or substantial portions of the Software.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\nTHE SOFTWARE.\r\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fericgj%2Fgroem","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fericgj%2Fgroem","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fericgj%2Fgroem/lists"}