summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAzul <azul@riseup.net>2016-09-14 11:11:48 +0200
committerAzul <azul@riseup.net>2016-09-14 11:11:48 +0200
commitf575f57915d5369269849352a70b0e4704c3fd19 (patch)
tree7b13646448e64e560101493eeac1a03c3a25dbd8
parent92481029074f34342cc35937f3aab94aca7887c7 (diff)
parent22478d315af3590d2a344eb1aa8cf2aec0730506 (diff)
Merge remote-tracking branch 'pr/237' into develop
Twitter Feature on Main-View from @loadtocode Twitter feature within home/index of LEAP web app as feed of tweets of one user's twitter account How to use it: set up an Application-only authentication by twitter (https://dev.twitter.com/oauth/application-only) and then create a config/secrets.yml including: ``` development: twitter: enabled: false # set to true for usage twitter_handle: XXXXX #put your twitter handle here bearer_token: XXXXX #put your bearer token here test: twitter: enabled: false # set to true for usage twitter_handle: XXXXX #put your twitter handle here bearer_token: XXXXX #put your bearer token here ``` things in the PR: - [x] gemfile:twitter - [x] twitter helper (caching (to avoid reaching twitter rate limits) - [x] index twitter view - [x] twitter css (twitter design as discussed in call: Profil-Header: Picture, Link to twitter + twitter_handle; removed picture and twitter_handle from individual tweet, heading aligns at the top, only the last 3 tweets are shown) - [x] script to generate bearer token - [x] script to invalidate generated bearer token - [x] documentation (Readme + Doc)
-rw-r--r--.gitignore1
-rw-r--r--Gemfile1
-rw-r--r--Gemfile.lock35
-rw-r--r--README.md3
-rw-r--r--app/assets/images/Twitter_Logo_Blue.pngbin0 -> 4298 bytes
-rw-r--r--app/assets/stylesheets/application.scss1
-rw-r--r--app/assets/stylesheets/twitter.scss72
-rw-r--r--app/helpers/twitter_helper.rb46
-rw-r--r--app/views/home/_content.html.haml31
-rw-r--r--app/views/twitter/_index.html.erb26
-rw-r--r--doc/TWITTER_FEED.md35
-rwxr-xr-xscript/generate_bearer_token84
-rwxr-xr-xscript/invalidate_bearer_token47
13 files changed, 368 insertions, 14 deletions
diff --git a/.gitignore b/.gitignore
index 8d49deb..f898d47 100644
--- a/.gitignore
+++ b/.gitignore
@@ -34,6 +34,7 @@ public/ca.crt
public/config/*
public/provider.json
config/config.yml
+config/secrets.yml
public/1/*
vendor/bundle/*
public/img
diff --git a/Gemfile b/Gemfile
index 1e82977..3b5435e 100644
--- a/Gemfile
+++ b/Gemfile
@@ -98,6 +98,7 @@ end
##
## OPTIONAL GEMS AND ENGINES
##
+gem 'twitter'
enabled_engines.each do |name, gem_info|
gem gem_info[:name], :path => gem_info[:path], :groups => gem_info[:env]
diff --git a/Gemfile.lock b/Gemfile.lock
index ab97719..8601b75 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -76,6 +76,7 @@ GEM
sass (>= 3.3.4)
braintree (2.65.0)
builder (>= 2.0.0)
+ buftok (0.2.0)
builder (3.2.2)
byebug (9.0.5)
capybara (2.7.1)
@@ -126,6 +127,9 @@ GEM
cucumber-wire (0.0.1)
debug_inspector (0.0.2)
diff-lcs (1.2.5)
+ domain_name (0.5.20160615)
+ unf (>= 0.0.5, < 1.0.0)
+ equalizer (0.0.10)
erubis (2.7.0)
execjs (2.7.0)
factory_girl (4.7.0)
@@ -140,6 +144,8 @@ GEM
sinatra
faker (1.6.6)
i18n (~> 0.5)
+ faraday (0.9.2)
+ multipart-post (>= 1.2, < 3)
ffi (1.9.14)
gherkin (4.0.0)
globalid (0.3.7)
@@ -157,7 +163,16 @@ GEM
haml (~> 4.0.0)
nokogiri (~> 1.6.0)
ruby_parser (~> 3.5)
+ http (1.0.4)
+ addressable (~> 2.3)
+ http-cookie (~> 1.0)
+ http-form_data (~> 1.0.1)
+ http_parser.rb (~> 0.6.0)
+ http-cookie (1.0.2)
+ domain_name (~> 0.5)
+ http-form_data (1.0.1)
http_accept_language (2.0.5)
+ http_parser.rb (0.6.0)
httpclient (2.8.1)
i18n (0.7.0)
i18n-missing_translations (0.0.1)
@@ -178,6 +193,8 @@ GEM
nokogiri (>= 1.5.9)
mail (2.6.4)
mime-types (>= 1.16, < 4)
+ memoizable (0.4.2)
+ thread_safe (~> 0.3, >= 0.3.1)
metaclass (0.0.4)
method_source (0.8.2)
mime-types (3.1)
@@ -190,6 +207,8 @@ GEM
metaclass (~> 0.0.1)
multi_json (1.12.1)
multi_test (0.1.2)
+ multipart-post (2.0.0)
+ naught (1.1.0)
nokogiri (1.6.8)
mini_portile2 (~> 2.1.0)
pkg-config (~> 1.1.7)
@@ -258,6 +277,7 @@ GEM
simple_form (3.2.1)
actionpack (> 4, < 5.1)
activemodel (> 4, < 5.1)
+ simple_oauth (0.3.1)
sinatra (1.4.7)
rack (~> 1.5)
rack-protection (~> 1.4)
@@ -278,10 +298,22 @@ GEM
thor (0.19.1)
thread_safe (0.3.5)
tilt (2.0.5)
+ twitter (5.16.0)
+ addressable (~> 2.3)
+ buftok (~> 0.2.0)
+ equalizer (= 0.0.10)
+ faraday (~> 0.9.0)
+ http (~> 1.0)
+ http_parser.rb (~> 0.6.0)
+ json (~> 1.8)
+ memoizable (~> 0.4.0)
+ naught (~> 1.0)
+ simple_oauth (~> 0.3.0)
tzinfo (1.2.2)
thread_safe (~> 0.1)
uglifier (3.0.1)
execjs (>= 0.3.0, < 3)
+ unf (0.2.0.beta2)
valid_email (0.0.13)
activemodel
mail (~> 2.6.1)
@@ -337,8 +369,9 @@ DEPENDENCIES
sass-rails
simple_form
therubyracer
+ twitter
uglifier
valid_email
BUNDLED WITH
- 1.11.2
+ 1.12.5
diff --git a/README.md b/README.md
index e68c117..923b239 100644
--- a/README.md
+++ b/README.md
@@ -9,6 +9,7 @@ The LEAP Web App provides the following functions:
* Webfinger access to user’s public keys
* Email aliases and forwarding
* Localized and Customizable documentation
+* Display of status updates from Twitter (access to tweets via Twitter API)
Written in: Ruby, Rails.
@@ -36,6 +37,7 @@ For more information, see these files in the ``doc`` directory:
* DEPLOY -- for notes on deployment.
* DEVELOP -- for developer notes.
* CUSTOM -- how to customize.
+* TWITTER_FEED -- how to use it.
External docs:
@@ -186,4 +188,3 @@ Known problems
attacks. These are very hard to prevent, because our protocol is
designed to allow query of a user database via proxy in order to
provide network perspective.
-
diff --git a/app/assets/images/Twitter_Logo_Blue.png b/app/assets/images/Twitter_Logo_Blue.png
new file mode 100644
index 0000000..b5eebc8
--- /dev/null
+++ b/app/assets/images/Twitter_Logo_Blue.png
Binary files differ
diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss
index 856a559..f42044b 100644
--- a/app/assets/stylesheets/application.scss
+++ b/app/assets/stylesheets/application.scss
@@ -16,6 +16,7 @@
// LEAP web app specific overrides
//
@import "leap";
+@import "twitter";
// And finally bootswatch style itself
// @import "bootswatch/cerulean/bootswatch";
diff --git a/app/assets/stylesheets/twitter.scss b/app/assets/stylesheets/twitter.scss
new file mode 100644
index 0000000..b2233d0
--- /dev/null
+++ b/app/assets/stylesheets/twitter.scss
@@ -0,0 +1,72 @@
+.twitter {
+ position: relative;
+}
+
+.twitter_header {
+ font-size: 16px;
+ text-align: left;
+ margin-bottom: 55px;
+ padding: 10px 8px;
+}
+
+.twitter_id {
+ position: absolute;
+}
+
+.twitter_image_frame {
+ display: block;
+ width: 40px;
+ height: 40px;
+ overflow: hidden;
+ position: absolute;
+ left: 0;
+ top: 0;
+ }
+
+.twitter_image_frame > img {
+ display: block;
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 30;
+ width: 100%;
+ margin: auto;
+ }
+
+.twitter_name {
+ padding-left: 55px;
+ line-height: 20px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ font-size: 16px;
+}
+
+// Body = displays tweets
+.twitter_list {
+ box-sizing: border-box;
+}
+
+.tweet {
+ border-top-style: solid;
+ border-color: lightgrey;
+ padding: 10px 8px;
+}
+
+.tweet_text {
+box-sizing: border-box;
+}
+
+.tweet_text_date {
+ text-align: right;
+ padding-top: 4px;
+ font-size: 12px ;
+}
+
+.twitter_footer {
+ border-top-style: solid;
+ border-color: lightgrey;
+ padding: 10px 8px;
+ font-style: italic;
+ font-size: 12px;
+}
diff --git a/app/helpers/twitter_helper.rb b/app/helpers/twitter_helper.rb
new file mode 100644
index 0000000..f824a03
--- /dev/null
+++ b/app/helpers/twitter_helper.rb
@@ -0,0 +1,46 @@
+module TwitterHelper
+ def twitter_enabled
+ if Rails.application.secrets.twitter
+ Rails.application.secrets.twitter['enabled'] == true
+ end
+ end
+
+ def twitter_client
+ Twitter::REST::Client.new do |config|
+ config.bearer_token = Rails.application.secrets.twitter['bearer_token']
+ end
+ end
+
+ def twitter_handle
+ Rails.application.secrets.twitter['twitter_handle']
+ end
+
+ def twitter_user_info
+ $twitter_user_info ||= []
+ end
+
+ def update_twitter_info
+ twitter_user_info[0] = Time.now
+ twitter_user_info[1] = twitter_client.user(twitter_handle).name
+ twitter_user_info[2] = twitter_client.user_timeline(twitter_handle).select{ |tweet| tweet.text.start_with?('RT','@')==false}.take(3)
+ end
+
+ def cached_info
+ if twitter_user_info[0] == nil
+ update_twitter_info
+ else
+ if Time.now > twitter_user_info[0] + 15.minutes
+ update_twitter_info
+ end
+ end
+ twitter_user_info
+ end
+
+ def twitter_name
+ cached_info[1]
+ end
+
+ def tweets
+ cached_info[2]
+ end
+end
diff --git a/app/views/home/_content.html.haml b/app/views/home/_content.html.haml
index 67e4533..5341189 100644
--- a/app/views/home/_content.html.haml
+++ b/app/views/home/_content.html.haml
@@ -1,12 +1,19 @@
-.row
- %h1= t(:welcome, :provider => APP_CONFIG[:domain])
- .p=t(:welcome_message_html)
-
-.row
- = home_page_buttons
-
- - if Rails.env == 'development'
- .row
- %hr
- %p
- = link_to "make donation", new_payment_path if APP_CONFIG[:payment].present?
+.col-md-8
+ .row
+ %h1= t(:welcome, :provider => APP_CONFIG[:domain])
+ .p=t(:welcome_message_html)
+
+ .row
+ = home_page_buttons
+
+.col-md-1
+
+.col-md-3
+ .row
+ = render 'twitter/index'
+
+ - if Rails.env == 'development'
+ .row
+ %hr
+ %p
+ = link_to "make donation", new_payment_path if APP_CONFIG[:payment].present?
diff --git a/app/views/twitter/_index.html.erb b/app/views/twitter/_index.html.erb
new file mode 100644
index 0000000..a7ebd1b
--- /dev/null
+++ b/app/views/twitter/_index.html.erb
@@ -0,0 +1,26 @@
+<% if twitter_enabled == true %>
+ <div class="twitter">
+
+ <div class="twitter_header">
+ <div class="twitter_id">
+ <div class="twitter_image_frame"><%= image_tag("Twitter_Logo_Blue.png") %></div>
+ <div class="twitter_name"><%= twitter_name%><br><a href="https://twitter.com/loadtocode">@<%= twitter_handle %></a></div>
+ </div>
+ </div>
+
+ <div class="twitter_list">
+ <% tweets.each do |e| %>
+ <div class="tweet">
+ <div class="tweet_text"><%= " #{e.text}" %>
+ </div>
+ <div class="tweet_text_date">tweeted on <% t = e.created_at%> <%= t.strftime("%m/%d/%y").to_s %>
+ </div>
+ </div>
+ <% end %>
+ </div>
+
+ <div class="twitter_footer">
+ <p>This feed uses a Ruby interface to access the Twitter API. Within LEAP Twitter does not track you.</p>
+ </div>
+</div>
+<% end %>
diff --git a/doc/TWITTER_FEED.md b/doc/TWITTER_FEED.md
new file mode 100644
index 0000000..dd3954c
--- /dev/null
+++ b/doc/TWITTER_FEED.md
@@ -0,0 +1,35 @@
+# Display of status updates from twitter on main view #
+
+This is a feature to include status updates that displays most recent tweets
+of a (determined) twitter account (accessed via Twitter API).
+If you chose to use it, the feature gets included in `home/index` of
+LEAP web app (as part of the main view).
+
+## How to use it ##
+
+* Create Twitter Application on https://apps.twitter.com/
+ * Visit https://apps.twitter.com/ and log in with the twitter account you want to use
+ * Make sure you have a mobile phone number registered with your account to be able to proceed
+ * Choose the option to `Create New App`
+ * Fill in Application Details and Developer Agreement and `Create your Twitter application`
+ * Choose the section "Keys and Access Tokens" to get your consumer key and consumer secret
+ * Optional: Go to section "Permissions" and change the "Access" from `Read and Write` (by default) to `Read only`
+ * Have your consumer key and secret by hand for one of the next steps
+
+* Activate the feature within your local LEAP Web Application
+ * If not already existing create a secrets-file in /config with the name secrets.yml (`/config/secrets.yml`)
+ * Secrets-file should contain the following, make sure its in YAML: {"development"=> {"twitter"=>{"enabled"=>false, "twitter_handle"=>"", "bearer_token"=>"", "twitter_picture"=>nil}}, "test"=>{"twitter"=>{"enabled"=>false, "twitter_handle"=>"", "bearer_token"=>"", "twitter_picture"=>nil}}}
+ * To have your bearer token created, run script in terminal being in the file of leap_web: `script/generate_bearer_token`
+ * To have the script run properly you have to add before running: `--key your_consumerkey --secret your_consumersecret`
+ * Add also `--projectroot your_projectroot --twitterhandle your_twitterhandle` as well to not have manually put the data in your secrets-file
+ * The full command looks like this: `script/generate_bearer_token --key your_consumerkey --secret your_consumersecret --projectroot your_projectroot --twitterhandle your_twitterhandle`
+ * If you didn't give all your information to the script, had a typo or want to change anything else, please do so by finding the secrets-file at `/config/secrets.yml`
+ * Make sure that the correct twitterhandle and bearer-token is included
+
+* Deactivate your bearer token
+ * To deactivate your generated bearer token you can run script/invalidate_bearer_token
+ * The full command looks like this: script/invalidate_bearer_token --key your_consumerkey --secret your_consumersecret --token your_bearer_token
+
+### Default avatar image ###
+
+This feature uses by default the twitter bird as avatar picture (Twitter_Logo_Blue.png). By using the Twitter trademarks, you agree to follow the Twitter Trademark Guidelines as well as Twitter's Terms of Service and all other Twitter rules and policies. Please find more details here: https://brand.twitter.com/.
diff --git a/script/generate_bearer_token b/script/generate_bearer_token
new file mode 100755
index 0000000..7091a8d
--- /dev/null
+++ b/script/generate_bearer_token
@@ -0,0 +1,84 @@
+#!/usr/bin/env ruby
+
+require "net/http"
+require "uri"
+require "json"
+require "base64"
+require "optparse"
+require "yaml"
+
+options = {}
+
+option_parser = OptionParser.new do |opts|
+ opts.banner = "Create your bearer_token for twitter by including following two [options], feel free to have your secrets-file created/filled giving the other information as well:"
+
+ opts.on("--key KEY", "consumer_key of your twitter application") do |key|
+ options[:conkey] = key
+ end
+
+ opts.on("--secret SECRET", "consumer_secret of your twitter application") do |secret|
+ options[:consec] = secret
+ end
+
+ opts.on("--projectroot DIR", "directory where leapweb is") do |projectroot|
+ options[:projectroot] = projectroot
+ end
+
+ opts.on("--twitterhandle TWI", "twitterhandle without @ which will be passed into secrets-file") do |twitterhandle|
+ options[:twitterhandle] = twitterhandle
+ end
+
+end
+
+option_parser.parse!
+
+if options[:conkey].nil? || options[:consec].nil? then
+ puts option_parser
+ exit
+else
+ consumer_key = options[:conkey]
+ consumer_secret = options[:consec]
+end
+
+uri = URI("https://api.twitter.com/oauth2/token")
+data = "grant_type=client_credentials"
+cre = Base64.strict_encode64("#{consumer_key}:#{consumer_secret}")
+authorization_headers = { "Authorization" => "Basic #{cre}"}
+
+Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
+ response = http.request_post(uri, data, authorization_headers)
+ token_hash = JSON.parse(response.body)
+ $bearer_token = token_hash["access_token"]
+end
+
+if options[:projectroot].nil? then
+ puts "You didn't tell us the directory to have your secrets-file created or being filled. Feel free to copy/paste your bearer_token:"
+ puts $bearer_token
+else
+ if File.exist?("#{options[:projectroot]}/leap_web/config/secrets.yml")
+ secrets = YAML.load_file("#{options[:projectroot]}/leap_web/config/secrets.yml")
+ else
+ puts "Please make sure that you created a secrets-file as described in the documentation or have given the correct directory. No secrets-file could be found."
+ exit
+ # secrets_content = {"twitter"=>{"enabled"=>false, "twitter_handle"=>"", "bearer_token"=>"", "twitter_picture"=>nil}}
+ # secrets = {"development"=> secrets_content, "test"=>secrets_content}
+ # secrets = {"development"=> {"twitter"=>{"enabled"=>false, "twitter_handle"=>"", "bearer_token"=>"", "twitter_picture"=>nil}}, "test"=>{"twitter"=>{"enabled"=>false, "twitter_handle"=>"", "bearer_token"=>"", "twitter_picture"=>nil}}}
+ # File.new("#{options[:projectroot]}/leap_web/config/secrets.yml", "w")
+ end
+
+ if options[:twitterhandle].nil? then
+ if secrets["development"]["twitter"]["twitter_handle"] == "" then
+ puts "You didn't put your twitter-handle neither in the secrets-file nor passed it as a flag. Don't forget that you can't use the twitter-feature without your twitter-handle."
+ end
+ else
+ secrets["development"]["twitter"]["twitter_handle"] = options[:twitterhandle]
+ secrets["test"]["twitter"]["twitter_handle"] = options[:twitterhandle]
+ end
+
+ secrets["development"]["twitter"]["bearer_token"] = $bearer_token
+ secrets["test"]["twitter"]["bearer_token"] = $bearer_token
+
+ File.open("#{options[:projectroot]}/leap_web/config/secrets.yml", "r+") do |file|
+ file.write(secrets.to_yaml)
+ end
+end
diff --git a/script/invalidate_bearer_token b/script/invalidate_bearer_token
new file mode 100755
index 0000000..eda1c7d
--- /dev/null
+++ b/script/invalidate_bearer_token
@@ -0,0 +1,47 @@
+#!/usr/bin/env ruby
+
+require "net/http"
+require "uri"
+require "json"
+require "base64"
+require "optparse"
+
+options = {}
+
+option_parser = OptionParser.new do |opts|
+ opts.banner = "Invalidate your bearer_token for twitter by including the following [options]. The bearer token can't be used afterwards anymore. Please create a new bearer-token if you want to activate the twitter feature again."
+
+ opts.on("--key KEY", "consumer_key of your twitter application") do |key|
+ options[:conkey] = key
+ end
+
+ opts.on("--secret SECRET", "consumer_secret of your twitter application") do |secret|
+ options[:consec] = secret
+ end
+
+ opts.on("--token TOKEN", "bearer token for twitter") do |token|
+ options[:token] = token
+ end
+
+end
+
+option_parser.parse!
+
+if options[:conkey].nil? || options[:consec].nil? || options[:token].nil? then
+ puts option_parser
+ exit
+else
+ consumer_key = options[:conkey]
+ consumer_secret = options[:consec]
+ bearer_token = options[:token]
+end
+
+uri = URI("https://api.twitter.com/oauth2/invalidate_token")
+data = "access_token=#{bearer_token}"
+cre = Base64.strict_encode64("#{consumer_key}:#{consumer_secret}")
+authorization_headers = { "Authorization" => "Basic #{cre}"}
+
+Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
+ response = http.request_post(uri, data, authorization_headers)
+ puts JSON.parse(response.body)
+end