├── .gitignore ├── .rspec ├── .rvmrc ├── Gemfile ├── Gemfile.lock ├── Procfile ├── README.md ├── Rakefile ├── app ├── assets │ ├── images │ │ └── rails.png │ ├── javascripts │ │ └── application.js │ └── stylesheets │ │ ├── application.scss │ │ └── base │ │ ├── 960.scss │ │ ├── global.scss │ │ └── reset.scss ├── controllers │ ├── application_controller.rb │ └── home_controller.rb ├── helpers │ └── application_helper.rb ├── mailers │ └── .gitkeep ├── models │ ├── .gitkeep │ └── article.rb └── views │ ├── home │ └── index.html.erb │ └── layouts │ └── application.html.erb ├── config.ru ├── config ├── application.rb ├── boot.rb ├── config.yml ├── database.yml ├── environment.rb ├── environments │ ├── development.rb │ ├── production.rb │ └── test.rb ├── initializers │ ├── backtrace_silencers.rb │ ├── config.rb │ ├── delayed_jobs.rb │ ├── inflections.rb │ ├── mime_types.rb │ ├── mongo.rb │ ├── secret_token.rb │ ├── session_store.rb │ └── wrap_parameters.rb ├── locales │ └── en.yml └── routes.rb ├── db ├── migrate │ └── 20111202224006_create_delayed_jobs.rb ├── schema.rb └── seeds.rb ├── doc └── README_FOR_APP ├── lib ├── delayed_jobs │ └── process_new_articles_job.rb └── tasks │ ├── .gitkeep │ ├── restart_processing.rake │ ├── start_processing.rake │ └── stop_processing.rake ├── log └── .gitkeep ├── public ├── 404.html ├── 422.html ├── 500.html ├── favicon.ico ├── images │ ├── backgrounds │ │ ├── page_bg.png │ │ ├── paper_bg.png │ │ ├── paper_bottom.png │ │ ├── paper_top.png │ │ ├── soil.png │ │ └── tree.png │ ├── content │ │ └── hook.png │ └── icons │ │ └── penguin.png └── robots.txt ├── script ├── delayed_job └── rails ├── spec ├── delayed_jobs │ └── process_new_articles_job_spec.rb ├── models │ └── article_spec.rb └── spec_helper.rb └── vendor ├── assets └── stylesheets │ └── .gitkeep └── plugins └── .gitkeep /.gitignore: -------------------------------------------------------------------------------- 1 | .bundle 2 | db/*.sqlite3 3 | log/*.log 4 | tmp/ 5 | .sass-cache/ 6 | .DS_Store 7 | .idea 8 | coverage 9 | spec/coverage 10 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --colour 2 | -------------------------------------------------------------------------------- /.rvmrc: -------------------------------------------------------------------------------- 1 | rvm use 1.9.2@rss2webhook -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'http://rubygems.org' 2 | source 'http://gemcutter.org' 3 | 4 | gem 'rails', '3.1.0' 5 | 6 | # Infrastructure 7 | gem 'pg' 8 | gem 'delayed_job' 9 | gem 'mongo_mapper' 10 | gem 'bson_ext' 11 | gem 'rest-client' 12 | gem 'thin' 13 | 14 | # Gems used only for assets and not required 15 | # in production environments by default. 16 | group :assets do 17 | gem 'coffee-rails', "~> 3.1.0" 18 | gem 'uglifier' 19 | end 20 | 21 | # Frontend 22 | gem 'sass-rails', "~> 3.1.0" 23 | gem 'jquery-rails' 24 | gem 'redcarpet' 25 | 26 | group :development, :test do 27 | gem 'annotate' 28 | gem 'rspec-rails' 29 | gem 'foreman' 30 | gem 'heroku' 31 | end 32 | 33 | group :test do 34 | # Pretty printed test output 35 | gem 'turn', :require => false 36 | gem 'simplecov', :require => false 37 | gem 'webmock' 38 | end 39 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: http://rubygems.org/ 3 | remote: http://gemcutter.org/ 4 | specs: 5 | actionmailer (3.1.0) 6 | actionpack (= 3.1.0) 7 | mail (~> 2.3.0) 8 | actionpack (3.1.0) 9 | activemodel (= 3.1.0) 10 | activesupport (= 3.1.0) 11 | builder (~> 3.0.0) 12 | erubis (~> 2.7.0) 13 | i18n (~> 0.6) 14 | rack (~> 1.3.2) 15 | rack-cache (~> 1.0.3) 16 | rack-mount (~> 0.8.2) 17 | rack-test (~> 0.6.1) 18 | sprockets (~> 2.0.0) 19 | activemodel (3.1.0) 20 | activesupport (= 3.1.0) 21 | bcrypt-ruby (~> 3.0.0) 22 | builder (~> 3.0.0) 23 | i18n (~> 0.6) 24 | activerecord (3.1.0) 25 | activemodel (= 3.1.0) 26 | activesupport (= 3.1.0) 27 | arel (~> 2.2.1) 28 | tzinfo (~> 0.3.29) 29 | activeresource (3.1.0) 30 | activemodel (= 3.1.0) 31 | activesupport (= 3.1.0) 32 | activesupport (3.1.0) 33 | multi_json (~> 1.0) 34 | addressable (2.2.6) 35 | annotate (2.4.0) 36 | ansi (1.4.1) 37 | arel (2.2.1) 38 | bcrypt-ruby (3.0.1) 39 | bson (1.5.1) 40 | bson_ext (1.5.1) 41 | builder (3.0.0) 42 | coffee-rails (3.1.1) 43 | coffee-script (>= 2.2.0) 44 | railties (~> 3.1.0) 45 | coffee-script (2.2.0) 46 | coffee-script-source 47 | execjs 48 | coffee-script-source (1.1.3) 49 | crack (0.3.1) 50 | daemons (1.1.4) 51 | delayed_job (2.1.4) 52 | activesupport (~> 3.0) 53 | daemons 54 | diff-lcs (1.1.3) 55 | erubis (2.7.0) 56 | eventmachine (0.12.10) 57 | execjs (1.2.9) 58 | multi_json (~> 1.0) 59 | foreman (0.26.1) 60 | term-ansicolor (~> 1.0.5) 61 | thor (>= 0.13.6) 62 | heroku (2.15.0) 63 | launchy (>= 0.3.2) 64 | rest-client (~> 1.6.1) 65 | rubyzip 66 | term-ansicolor (~> 1.0.5) 67 | hike (1.2.1) 68 | i18n (0.6.0) 69 | jquery-rails (1.0.19) 70 | railties (~> 3.0) 71 | thor (~> 0.14) 72 | json (1.6.3) 73 | launchy (2.0.5) 74 | addressable (~> 2.2.6) 75 | mail (2.3.0) 76 | i18n (>= 0.4.0) 77 | mime-types (~> 1.16) 78 | treetop (~> 1.4.8) 79 | mime-types (1.17.2) 80 | mongo (1.5.1) 81 | bson (= 1.5.1) 82 | mongo_mapper (0.10.1) 83 | activemodel (~> 3.0) 84 | activesupport (~> 3.0) 85 | plucky (~> 0.4.0) 86 | multi_json (1.0.4) 87 | pg (0.11.0) 88 | plucky (0.4.3) 89 | mongo (~> 1.3) 90 | polyglot (0.3.3) 91 | rack (1.3.5) 92 | rack-cache (1.0.3) 93 | rack (>= 0.4) 94 | rack-mount (0.8.3) 95 | rack (>= 1.0.0) 96 | rack-ssl (1.3.2) 97 | rack 98 | rack-test (0.6.1) 99 | rack (>= 1.0) 100 | rails (3.1.0) 101 | actionmailer (= 3.1.0) 102 | actionpack (= 3.1.0) 103 | activerecord (= 3.1.0) 104 | activeresource (= 3.1.0) 105 | activesupport (= 3.1.0) 106 | bundler (~> 1.0) 107 | railties (= 3.1.0) 108 | railties (3.1.0) 109 | actionpack (= 3.1.0) 110 | activesupport (= 3.1.0) 111 | rack-ssl (~> 1.3.2) 112 | rake (>= 0.8.7) 113 | rdoc (~> 3.4) 114 | thor (~> 0.14.6) 115 | rake (0.9.2.2) 116 | rdoc (3.11) 117 | json (~> 1.4) 118 | redcarpet (2.0.0) 119 | rest-client (1.6.7) 120 | mime-types (>= 1.16) 121 | rspec (2.7.0) 122 | rspec-core (~> 2.7.0) 123 | rspec-expectations (~> 2.7.0) 124 | rspec-mocks (~> 2.7.0) 125 | rspec-core (2.7.1) 126 | rspec-expectations (2.7.0) 127 | diff-lcs (~> 1.1.2) 128 | rspec-mocks (2.7.0) 129 | rspec-rails (2.7.0) 130 | actionpack (~> 3.0) 131 | activesupport (~> 3.0) 132 | railties (~> 3.0) 133 | rspec (~> 2.7.0) 134 | rubyzip (0.9.5) 135 | sass (3.1.11) 136 | sass-rails (3.1.5) 137 | actionpack (~> 3.1.0) 138 | railties (~> 3.1.0) 139 | sass (~> 3.1.10) 140 | tilt (~> 1.3.2) 141 | simplecov (0.5.4) 142 | multi_json (~> 1.0.3) 143 | simplecov-html (~> 0.5.3) 144 | simplecov-html (0.5.3) 145 | sprockets (2.0.3) 146 | hike (~> 1.2) 147 | rack (~> 1.0) 148 | tilt (~> 1.1, != 1.3.0) 149 | term-ansicolor (1.0.7) 150 | thin (1.3.1) 151 | daemons (>= 1.0.9) 152 | eventmachine (>= 0.12.6) 153 | rack (>= 1.0.0) 154 | thor (0.14.6) 155 | tilt (1.3.3) 156 | treetop (1.4.10) 157 | polyglot 158 | polyglot (>= 0.3.1) 159 | turn (0.8.3) 160 | ansi 161 | tzinfo (0.3.31) 162 | uglifier (1.1.0) 163 | execjs (>= 0.3.0) 164 | multi_json (>= 1.0.2) 165 | webmock (1.7.8) 166 | addressable (~> 2.2, > 2.2.5) 167 | crack (>= 0.1.7) 168 | 169 | PLATFORMS 170 | ruby 171 | 172 | DEPENDENCIES 173 | annotate 174 | bson_ext 175 | coffee-rails (~> 3.1.0) 176 | delayed_job 177 | foreman 178 | heroku 179 | jquery-rails 180 | mongo_mapper 181 | pg 182 | rails (= 3.1.0) 183 | redcarpet 184 | rest-client 185 | rspec-rails 186 | sass-rails (~> 3.1.0) 187 | simplecov 188 | thin 189 | turn 190 | uglifier 191 | webmock 192 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: bundle exec rails server -p $PORT thin -e $RACK_ENV 2 | worker: bundle exec rake jobs:work 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rss2webhook - Send RSS feed articles to any webhook 2 | 3 | rss2webhook is a barebones Rails app made to fetch new articles from an arbitrary 4 | RSS feed and POST (or GET) them to user-specified webhooks. It easily handles both plain 5 | and authenticated RSS feeds over http/https. 6 | 7 | The vast majority of the app's functionality can be configured through one YAML 8 | configuration file: ``config.yml``. RSS feeds are set up here with a URL and authentication 9 | options. The webhook can be configured to either POST or GET a custom JSON object. 10 | The JSON data to be sent is configurable to be any tree structure you like. 11 | ``|pipes|`` are used to inject RSS item attributes into the JSON. See the examples 12 | below for details. 13 | 14 | rss2webhook was made to be deployed easily on Heroku. You can however, tweak it to 15 | deploy on any server with a SQL-DB backing and MongoDB support. 16 | 17 | ## Deploying rss2webhook 18 | 19 | Below are instructions for deploying rss2webhook on Heroku. 20 | 21 | ### Step 1: Clone this repo 22 | 23 | git clone git://github.com/jookyboi/rss2webhook.git 24 | 25 | ### Step 2: Bundle the gems 26 | 27 | bundle install 28 | 29 | ### Step 3: Configure the Postgres database 30 | 31 | All database configuration is in ``config/database.yml``. Change it to suit your environment. 32 | 33 | ### Step 4: Create and Migrate database 34 | 35 | rake db:create 36 | rake db:migrate 37 | 38 | ### Step 5: Configure MongoMapper 39 | 40 | rss2webhook uses MongoMapper as an adapter for MongoDB. If you are testing the app out 41 | in your local environment and you already have MongoDB running, the first condition 42 | in ``config/initializers/mongo.rb`` should have you covered. If you don't have MongoDB installed, 43 | download the appropriate distro [here](http://www.mongodb.org/downloads). 44 | 45 | If you are deploying to Heroku, you'll need to sign up for either a [MongoLab](http://addons.heroku.com/mongolab) 46 | or [MongoHQ](http://addons.heroku.com/mongohq) account after deploying. (I personally use MongoLab as their 47 | free plan comes with a generous 240MB of space.) In ``mongo.rb``, uncomment one of the 2 configuration 48 | lines to work with your MongoDB provider. 49 | 50 | ```ruby 51 | MongoMapper.config = { Rails.env => {'uri' => ENV['MONGOLAB_URI']} } 52 | ``` 53 | 54 | or for MongoHQ: 55 | 56 | ```ruby 57 | MongoMapper.config = { Rails.env => {'uri' => ENV['MONGOHQ_URL']} } 58 | ``` 59 | 60 | ### Step 6: Configure your RSS feeds and webhooks 61 | 62 | Open up ``config/config.yml``, the central [YAML](http://www.yaml.org/) file for configuring 63 | rss2webhook. 64 | 65 | You'll need to start by adding sections for ``development:`` and ``production:``. For each section, 66 | define a set of global settings. Below is an example of one section for ``production``. Be sure to 67 | do something similar for ``development`` so you can test locally. 68 | 69 | ```yaml 70 | production: 71 | settings: 72 | type: post # either post or get 73 | process_on_start: false # whether to send all the articles on first fetch of the feed 74 | check_interval: 10 # check for feed updates every x seconds 75 | ``` 76 | 77 | After that, you need to configure at least one RSS feed and its corresponding webhook. Here is 78 | an example for an unauthenticated RSS sample feed from [SilverOrange](http://labs.silverorange.com/archive/2003/july/privaterss) 79 | sending data to a HipChat room webhook ([message API](https://www.hipchat.com/docs/api/method/rooms/message)). 80 | 81 | ```yaml 82 | rss_feeds: 83 | - 84 | connection: http://labs.silverorange.com/local/solabs/rsstest/rss_plain.xml 85 | webhook: https://api.hipchat.com/v1/rooms/message # any web url 86 | type: get # can be get or post 87 | output: 88 | auth_token: 37b6805ad9ef28b523268053d5953c 89 | room_id: 48856 90 | from: |author| # equivalent to rss_article['author'] 91 | message: |title| at |link| # interpolated values of rss_article 92 | format: json 93 | ``` 94 | 95 | See the configuration examples below for info on dealing with basic authentication, SSL, 96 | and different output formats. 97 | 98 | ### Step 7: Test it out locally 99 | 100 | rss2webhook provides you with a few rake scripts to start, stop, and restart the RSS feed processing. 101 | Under the hood, they insert and delete delayed jobs. 102 | 103 | Assumbing your Mongo and DJ configuration is correct, you can insert the DJ for processing feeds with: 104 | 105 | rake start_processing 106 | 107 | Next, use [Foreman](http://michaelvanrooijen.com/articles/2011/06/08-managing-and-monitoring-your-ruby-application-with-foreman-and-upstart/) 108 | to spin up a worker: 109 | 110 | foreman start worker 111 | 112 | Assuming things are working, you should see the DJ fire once every few seconds for each one of the configured feeds. 113 | 114 | ### Step 8: Deploy on Heroku 115 | 116 | You are now ready to deploy on Heroku. Due to rss2webhook's reliance on a Procfile and Rails 3.1, it is 117 | recommended you use the [Cedar](http://devcenter.heroku.com/articles/cedar#using_cedar) stack. 118 | 119 | First, commit your changes: 120 | 121 | git commit -am "Changed configuration for deployment" 122 | 123 | In the project directory, type: 124 | 125 | heroku create --stack cedar 126 | 127 | Push your repo to Heroku: 128 | 129 | git push heroku master 130 | 131 | You'll need an instance of a MongoDB running. I recommend MongoLab. Their starter plan is free: 132 | 133 | heroku addons:add mongolab:starter 134 | 135 | Next, migrate the database: 136 | 137 | heroku run rake db:migrate 138 | 139 | Make sure you scale down the web worker (there is no frontend) and scale up the background worker: 140 | 141 | heroku scale web=0 142 | heroku scale worker=1 143 | 144 | Lastly, to kick everything off, run the rake task: 145 | 146 | heroku run rake start_processing 147 | 148 | Tail the logs just to make sure things are going as expected: 149 | 150 | heroku logs --tail 151 | 152 | That's it! You should now have an instance of rss2webhook sending RSS articles to webhooks. 153 | 154 | ## Configuration Examples 155 | 156 | Below are a few typical configurations. 157 | 158 | ### RSS over no-auth HTTP, simple POST to webhook 159 | 160 | ```yaml 161 | - 162 | connection: http://www.example.com/rss.xml 163 | webhook: http://www.chatroom.com/webhook # POSTs article => { link => ... } 164 | ``` 165 | 166 | ### RSS over basic-auth HTTP, simple POST to webhook 167 | 168 | ```yaml 169 | - 170 | connection: 171 | host: basicauth.example.com 172 | ssl: false # for HTTPs, just set this to true 173 | resource: /feeds/daily 174 | auth: 175 | username: basicauth_user 176 | password: my_password 177 | webhook: http://www.chatroom.com/webhook 178 | ``` 179 | 180 | ### Custom webhook data 181 | 182 | ```yaml 183 | - 184 | connection: http://www.example.com/rss.xml 185 | webhook: http://www.chatroom.com/webhook_custom 186 | output: # POSTs message => { summary => ..., content => ... } 187 | message: 188 | summary: |title| 189 | content: Full content - |description| 190 | ``` 191 | 192 | ### Send data to webhook with GET request 193 | 194 | ```yaml 195 | - 196 | connection: http://www.example.com/rss.xml 197 | webhook: http://www.chatroom.com/webhook_get 198 | type: get 199 | ``` 200 | 201 | ## Advanced Usage 202 | 203 | All the logic for rss2webhook can be found in the delayed job ``lib/delayed_jobs/process_new_articles_job.rb``. 204 | The ``perform`` method downloads the entire RSS feed and parses articles using the ``RSS::Parser`` lib. It then 205 | checks for new RSS items depending whether any of the newly fetched articles have urls which match articles 206 | already stored in MongoDB. If not, ``call_webhook`` is invoked for each of the new articles. If you have a need 207 | to use a different article field not officially specified in the RSS 2.0 standard (i.e. ``pubDate``), ``perform`` is the place 208 | to change it. 209 | 210 | rss2webhook performs very basic interpolation of RSS article fields in the output sent to webhooks. If 211 | for some reason the ``|`` pipe symbol is not working well for your content, the place to change it is in 212 | ``interpolate_output_with_values``. Simply change the ``regex`` specified to be what you need. -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env rake 2 | # Add your own tasks in files placed in lib/tasks ending in .rake, 3 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 4 | 5 | require File.expand_path('../config/application', __FILE__) 6 | 7 | Rss2webhook::Application.load_tasks 8 | -------------------------------------------------------------------------------- /app/assets/images/rails.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jookyboi/rss2webhook/286950c9b14ac0aba847ed0bb42840a1816a19be/app/assets/images/rails.png -------------------------------------------------------------------------------- /app/assets/javascripts/application.js: -------------------------------------------------------------------------------- 1 | // This is a manifest file that'll be compiled into including all the files listed below. 2 | // Add new JavaScript/Coffee code in separate files in this directory and they'll automatically 3 | // be included in the compiled file accessible from http://example.com/assets/application.js 4 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the 5 | // the compiled file. 6 | // 7 | //= require jquery 8 | //= require jquery_ujs 9 | //= require_tree . 10 | -------------------------------------------------------------------------------- /app/assets/stylesheets/application.scss: -------------------------------------------------------------------------------- 1 | /* Base */ 2 | @import 'base/reset'; 3 | @import 'base/960'; 4 | @import 'base/global'; -------------------------------------------------------------------------------- /app/assets/stylesheets/base/960.scss: -------------------------------------------------------------------------------- 1 | /* 2 | Variable Grid System. 3 | Learn more ~ http://www.spry-soft.com/grids/ 4 | Based on 960 Grid System - http://960.gs/ 5 | 6 | Licensed under GPL and MIT. 7 | */ 8 | 9 | /* 10 | Forces backgrounds to span full width, 11 | even if there is horizontal scrolling. 12 | Increase this if your layout is wider. 13 | 14 | Note: IE6 works fine without this fix. 15 | */ 16 | 17 | body { 18 | min-width: 660px; 19 | } 20 | 21 | /* Containers 22 | ----------------------------------------------------------------------------------------------------*/ 23 | .container_12 { 24 | margin-left: auto; 25 | margin-right: auto; 26 | width: 660px; 27 | } 28 | 29 | /* Grid >> Global 30 | ----------------------------------------------------------------------------------------------------*/ 31 | 32 | 33 | .grid_1, 34 | .grid_2, 35 | .grid_3, 36 | .grid_4, 37 | .grid_5, 38 | .grid_6, 39 | .grid_7, 40 | .grid_8, 41 | .grid_9, 42 | .grid_10, 43 | .grid_11, 44 | .grid_12 { 45 | display:inline; 46 | float: left; 47 | position: relative; 48 | margin-left: 5px; 49 | margin-right: 5px; 50 | } 51 | 52 | 53 | 54 | .push_1, .pull_1, 55 | .push_2, .pull_2, 56 | .push_3, .pull_3, 57 | .push_4, .pull_4, 58 | .push_5, .pull_5, 59 | .push_6, .pull_6, 60 | .push_7, .pull_7, 61 | .push_8, .pull_8, 62 | .push_9, .pull_9, 63 | .push_10, .pull_10, 64 | .push_11, .pull_11, 65 | .push_12, .pull_12 { 66 | position:relative; 67 | } 68 | 69 | 70 | /* Grid >> Children (Alpha ~ First, Omega ~ Last) 71 | ----------------------------------------------------------------------------------------------------*/ 72 | 73 | .alpha { 74 | margin-left: 0; 75 | } 76 | 77 | .omega { 78 | margin-right: 0; 79 | } 80 | 81 | /* Grid >> 12 Columns 82 | ----------------------------------------------------------------------------------------------------*/ 83 | 84 | 85 | .container_12 .grid_1 { 86 | width:45px; 87 | } 88 | 89 | .container_12 .grid_2 { 90 | width:100px; 91 | } 92 | 93 | .container_12 .grid_3 { 94 | width:155px; 95 | } 96 | 97 | .container_12 .grid_4 { 98 | width:210px; 99 | } 100 | 101 | .container_12 .grid_5 { 102 | width:265px; 103 | } 104 | 105 | .container_12 .grid_6 { 106 | width:320px; 107 | } 108 | 109 | .container_12 .grid_7 { 110 | width:375px; 111 | } 112 | 113 | .container_12 .grid_8 { 114 | width:430px; 115 | } 116 | 117 | .container_12 .grid_9 { 118 | width:485px; 119 | } 120 | 121 | .container_12 .grid_10 { 122 | width:540px; 123 | } 124 | 125 | .container_12 .grid_11 { 126 | width:595px; 127 | } 128 | 129 | .container_12 .grid_12 { 130 | width:650px; 131 | } 132 | 133 | 134 | 135 | 136 | /* Prefix Extra Space >> 12 Columns 137 | ----------------------------------------------------------------------------------------------------*/ 138 | 139 | 140 | .container_12 .prefix_1 { 141 | padding-left:55px; 142 | } 143 | 144 | .container_12 .prefix_2 { 145 | padding-left:110px; 146 | } 147 | 148 | .container_12 .prefix_3 { 149 | padding-left:165px; 150 | } 151 | 152 | .container_12 .prefix_4 { 153 | padding-left:220px; 154 | } 155 | 156 | .container_12 .prefix_5 { 157 | padding-left:275px; 158 | } 159 | 160 | .container_12 .prefix_6 { 161 | padding-left:330px; 162 | } 163 | 164 | .container_12 .prefix_7 { 165 | padding-left:385px; 166 | } 167 | 168 | .container_12 .prefix_8 { 169 | padding-left:440px; 170 | } 171 | 172 | .container_12 .prefix_9 { 173 | padding-left:495px; 174 | } 175 | 176 | .container_12 .prefix_10 { 177 | padding-left:550px; 178 | } 179 | 180 | .container_12 .prefix_11 { 181 | padding-left:605px; 182 | } 183 | 184 | 185 | 186 | /* Suffix Extra Space >> 12 Columns 187 | ----------------------------------------------------------------------------------------------------*/ 188 | 189 | 190 | .container_12 .suffix_1 { 191 | padding-right:55px; 192 | } 193 | 194 | .container_12 .suffix_2 { 195 | padding-right:110px; 196 | } 197 | 198 | .container_12 .suffix_3 { 199 | padding-right:165px; 200 | } 201 | 202 | .container_12 .suffix_4 { 203 | padding-right:220px; 204 | } 205 | 206 | .container_12 .suffix_5 { 207 | padding-right:275px; 208 | } 209 | 210 | .container_12 .suffix_6 { 211 | padding-right:330px; 212 | } 213 | 214 | .container_12 .suffix_7 { 215 | padding-right:385px; 216 | } 217 | 218 | .container_12 .suffix_8 { 219 | padding-right:440px; 220 | } 221 | 222 | .container_12 .suffix_9 { 223 | padding-right:495px; 224 | } 225 | 226 | .container_12 .suffix_10 { 227 | padding-right:550px; 228 | } 229 | 230 | .container_12 .suffix_11 { 231 | padding-right:605px; 232 | } 233 | 234 | 235 | 236 | /* Push Space >> 12 Columns 237 | ----------------------------------------------------------------------------------------------------*/ 238 | 239 | 240 | .container_12 .push_1 { 241 | left:55px; 242 | } 243 | 244 | .container_12 .push_2 { 245 | left:110px; 246 | } 247 | 248 | .container_12 .push_3 { 249 | left:165px; 250 | } 251 | 252 | .container_12 .push_4 { 253 | left:220px; 254 | } 255 | 256 | .container_12 .push_5 { 257 | left:275px; 258 | } 259 | 260 | .container_12 .push_6 { 261 | left:330px; 262 | } 263 | 264 | .container_12 .push_7 { 265 | left:385px; 266 | } 267 | 268 | .container_12 .push_8 { 269 | left:440px; 270 | } 271 | 272 | .container_12 .push_9 { 273 | left:495px; 274 | } 275 | 276 | .container_12 .push_10 { 277 | left:550px; 278 | } 279 | 280 | .container_12 .push_11 { 281 | left:605px; 282 | } 283 | 284 | 285 | 286 | /* Pull Space >> 12 Columns 287 | ----------------------------------------------------------------------------------------------------*/ 288 | 289 | 290 | .container_12 .pull_1 { 291 | left:-55px; 292 | } 293 | 294 | .container_12 .pull_2 { 295 | left:-110px; 296 | } 297 | 298 | .container_12 .pull_3 { 299 | left:-165px; 300 | } 301 | 302 | .container_12 .pull_4 { 303 | left:-220px; 304 | } 305 | 306 | .container_12 .pull_5 { 307 | left:-275px; 308 | } 309 | 310 | .container_12 .pull_6 { 311 | left:-330px; 312 | } 313 | 314 | .container_12 .pull_7 { 315 | left:-385px; 316 | } 317 | 318 | .container_12 .pull_8 { 319 | left:-440px; 320 | } 321 | 322 | .container_12 .pull_9 { 323 | left:-495px; 324 | } 325 | 326 | .container_12 .pull_10 { 327 | left:-550px; 328 | } 329 | 330 | .container_12 .pull_11 { 331 | left:-605px; 332 | } 333 | 334 | 335 | 336 | 337 | /* `Clear Floated Elements 338 | ----------------------------------------------------------------------------------------------------*/ 339 | 340 | /* http://sonspring.com/journal/clearing-floats */ 341 | 342 | .clear { 343 | clear: both; 344 | display: block; 345 | overflow: hidden; 346 | visibility: hidden; 347 | width: 0; 348 | height: 0; 349 | } 350 | 351 | /* http://www.yuiblog.com/blog/2010/09/27/clearfix-reloaded-overflowhidden-demystified */ 352 | 353 | .clearfix:before, 354 | .clearfix:after { 355 | content: '\0020'; 356 | display: block; 357 | overflow: hidden; 358 | visibility: hidden; 359 | width: 0; 360 | height: 0; 361 | } 362 | 363 | .clearfix:after { 364 | clear: both; 365 | } 366 | 367 | /* 368 | The following zoom:1 rule is specifically for IE6 + IE7. 369 | Move to separate stylesheet if invalid CSS is a problem. 370 | */ 371 | 372 | .clearfix { 373 | zoom: 1; 374 | } -------------------------------------------------------------------------------- /app/assets/stylesheets/base/global.scss: -------------------------------------------------------------------------------- 1 | /* Make HTML 5 elements display block-level for consistent styling */ 2 | header, nav, article, footer, address, section { 3 | display: block; 4 | } 5 | 6 | /* Colors */ 7 | $white: white; 8 | $brown: #978e7f; 9 | $light_paper: #faf8eb; 10 | $dark_brown: #231f20; 11 | $red_orange: #f7481b; 12 | 13 | /* Fonts */ 14 | $monospace: anonymous-pro, 'Courier New', monospace; 15 | 16 | html { 17 | height: 100%; 18 | } 19 | 20 | body { 21 | background: url('/images/backgrounds/page_bg.png') $brown; 22 | font-family: myriad-pro, 'Helvetica Neue', Helvetica, Arial, sans-serif; 23 | min-height: 100%; 24 | } 25 | 26 | /* Text */ 27 | a { 28 | text-decoration: none; 29 | } 30 | 31 | h1 { 32 | border-top: 5px solid $brown; 33 | padding-top: 10px; 34 | font-size: 25px; 35 | font-weight: bold; 36 | margin-bottom: 25px; 37 | } 38 | 39 | h2 { 40 | border-top: 2px solid $brown; 41 | padding-top: 10px; 42 | font-size: 20px; 43 | font-weight: bold; 44 | margin: 30px 0 20px 0; 45 | } 46 | 47 | h3 { 48 | font-size: 16px; 49 | font-weight: bold; 50 | margin: 25px 0 15px 0; 51 | } 52 | 53 | p { 54 | margin-bottom: 15px; 55 | } 56 | 57 | code { 58 | font-family: $monospace; 59 | } 60 | 61 | pre { 62 | font-family: $monospace; 63 | background: $brown; 64 | color: $white; 65 | padding: 4px 8px; 66 | font-size: 14px; 67 | line-height: 18px; 68 | margin-bottom: 15px; 69 | overflow: auto; 70 | 71 | box-shadow: inset 0 1px 4px $dark_brown; 72 | } 73 | 74 | /* Page elements */ 75 | header { 76 | height: 117px; 77 | 78 | div.left { 79 | position: relative; 80 | 81 | div#hook { 82 | position: absolute; 83 | background: url('/images/content/hook.png') no-repeat; 84 | width: 59px; 85 | height: 177px; 86 | 87 | top: 0; 88 | left: 0; 89 | } 90 | 91 | div#name { 92 | position: absolute; 93 | font-size: 45px; 94 | color: $dark_brown; 95 | font-weight: 300; 96 | top: 45px; 97 | left: 30px; 98 | 99 | cursor: default; 100 | } 101 | } 102 | 103 | div.right { 104 | text-align: right; 105 | font-size: 25px; 106 | font-weight: 300; 107 | color: $white; 108 | padding-top: 62px; 109 | 110 | cursor: default; 111 | } 112 | } 113 | 114 | section.page { 115 | div.top_pattern { 116 | background: url('/images/backgrounds/paper_top.png') repeat-x; 117 | height: 48px; 118 | } 119 | 120 | div.content { 121 | background: url('/images/backgrounds/paper_bg.png'); 122 | min-height: 300px; 123 | font-family: ff-tisa-web-pro, Georgia, serif; 124 | color: $dark_brown; 125 | 126 | padding: 60px 0; 127 | font-size: 16px; 128 | line-height: 24px; 129 | 130 | a { 131 | color: $red_orange; 132 | } 133 | 134 | a:hover { 135 | color: $dark_brown; 136 | } 137 | 138 | p code { 139 | font-size: 14px; 140 | } 141 | } 142 | 143 | div.bottom_pattern { 144 | background: url('/images/backgrounds/paper_bottom.png') repeat-x; 145 | height: 46px; 146 | } 147 | } 148 | 149 | footer { 150 | height: 240px; 151 | background: url('/images/backgrounds/soil.png') repeat-x 0% 100%; 152 | 153 | div.left { 154 | div#attrib { 155 | line-height: 200px; 156 | font-size: 15px; 157 | color: $light_paper; 158 | cursor: default; 159 | font-weight: 300; 160 | 161 | a { 162 | color: $dark_brown; 163 | } 164 | 165 | a:hover { 166 | color: $light_paper; 167 | } 168 | 169 | img { 170 | vertical-align: middle; 171 | margin: 0 8px 0 9px; 172 | } 173 | } 174 | } 175 | 176 | div.right { 177 | height: 232px; 178 | background: url('/images/backgrounds/tree.png') no-repeat 100% 100%; 179 | } 180 | } -------------------------------------------------------------------------------- /app/assets/stylesheets/base/reset.scss: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2011, Yahoo! Inc. All rights reserved. 3 | Code licensed under the BSD License: 4 | http://developer.yahoo.com/yui/license.html 5 | version: 2.9.0 6 | */ 7 | html{color:#000;background:#FFF}body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,button,textarea,select,p,blockquote,th,td{margin:0;padding:0}table{border-collapse:collapse;border-spacing:0}fieldset,img{border:0}address,button,caption,cite,code,dfn,em,input,optgroup,option,select,strong,textarea,th,var{font:inherit}del,ins{text-decoration:none}li{list-style:none}caption,th{text-align:left}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal}q:before,q:after{content:''}abbr,acronym{border:0;font-variant:normal}sup{vertical-align:baseline}sub{vertical-align:baseline}legend{color:#000} -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | protect_from_forgery 3 | end 4 | -------------------------------------------------------------------------------- /app/controllers/home_controller.rb: -------------------------------------------------------------------------------- 1 | class HomeController < ApplicationController 2 | def index 3 | @markdown = Redcarpet::Markdown.new( 4 | Redcarpet::Render::HTML, 5 | :no_intra_emphasis => true, 6 | :fenced_code_blocks => true 7 | ) 8 | 9 | @readme = File.open(File.join(Rails.root, 'README.md')).read 10 | end 11 | end -------------------------------------------------------------------------------- /app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/mailers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jookyboi/rss2webhook/286950c9b14ac0aba847ed0bb42840a1816a19be/app/mailers/.gitkeep -------------------------------------------------------------------------------- /app/models/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jookyboi/rss2webhook/286950c9b14ac0aba847ed0bb42840a1816a19be/app/models/.gitkeep -------------------------------------------------------------------------------- /app/models/article.rb: -------------------------------------------------------------------------------- 1 | class Article 2 | include MongoMapper::Document 3 | 4 | # Written according to RSS 2.0 specs: http://cyber.law.harvard.edu/rss/rss.html 5 | 6 | # required 7 | key :title, String, :required => true 8 | key :link, String, :required => true 9 | key :description, String, :required => true 10 | 11 | # optional 12 | key :language, String 13 | key :copyright, String 14 | key :managingEditor, String 15 | key :webMaster, String 16 | key :pubDate, String 17 | key :lastBuildDate, Date 18 | key :category, String 19 | key :generator, String 20 | key :docs, String 21 | key :cloud, String 22 | key :ttl, Integer 23 | key :image, String 24 | key :rating, String 25 | key :textInput, String 26 | key :skipHours, Array 27 | key :skipDays, Array 28 | 29 | timestamps! 30 | end -------------------------------------------------------------------------------- /app/views/home/index.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= @markdown.render(@readme).html_safe %> 3 |
4 |
-------------------------------------------------------------------------------- /app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | rss2webook - open source Rails 5 | <%= stylesheet_link_tag "application" %> 6 | <%= javascript_include_tag "application" %> 7 | <%= csrf_meta_tags %> 8 | 9 | 12 | 13 | 14 | 15 | 19 | 20 | 21 | 37 | 38 | 39 | 40 | 41 | Fork me on GitHub 42 | 43 | 44 |
45 |
46 |   47 |
48 |
rss2webhook
49 |
50 |
51 | an open source Rails project 52 |
53 |
54 |
55 | 56 |
57 |
58 |
59 |
60 | <%= yield %> 61 |
62 |
63 |
64 |
65 | 66 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require ::File.expand_path('../config/environment', __FILE__) 4 | run Rss2webhook::Application 5 | -------------------------------------------------------------------------------- /config/application.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../boot', __FILE__) 2 | 3 | require 'rails/all' 4 | 5 | if defined?(Bundler) 6 | # If you precompile assets before deploying to production, use this line 7 | Bundler.require *Rails.groups(:assets => %w(development test)) 8 | # If you want your assets lazily compiled in production, use this line 9 | # Bundler.require(:default, :assets, Rails.env) 10 | end 11 | 12 | module Rss2webhook 13 | class Application < Rails::Application 14 | # Settings in config/environments/* take precedence over those specified here. 15 | # Application configuration should go into files in config/initializers 16 | # -- all .rb files in that directory are automatically loaded. 17 | 18 | # Custom directories with classes and modules you want to be autoloadable. 19 | config.autoload_paths += %W(#{config.root}/lib) 20 | config.autoload_paths += %W(#{config.root}/lib/delayed_jobs) 21 | 22 | # Only load the plugins named here, in the order given (default is alphabetical). 23 | # :all can be used as a placeholder for all plugins not explicitly named. 24 | # config.plugins = [ :exception_notification, :ssl_requirement, :all ] 25 | 26 | # Activate observers that should always be running. 27 | # config.active_record.observers = :cacher, :garbage_collector, :forum_observer 28 | 29 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. 30 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. 31 | # config.time_zone = 'Central Time (US & Canada)' 32 | 33 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. 34 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] 35 | # config.i18n.default_locale = :de 36 | 37 | # Configure the default encoding used in templates for Ruby 1.9. 38 | config.encoding = "utf-8" 39 | 40 | # Configure sensitive parameters which will be filtered from the log file. 41 | config.filter_parameters += [:password] 42 | 43 | # Enable the asset pipeline 44 | config.assets.enabled = true 45 | 46 | # Version of your assets, change this if you want to expire all your assets 47 | config.assets.version = '1.0' 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | 3 | # Set up gems listed in the Gemfile. 4 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 5 | 6 | require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE']) 7 | -------------------------------------------------------------------------------- /config/config.yml: -------------------------------------------------------------------------------- 1 | development: 2 | settings: 3 | process_on_start: false 4 | check_interval: 10 # in seconds 5 | 6 | rss_feeds: 7 | - 8 | connection: 9 | host: secure3.silverorange.com 10 | ssl: true 11 | resource: /rsstest/httpauth/rss_with_ssl_and_auth.xml 12 | auth: 13 | username: testuser 14 | password: testpass 15 | 16 | webhook: http://localhost:3011/hook 17 | output: 18 | room_id: 34343 19 | from: TMZ 20 | message: Body is |title| 21 | color: green 22 | format: json 23 | 24 | - 25 | connection: http://news.yahoo.com/rss/us 26 | webhook: http://localhost:3011/get_hook 27 | type: get 28 | output: 29 | article: 30 | from: Yahoo! News 31 | title: |title| 32 | link: |link| 33 | 34 | test: 35 | settings: 36 | process_on_start: false 37 | check_interval: 10 # in seconds 38 | 39 | rss_feeds: 40 | - 41 | connection: http://labs.silverorange.com/local/solabs/rsstest/rss_plain.xml 42 | webhook: http://example.com/webhook/post 43 | 44 | - 45 | connection: https://secure3.silverorange.com/rsstest/rss_with_ssl.xml 46 | webhook: http://example.com/webhook/get 47 | type: get 48 | 49 | - 50 | connection: 51 | host: labs.silverorange.com 52 | ssl: false 53 | resource: /local/solabs/rsstest/httpauth/rss_with_auth.xml 54 | auth: 55 | username: testuser 56 | password: testpass 57 | webhook: http://example.com/webhook/post_article 58 | output: 59 | article: 60 | title: My title is |title| from |link| with |description| 61 | link: |link| 62 | 63 | - 64 | connection: 65 | host: labs.silverorange.com 66 | ssl: true 67 | resource: /rsstest/httpauth/rss_with_ssl_and_auth.xml 68 | auth: 69 | username: testuser 70 | password: testpass 71 | webhook: http://example.com/webhook/post 72 | 73 | - 74 | connection: 75 | host: labs.silverorange.com 76 | ssl: true 77 | resource: /rsstest/httpauth/rss_with_ssl_and_auth.xml 78 | auth: 79 | username: testuser 80 | password: nonsense 81 | webhook: http://example.com/webhook/post 82 | 83 | - 84 | connection: 85 | host: labs.silverorange.com 86 | ssl: false 87 | resource: /rsstest/httpauth/rss_with_ssl_and_auth.xml 88 | auth: 89 | username: testuser 90 | password: testpass 91 | webhook: http://example.com/webhook/post 92 | 93 | -------------------------------------------------------------------------------- /config/database.yml: -------------------------------------------------------------------------------- 1 | development: 2 | adapter: postgresql 3 | encoding: unicode 4 | database: rss2webhook_development 5 | pool: 5 6 | username: rss2webhook 7 | password: forme 8 | 9 | test: 10 | adapter: postgresql 11 | encoding: unicode 12 | database: rss2webhook_test 13 | pool: 5 14 | username: rss2webhook 15 | password: forme 16 | 17 | production: 18 | adapter: postgresql 19 | encoding: unicode 20 | database: rss2webhook_production 21 | pool: 5 22 | username: rss2webhook 23 | password: forme 24 | -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the rails application 2 | require File.expand_path('../application', __FILE__) 3 | 4 | # Initialize the rails application 5 | Rss2webhook::Application.initialize! 6 | -------------------------------------------------------------------------------- /config/environments/development.rb: -------------------------------------------------------------------------------- 1 | Rss2webhook::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb 3 | 4 | # In the development environment your application's code is reloaded on 5 | # every request. This slows down response time but is perfect for development 6 | # since you don't have to restart the web server when you make code changes. 7 | config.cache_classes = false 8 | 9 | # Log error messages when you accidentally call methods on nil. 10 | config.whiny_nils = true 11 | 12 | # Show full error reports and disable caching 13 | config.consider_all_requests_local = true 14 | config.action_controller.perform_caching = false 15 | 16 | # Don't care if the mailer can't send 17 | config.action_mailer.raise_delivery_errors = false 18 | 19 | # Print deprecation notices to the Rails logger 20 | config.active_support.deprecation = :log 21 | 22 | # Only use best-standards-support built into browsers 23 | config.action_dispatch.best_standards_support = :builtin 24 | 25 | # Do not compress assets 26 | config.assets.compress = false 27 | 28 | # Expands the lines which load the assets 29 | config.assets.debug = true 30 | end 31 | -------------------------------------------------------------------------------- /config/environments/production.rb: -------------------------------------------------------------------------------- 1 | Rss2webhook::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb 3 | 4 | # Code is not reloaded between requests 5 | config.cache_classes = true 6 | 7 | # Full error reports are disabled and caching is turned on 8 | config.consider_all_requests_local = false 9 | config.action_controller.perform_caching = true 10 | 11 | # Disable Rails's static asset server (Apache or nginx will already do this) 12 | config.serve_static_assets = false 13 | 14 | # Compress JavaScripts and CSS 15 | config.assets.compress = true 16 | 17 | # Don't fallback to assets pipeline if a precompiled asset is missed 18 | config.assets.compile = false 19 | 20 | # Generate digests for assets URLs 21 | config.assets.digest = true 22 | 23 | # Defaults to Rails.root.join("public/assets") 24 | # config.assets.manifest = YOUR_PATH 25 | 26 | # Specifies the header that your server uses for sending files 27 | # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache 28 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx 29 | 30 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 31 | # config.force_ssl = true 32 | 33 | # See everything in the log (default is :info) 34 | # config.log_level = :debug 35 | 36 | # Use a different logger for distributed setups 37 | # config.logger = SyslogLogger.new 38 | 39 | # Use a different cache store in production 40 | # config.cache_store = :mem_cache_store 41 | 42 | # Enable serving of images, stylesheets, and JavaScripts from an asset server 43 | # config.action_controller.asset_host = "http://assets.example.com" 44 | 45 | # Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added) 46 | # config.assets.precompile += %w( search.js ) 47 | 48 | # Disable delivery errors, bad email addresses will be ignored 49 | # config.action_mailer.raise_delivery_errors = false 50 | 51 | # Enable threaded mode 52 | # config.threadsafe! 53 | 54 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 55 | # the I18n.default_locale when a translation can not be found) 56 | config.i18n.fallbacks = true 57 | 58 | # Send deprecation notices to registered listeners 59 | config.active_support.deprecation = :notify 60 | end 61 | -------------------------------------------------------------------------------- /config/environments/test.rb: -------------------------------------------------------------------------------- 1 | Rss2webhook::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = true 9 | 10 | # Configure static asset server for tests with Cache-Control for performance 11 | config.serve_static_assets = true 12 | config.static_cache_control = "public, max-age=3600" 13 | 14 | # Log error messages when you accidentally call methods on nil 15 | config.whiny_nils = true 16 | 17 | # Show full error reports and disable caching 18 | config.consider_all_requests_local = true 19 | config.action_controller.perform_caching = false 20 | 21 | # Raise exceptions instead of rendering exception templates 22 | config.action_dispatch.show_exceptions = false 23 | 24 | # Disable request forgery protection in test environment 25 | config.action_controller.allow_forgery_protection = false 26 | 27 | # Tell Action Mailer not to deliver emails to the real world. 28 | # The :test delivery method accumulates sent emails in the 29 | # ActionMailer::Base.deliveries array. 30 | config.action_mailer.delivery_method = :test 31 | 32 | # Use SQL instead of Active Record's schema dumper when creating the test database. 33 | # This is necessary if your schema can't be completely dumped by the schema dumper, 34 | # like if you have constraints or database-specific column types 35 | # config.active_record.schema_format = :sql 36 | 37 | # Print deprecation notices to the stderr 38 | config.active_support.deprecation = :stderr 39 | 40 | # Allow pass debug_assets=true as a query parameter to load pages with unpackaged assets 41 | config.assets.allow_debugging = true 42 | end 43 | -------------------------------------------------------------------------------- /config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /config/initializers/config.rb: -------------------------------------------------------------------------------- 1 | RSS_CONFIG = YAML.load_file(File.join(Rails.root, 'config', 'config.yml'))[Rails.env] -------------------------------------------------------------------------------- /config/initializers/delayed_jobs.rb: -------------------------------------------------------------------------------- 1 | silence_warnings do 2 | Delayed::Job.const_set("MAX_ATTEMPTS", 1) 3 | Delayed::Job.const_set("MAX_RUN_TIME", 5.minutes) 4 | end -------------------------------------------------------------------------------- /config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format 4 | # (all these examples are active by default): 5 | # ActiveSupport::Inflector.inflections do |inflect| 6 | # inflect.plural /^(ox)$/i, '\1en' 7 | # inflect.singular /^(ox)en/i, '\1' 8 | # inflect.irregular 'person', 'people' 9 | # inflect.uncountable %w( fish sheep ) 10 | # end 11 | -------------------------------------------------------------------------------- /config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | # Mime::Type.register_alias "text/html", :iphone 6 | -------------------------------------------------------------------------------- /config/initializers/mongo.rb: -------------------------------------------------------------------------------- 1 | if Rails.env == 'development' || Rails.env == 'test' 2 | 3 | MongoMapper.database = "rss2webhook_#{Rails.env}" 4 | 5 | elsif Rails.env == 'production' 6 | 7 | # Example MongoLab settings for Heroku 8 | #MongoMapper.config = { Rails.env => {'uri' => ENV['MONGOLAB_URI']} } 9 | 10 | # Example MongoHQ settings for Heroku 11 | #MongoMapper.config = { Rails.env => {'uri' => ENV['MONGOHQ_URL']} } 12 | 13 | MongoMapper.connect(Rails.env) 14 | end 15 | -------------------------------------------------------------------------------- /config/initializers/secret_token.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | # Make sure the secret is at least 30 characters and all random, 6 | # no regular words or you'll be exposed to dictionary attacks. 7 | Rss2webhook::Application.config.secret_token = '971786c8ba03b4e57003f6770dab2e3d9d2565a6575ed91c64a5b77c4385a8316bdfa0f8ad1d399a1f949a7ab248be7a911b0f960bd269d5eee6ae999834b23e' 8 | -------------------------------------------------------------------------------- /config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Rss2webhook::Application.config.session_store :cookie_store, key: '_rss2webhook_session' 4 | 5 | # Use the database for sessions instead of the cookie-based default, 6 | # which shouldn't be used to store highly confidential information 7 | # (create the session table with "rails generate session_migration") 8 | # Rss2webhook::Application.config.session_store :active_record_store 9 | -------------------------------------------------------------------------------- /config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | # 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] 9 | end 10 | 11 | # Disable root element in JSON by default. 12 | ActiveSupport.on_load(:active_record) do 13 | self.include_root_in_json = false 14 | end 15 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Sample localization file for English. Add more files in this directory for other locales. 2 | # See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. 3 | 4 | en: 5 | hello: "Hello world" 6 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | Rss2webhook::Application.routes.draw do 2 | root :to => 'home#index' 3 | end 4 | -------------------------------------------------------------------------------- /db/migrate/20111202224006_create_delayed_jobs.rb: -------------------------------------------------------------------------------- 1 | class CreateDelayedJobs < ActiveRecord::Migration 2 | def self.up 3 | create_table :delayed_jobs, :force => true do |table| 4 | table.integer :priority, :default => 0 # Allows some jobs to jump to the front of the queue 5 | table.integer :attempts, :default => 0 # Provides for retries, but still fail eventually. 6 | table.text :handler # YAML-encoded string of the object that will do work 7 | table.text :last_error # reason for last failure (See Note below) 8 | table.datetime :run_at # When to run. Could be Time.zone.now for immediately, or sometime in the future. 9 | table.datetime :locked_at # Set when a client is working on this object 10 | table.datetime :failed_at # Set when all retries have failed (actually, by default, the record is deleted instead) 11 | table.string :locked_by # Who is working on this object (if locked) 12 | table.timestamps 13 | end 14 | 15 | add_index :delayed_jobs, [:priority, :run_at], :name => 'delayed_jobs_priority' 16 | end 17 | 18 | def self.down 19 | drop_table :delayed_jobs 20 | end 21 | end -------------------------------------------------------------------------------- /db/schema.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # This file is auto-generated from the current state of the database. Instead 3 | # of editing this file, please use the migrations feature of Active Record to 4 | # incrementally modify your database, and then regenerate this schema definition. 5 | # 6 | # Note that this schema.rb definition is the authoritative source for your 7 | # database schema. If you need to create the application database on another 8 | # system, you should be using db:schema:load, not running all the migrations 9 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations 10 | # you'll amass, the slower it'll run and the greater likelihood for issues). 11 | # 12 | # It's strongly recommended to check this file into your version control system. 13 | 14 | ActiveRecord::Schema.define(:version => 20111202224006) do 15 | 16 | create_table "delayed_jobs", :force => true do |t| 17 | t.integer "priority", :default => 0 18 | t.integer "attempts", :default => 0 19 | t.text "handler" 20 | t.text "last_error" 21 | t.datetime "run_at" 22 | t.datetime "locked_at" 23 | t.datetime "failed_at" 24 | t.string "locked_by" 25 | t.datetime "created_at" 26 | t.datetime "updated_at" 27 | end 28 | 29 | add_index "delayed_jobs", ["priority", "run_at"], :name => "delayed_jobs_priority" 30 | 31 | end 32 | -------------------------------------------------------------------------------- /db/seeds.rb: -------------------------------------------------------------------------------- 1 | # This file should contain all the record creation needed to seed the database with its default values. 2 | # The data can then be loaded with the rake db:seed (or created alongside the db with db:setup). 3 | # 4 | # Examples: 5 | # 6 | # cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }]) 7 | # Mayor.create(name: 'Emanuel', city: cities.first) 8 | -------------------------------------------------------------------------------- /doc/README_FOR_APP: -------------------------------------------------------------------------------- 1 | Use this README file to introduce your application and point to useful places in the API for learning more. 2 | Run "rake doc:app" to generate API documentation for your models, controllers, helpers, and libraries. 3 | -------------------------------------------------------------------------------- /lib/delayed_jobs/process_new_articles_job.rb: -------------------------------------------------------------------------------- 1 | require 'rss/2.0' 2 | require 'net/https' 3 | 4 | class ProcessNewArticlesJob < Struct.new(:rss_feed, :settings) 5 | def perform 6 | content = '' 7 | 8 | # support basic auth over ssl 9 | connection = rss_feed['connection'] 10 | 11 | if connection['auth'] 12 | content = fetch_rss_over_auth(rss_feed) 13 | else 14 | open(rss_feed['connection']) do |s| 15 | content = s.read 16 | end 17 | end 18 | 19 | # parse 20 | feed = RSS::Parser.parse(content, false) 21 | 22 | # all articles previously fetched 23 | feed_articles = Article.where(:feed_url => feed.channel.link) 24 | first_fetch = !feed_articles.any? 25 | 26 | # insert unique items into mongo 27 | feed.items.reverse.each do |item| 28 | item_hash = Hash.from_xml(item.to_s)['item'] 29 | item_hash['feed_url'] = feed.channel.link 30 | 31 | # collection could change in the loop 32 | unless Article.where(:feed_url => feed.channel.link, :link => item_hash['link']).any? 33 | 34 | if !first_fetch || (first_fetch && settings['process_on_start']) 35 | call_webhook(item_hash, rss_feed) 36 | end 37 | 38 | # assume that items with different URLs are different 39 | article = Article.new(item_hash) 40 | article.save! 41 | end 42 | end 43 | 44 | schedule_next(settings['check_interval']) 45 | end 46 | 47 | private 48 | 49 | def fetch_rss_over_auth(rss_feed) 50 | connection = rss_feed['connection'] 51 | port = 80 52 | 53 | if connection['ssl'] 54 | port = 443 55 | end 56 | 57 | content = href = '' 58 | begin 59 | http = Net::HTTP.new(connection['host'], port) 60 | http.use_ssl = connection['ssl'] 61 | 62 | http.start do |http| 63 | req = Net::HTTP::Get.new(connection['resource']) 64 | req.basic_auth(connection['auth']['username'], connection['auth']['password']) 65 | 66 | response = http.request(req) 67 | content = response.body 68 | end 69 | end 70 | 71 | content 72 | end 73 | 74 | def call_webhook(article_hash, rss_feed) 75 | webhook = rss_feed['webhook'] 76 | output_settings = nil 77 | 78 | if rss_feed['output'] 79 | output_settings = Marshal.load(Marshal.dump(rss_feed['output'])) # don't change the original 80 | end 81 | 82 | output_hash = Hash.new 83 | 84 | if output_settings 85 | # replace any placeholders 86 | interpolate_output_with_values(output_settings, article_hash) 87 | output_hash = output_settings 88 | else 89 | output_hash['article'] = article_hash.to_json 90 | end 91 | 92 | # post/get the request 93 | request_type = rss_feed['type'] 94 | begin 95 | if request_type && request_type == 'get' 96 | RestClient.get webhook, output_hash 97 | else 98 | RestClient.post webhook, output_hash 99 | end 100 | rescue => e 101 | e.response 102 | end 103 | end 104 | 105 | def interpolate_output_with_values(node, article_hash) 106 | node.each do |k, v| 107 | if v.is_a? Hash 108 | interpolate_output_with_values(v, article_hash) 109 | else 110 | if v.is_a? String 111 | regex = /\|([A-Za-z0-9_]+)\|/i 112 | matches = regex.match v 113 | 114 | if matches 115 | node[k] = v.gsub(regex) do |s| 116 | eval "article_hash['#{$1}']" 117 | end 118 | end 119 | end 120 | end 121 | end 122 | end 123 | 124 | def schedule_next(check_interval) 125 | Delayed::Job.enqueue self, 0, Time.now + check_interval 126 | end 127 | end -------------------------------------------------------------------------------- /lib/tasks/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jookyboi/rss2webhook/286950c9b14ac0aba847ed0bb42840a1816a19be/lib/tasks/.gitkeep -------------------------------------------------------------------------------- /lib/tasks/restart_processing.rake: -------------------------------------------------------------------------------- 1 | desc 'Restart processing new rss articles' 2 | task :restart_processing => :environment do 3 | # stop 4 | Delayed::Job.all.each do |dj| 5 | dj.delete 6 | end 7 | 8 | #start 9 | config = RSS_CONFIG 10 | feeds = config['rss_feeds'] 11 | 12 | feeds.each do |feed| 13 | Delayed::Job.enqueue ProcessNewArticlesJob.new(feed, config['settings']) 14 | end 15 | end -------------------------------------------------------------------------------- /lib/tasks/start_processing.rake: -------------------------------------------------------------------------------- 1 | desc 'Start processing new rss feed articles' 2 | task :start_processing => :environment do 3 | config = RSS_CONFIG 4 | feeds = config['rss_feeds'] 5 | 6 | feeds.each do |feed| 7 | Delayed::Job.enqueue ProcessNewArticlesJob.new(feed, config['settings']) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/tasks/stop_processing.rake: -------------------------------------------------------------------------------- 1 | desc 'Stop processing new rss articles' 2 | task :stop_processing => :environment do 3 | Delayed::Job.all.each do |dj| 4 | dj.delete 5 | end 6 | end -------------------------------------------------------------------------------- /log/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jookyboi/rss2webhook/286950c9b14ac0aba847ed0bb42840a1816a19be/log/.gitkeep -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 17 | 18 | 19 | 20 | 21 |
22 |

The page you were looking for doesn't exist.

23 |

You may have mistyped the address or the page may have moved.

24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 17 | 18 | 19 | 20 | 21 |
22 |

The change you wanted was rejected.

23 |

Maybe you tried to change something you didn't have access to.

24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 17 | 18 | 19 | 20 | 21 |
22 |

We're sorry, but something went wrong.

23 |

We've been notified about this issue and we'll take a look at it shortly.

24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jookyboi/rss2webhook/286950c9b14ac0aba847ed0bb42840a1816a19be/public/favicon.ico -------------------------------------------------------------------------------- /public/images/backgrounds/page_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jookyboi/rss2webhook/286950c9b14ac0aba847ed0bb42840a1816a19be/public/images/backgrounds/page_bg.png -------------------------------------------------------------------------------- /public/images/backgrounds/paper_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jookyboi/rss2webhook/286950c9b14ac0aba847ed0bb42840a1816a19be/public/images/backgrounds/paper_bg.png -------------------------------------------------------------------------------- /public/images/backgrounds/paper_bottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jookyboi/rss2webhook/286950c9b14ac0aba847ed0bb42840a1816a19be/public/images/backgrounds/paper_bottom.png -------------------------------------------------------------------------------- /public/images/backgrounds/paper_top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jookyboi/rss2webhook/286950c9b14ac0aba847ed0bb42840a1816a19be/public/images/backgrounds/paper_top.png -------------------------------------------------------------------------------- /public/images/backgrounds/soil.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jookyboi/rss2webhook/286950c9b14ac0aba847ed0bb42840a1816a19be/public/images/backgrounds/soil.png -------------------------------------------------------------------------------- /public/images/backgrounds/tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jookyboi/rss2webhook/286950c9b14ac0aba847ed0bb42840a1816a19be/public/images/backgrounds/tree.png -------------------------------------------------------------------------------- /public/images/content/hook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jookyboi/rss2webhook/286950c9b14ac0aba847ed0bb42840a1816a19be/public/images/content/hook.png -------------------------------------------------------------------------------- /public/images/icons/penguin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jookyboi/rss2webhook/286950c9b14ac0aba847ed0bb42840a1816a19be/public/images/icons/penguin.png -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file 2 | # 3 | # To ban all spiders from the entire site uncomment the next two lines: 4 | # User-Agent: * 5 | # Disallow: / 6 | -------------------------------------------------------------------------------- /script/delayed_job: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require File.expand_path(File.join(File.dirname(__FILE__), '..', 'config', 'environment')) 4 | require 'delayed/command' 5 | Delayed::Command.new(ARGV).daemonize 6 | -------------------------------------------------------------------------------- /script/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application. 3 | 4 | APP_PATH = File.expand_path('../../config/application', __FILE__) 5 | require File.expand_path('../../config/boot', __FILE__) 6 | require 'rails/commands' 7 | -------------------------------------------------------------------------------- /spec/delayed_jobs/process_new_articles_job_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe ProcessNewArticlesJob do 4 | before(:each) do 5 | Article.all.each do |a| 6 | a.delete 7 | end 8 | 9 | Delayed::Job.all.each do |job| 10 | job.delete 11 | end 12 | 13 | stub_requests 14 | 15 | @config = RSS_CONFIG 16 | @feeds = @config['rss_feeds'] 17 | end 18 | 19 | describe 'Fetch RSS' do 20 | it 'should process simple unauthenticated rss' do 21 | enqueue_process(@feeds[0], @config['settings']) 22 | work_off 23 | 24 | item = Article.where(:link => 'http://labs.silverorange.com/archives/2003/june/canyousaythat') 25 | item.any?.should be(true) 26 | item.size.should be(1) 27 | 28 | # ensure all the properties are on the article 29 | article = item.first 30 | article.description.should_not be(nil) 31 | article.description.should_not be "" 32 | 33 | article.title.should_not be(nil) 34 | article.link.should match 'http://labs.silverorange.com/archives/2003/june/canyousaythat' 35 | article.guid.should_not be(nil) 36 | article.comments.should_not be(nil) 37 | 38 | Article.all.size.should be(6) 39 | end 40 | 41 | it 'should process rss over https' do 42 | enqueue_process(@feeds[1], @config['settings']) 43 | work_off 44 | 45 | Article.all.size.should be(6) 46 | end 47 | 48 | it 'should process authenticated rss over http' do 49 | enqueue_process(@feeds[2], @config['settings']) 50 | work_off 51 | 52 | Article.all.size.should be(6) 53 | 54 | item = Article.where(:link => 'http://labs.silverorange.com/archives/2003/june/introducingthe').first 55 | item.title.should match 'Introducing the silverorange Labs weblog' 56 | end 57 | 58 | it 'should process authenticated rss over https' do 59 | enqueue_process(@feeds[3], @config['settings']) 60 | work_off 61 | 62 | Article.all.size.should be(6) 63 | end 64 | 65 | it 'should not process incorrect auth over https' do 66 | enqueue_process(@feeds[4], @config['settings']) 67 | work_off 68 | 69 | Article.all.size.should be(0) 70 | end 71 | 72 | it 'should not process authenticated ssl over http' do 73 | enqueue_process(@feeds[5], @config['settings']) 74 | work_off 75 | 76 | Article.all.size.should be(0) 77 | end 78 | end 79 | 80 | describe 'Call webhook' do 81 | it 'should send correct article' do 82 | # full fetch 83 | enqueue_process(@feeds[0], @config['settings']) 84 | work_off 85 | 86 | Article.all.size.should be(6) 87 | 88 | # take one out 89 | link = 'http://labs.silverorange.com/archives/2003/june/introducingthe' 90 | Article.where(:link => link).first.delete 91 | Article.all.size.should be(5) 92 | 93 | Article.where(:link => link).any?.should be(false) 94 | 95 | # cause one to be sent to webhook 96 | enqueue_process(@feeds[0], @config['settings']) 97 | work_off 98 | 99 | Article.where(:link => link).any?.should be(true) 100 | Article.all.size.should be(6) 101 | 102 | WebMock.should have_requested(:get, 'labs.silverorange.com/local/solabs/rsstest/rss_plain.xml').twice 103 | WebMock.should have_requested(:post, 'example.com/webhook/post').with { 104 | |req| req.body.include?('params[article]') && req.body.include?('introducingthe') 105 | } 106 | end 107 | 108 | it 'should send correct article via get request' do 109 | enqueue_process(@feeds[1], @config['settings']) 110 | work_off 111 | 112 | link = 'http://labs.silverorange.com/archives/2003/june/photogallery' 113 | Article.where(:link => link).first.delete 114 | Article.all.size.should be(5) 115 | 116 | # cause one to be sent to webhook 117 | enqueue_process(@feeds[1], @config['settings']) 118 | work_off 119 | 120 | Article.where(:link => link).any?.should be(true) 121 | Article.all.size.should be(6) 122 | 123 | WebMock.should have_requested(:get, 'https://secure3.silverorange.com/rsstest/rss_with_ssl.xml').twice 124 | WebMock.should have_requested(:get, /.*example.com\/webhook\/get.*/).with { 125 | |req| req.uri.query.include?('photogallery') 126 | } 127 | end 128 | 129 | it 'should send correct article with proper output' do 130 | enqueue_process(@feeds[2], @config['settings']) 131 | work_off 132 | 133 | link = 'http://labs.silverorange.com/archives/2003/june/photogallery' 134 | Article.where(:link => link).first.delete 135 | Article.all.size.should be(5) 136 | 137 | # cause one to be sent to webhook 138 | enqueue_process(@feeds[2], @config['settings']) 139 | work_off 140 | 141 | Article.where(:link => link).any?.should be(true) 142 | Article.all.size.should be(6) 143 | 144 | WebMock.should have_requested(:get, 'http://testuser:testpass@labs.silverorange.com/local/solabs/rsstest/httpauth/rss_with_auth.xml').twice 145 | WebMock.should have_requested(:post, 'example.com/webhook/post_article').with { 146 | |req| req.body.include?('params[article][title]') && req.body.include?('params[article][link]') && req.body.include?('photogallery') 147 | } 148 | end 149 | end 150 | 151 | end 152 | 153 | private 154 | 155 | def stub_requests 156 | WebMock.reset! 157 | 158 | # allow fetches for RSS to pass right through 159 | WebMock.disable_net_connect!(:allow => ['labs.silverorange.com', 'secure3.silverorange.com']) 160 | 161 | stub_request(:get, /.*example.com\/webhook\/get.*/) 162 | stub_request(:post, 'example.com/webhook/post') 163 | stub_request(:post, 'example.com/webhook/post_article') 164 | end 165 | 166 | def enqueue_process(rss_feed, settings) 167 | Delayed::Job.enqueue ProcessNewArticlesJob.new(rss_feed, settings) 168 | end 169 | 170 | def work_off 171 | Delayed::Worker.new.work_off 172 | end -------------------------------------------------------------------------------- /spec/models/article_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Article do 4 | before(:each) do 5 | Article.all.each do |a| 6 | a.delete 7 | end 8 | end 9 | 10 | it 'should let a valid article be saved' do 11 | article = Article.new( 12 | { 13 | :title => 'My new article', 14 | :link => 'http://www.google.com', 15 | :description => 'Test article' 16 | } 17 | ) 18 | 19 | article.save! 20 | article.should have(:no).errors 21 | end 22 | 23 | it 'should not let invalid articles be saved' do 24 | article = Article.new( 25 | { 26 | :link => 'http://www.google.com', 27 | :description => 'Test article' 28 | } 29 | ) 30 | 31 | article.save 32 | article.should have(1).error_on(:title) 33 | 34 | article = Article.new 35 | article.save 36 | article.should have(1).error_on(:title) 37 | article.should have(1).error_on(:link) 38 | article.should have(1).error_on(:description) 39 | end 40 | end -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'webmock/rspec' 2 | require 'simplecov' 3 | 4 | SimpleCov.start 'rails' 5 | 6 | # This file is copied to spec/ when you run 'rails generate rspec:install' 7 | ENV["RAILS_ENV"] ||= 'test' 8 | require File.expand_path("../../config/environment", __FILE__) 9 | require 'rspec/rails' 10 | require 'rspec/autorun' 11 | 12 | # Requires supporting ruby files with custom matchers and macros, etc, 13 | # in spec/support/ and its subdirectories. 14 | Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f} 15 | 16 | RSpec.configure do |config| 17 | config.mock_with :rspec 18 | 19 | # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures 20 | config.fixture_path = "#{::Rails.root}/spec/fixtures" 21 | 22 | # If you're not using ActiveRecord, or you'd prefer not to run each of your 23 | # examples within a transaction, remove the following line or assign false 24 | # instead of true. 25 | config.use_transactional_fixtures = true 26 | 27 | # If true, the base class of anonymous controllers will be inferred 28 | # automatically. This will be the default behavior in future versions of 29 | # rspec-rails. 30 | config.infer_base_class_for_anonymous_controllers = false 31 | end 32 | -------------------------------------------------------------------------------- /vendor/assets/stylesheets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jookyboi/rss2webhook/286950c9b14ac0aba847ed0bb42840a1816a19be/vendor/assets/stylesheets/.gitkeep -------------------------------------------------------------------------------- /vendor/plugins/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jookyboi/rss2webhook/286950c9b14ac0aba847ed0bb42840a1816a19be/vendor/plugins/.gitkeep --------------------------------------------------------------------------------