Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added reply by tweet feature #3175

Merged
merged 44 commits into from
Mar 5, 2019
Merged
Show file tree
Hide file tree
Changes from 38 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
6521d27
Added reply by tweet feature
namangupta01 Aug 1, 2018
c255060
Updated schedule.rb file
namangupta01 Aug 1, 2018
9c4a9d5
Finalized reply-by-tweet
namangupta01 Aug 2, 2018
192db25
Corrected schema version
namangupta01 Aug 2, 2018
d835e93
Minor change
namangupta01 Aug 2, 2018
e5415f2
Added reply_by_tweet doc
namangupta01 Aug 4, 2018
20f02d9
Minor changes
namangupta01 Aug 4, 2018
4be39a4
Added twitter gem
namangupta01 Aug 4, 2018
245c4be
Minor changes
namangupta01 Aug 7, 2018
f65eb9a
Added Environment variables in Docker
namangupta01 Aug 7, 2018
09582ed
Added summery in Doc file
namangupta01 Aug 8, 2018
55af02e
Corrected schema version
namangupta01 Aug 8, 2018
b960952
Added some documentation
namangupta01 Aug 8, 2018
f5a680d
Added some documentation
namangupta01 Aug 8, 2018
712d7f2
Migration timestamp changed
namangupta01 Aug 9, 2018
3ceb0c2
Changed migration
namangupta01 Aug 9, 2018
73ba28c
Minor change
namangupta01 Aug 9, 2018
73df811
Minor changes
namangupta01 Aug 9, 2018
0a4ab12
Added rake to general gem list
namangupta01 Aug 9, 2018
9e88993
Added bundle exec for rake/rails tasks in schedule.rb
namangupta01 Aug 9, 2018
8073289
Added path env variable
namangupta01 Aug 9, 2018
974d62a
MINOR CHANGE
namangupta01 Aug 9, 2018
6a2a036
MINOR CHANGE
namangupta01 Aug 9, 2018
0d80b03
MINOR CHANGE
namangupta01 Aug 9, 2018
4d7fa41
MINOR CHANGE
namangupta01 Aug 9, 2018
4056f4a
Added print statement to check print
namangupta01 Aug 9, 2018
c973443
Minor change
namangupta01 Aug 10, 2018
f4d07f3
Minor change
namangupta01 Aug 10, 2018
8f1b4a3
Changed whenever config
namangupta01 Aug 10, 2018
c3a68ef
Minor change
namangupta01 Aug 14, 2018
9cd5cbb
Completed reply by tweet feature
namangupta01 Jan 15, 2019
b817e2c
Merge branch 'master' into reply_by_tweet
namangupta01 Jan 15, 2019
a57097d
Minor change
namangupta01 Jan 15, 2019
6eeb4bc
Minor change
namangupta01 Jan 15, 2019
9dba8ae
Merge remote-tracking branch 'origin' into reply_by_tweet
namangupta01 Jan 21, 2019
093d503
Added gemfile.lock
namangupta01 Jan 21, 2019
1136184
Update comment.rb
jywarren Feb 9, 2019
9278621
Merge branch 'master' into reply_by_tweet
jywarren Feb 9, 2019
7af11b5
Added gemfile.lock
namangupta01 Feb 9, 2019
5ea493c
Minor changes
namangupta01 Mar 5, 2019
74894b6
Merge branch 'master' of https://github.com/publiclab/plots2 into rep…
namangupta01 Mar 5, 2019
3d07ec2
Minor changes
namangupta01 Mar 5, 2019
b4a7557
Minor changes
namangupta01 Mar 5, 2019
f4d965c
Minor changes
namangupta01 Mar 5, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ gem 'mailman', require: false
# To convert html to markdown
gem 'reverse_markdown'

gem 'twitter'

# run with `bundle install --without production` or `bundle install --without mysql` to exclude this
group :mysql, :production do
gem 'mysql2', '>= 0.4.4'
Expand Down
26 changes: 25 additions & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ GEM
ice_nine (~> 0.11.0)
thread_safe (~> 0.3, >= 0.3.1)
bindex (0.5.0)
buftok (0.2.0)
builder (3.2.3)
byebug (10.0.2)
chronic (0.10.2)
Expand Down Expand Up @@ -148,9 +149,16 @@ GEM
highline (2.0.0)
hoe (3.17.0)
rake (>= 0.8, < 13.0)
http (3.3.0)
addressable (~> 2.3)
http-cookie (~> 1.0)
http-form_data (~> 2.0)
http_parser.rb (~> 0.6.0)
http-cookie (1.0.3)
domain_name (~> 0.5)
http-form_data (2.1.1)
http_accept_language (2.1.1)
http_parser.rb (0.6.0)
i18n (1.5.3)
concurrent-ruby (~> 1.0)
i18n-js (3.2.1)
Expand Down Expand Up @@ -207,6 +215,8 @@ GEM
maildir (>= 0.5.0)
marcel (0.3.3)
mimemagic (~> 0.3.2)
memoizable (0.4.2)
thread_safe (~> 0.3, >= 0.3.1)
metaclass (0.0.4)
method_source (0.9.2)
mime-types (3.2.2)
Expand All @@ -230,6 +240,7 @@ GEM
mustermann-grape (1.0.0)
mustermann (~> 1.0.0)
mysql2 (0.5.2)
naught (1.1.0)
netrc (0.11.0)
nifty-generators (0.4.6)
nio4r (2.3.1)
Expand Down Expand Up @@ -405,6 +416,7 @@ GEM
rack (>= 1.5.0)
rack-protection (>= 1.5.0)
redis (>= 3.3.5, < 5)
simple_oauth (0.3.1)
simplecov (0.16.1)
docile (~> 1.1)
json (>= 1.8, < 3)
Expand Down Expand Up @@ -443,6 +455,17 @@ GEM
turbolinks (5.2.0)
turbolinks-source (~> 5.2)
turbolinks-source (5.2.0)
twitter (6.2.0)
addressable (~> 2.3)
buftok (~> 0.2.0)
equalizer (~> 0.0.11)
http (~> 3.0)
http-form_data (~> 2.0)
http_parser.rb (~> 0.6.0)
memoizable (~> 0.4.0)
multipart-post (~> 2.0)
naught (~> 1.0)
simple_oauth (~> 0.3.0)
tzinfo (1.2.5)
thread_safe (~> 0.1)
uglifier (4.1.20)
Expand Down Expand Up @@ -557,6 +580,7 @@ DEPENDENCIES
therubyracer
timecop
turbolinks (~> 5)
twitter
tzinfo-data
uglifier (>= 1.0.3)
unicode-emoji
Expand All @@ -566,7 +590,7 @@ DEPENDENCIES
will_paginate-bootstrap (>= 1.0.1)

RUBY VERSION
ruby 2.4.4p296
ruby 2.3.1p112
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops let's change this back to 2.4.4


BUNDLED WITH
1.17.1
94 changes: 94 additions & 0 deletions app/models/comment.rb
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,100 @@ def trimmed_content?
comment.include?(COMMENT_FILTER)
end

def self.receive_tweet
comments = Comment.where.not(tweet_id: nil)
if comments.any?
receive_tweet_using_since comments
else
receive_tweet_without_using_since
end
end

def self.receive_tweet_using_since(comments)
comment = comments.last
since_id = comment.tweet_id
tweets = Client.search(ENV["TWEET_SEARCH"], since_id: since_id).collect do |tweet|
tweet
end
tweets.each do |tweet|
puts tweet.text
end
tweets = tweets.reverse
check_and_add_tweets tweets
end

def self.receive_tweet_without_using_since
tweets = Client.search(ENV["TWEET_SEARCH"]).collect do |tweet|
tweet
end
tweets = tweets.reverse
check_and_add_tweets tweets
tweets.each do |tweet|
puts tweet.text
end
end

def self.check_and_add_tweets(tweets)
tweets.each do |tweet|
if tweet.reply?
in_reply_to_tweet_id = tweet.in_reply_to_tweet_id
if in_reply_to_tweet_id.class == Fixnum
parent_tweet = Client.status(in_reply_to_tweet_id, tweet_mode: "extended")
parent_tweet_full_text = parent_tweet.attrs[:text] || parent_tweet.attrs[:full_text]
urls = URI.extract(parent_tweet_full_text)
node = get_node_from_urls_present(urls)
unless node.nil?
twitter_user_name = tweet.user.screen_name
tweet_email = find_email(twitter_user_name)
users = User.where(email: tweet_email)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, can we look up the user in any other way?

if users.any?
user = users.first
replied_tweet_text = tweet.text
if tweet.truncated?
replied_tweet = Client.status(tweet.id, tweet_mode: "extended")
replied_tweet_text = replied_tweet.attrs[:text] || replied_tweet.attrs[:full_text]
end
comment = node.add_comment(uid: user.uid, body: replied_tweet_text, comment_via: 2, tweet_id: tweet.id)
comment.notify user
end
end
end
end
end
end

def self.get_node_from_urls_present(urls)
urls.each do |url|
if url.include? "https://"
if url.last == "."
url = url[0...url.length-1]
end
response = Net::HTTP.get_response(URI(url))
redirected_url = response['location']
if redirected_url != nil && redirected_url.include?(ENV["WEBSITE_HOST_PATTERN"])
node_id = redirected_url.split("/")[-1]
if !node_id.nil?
node = Node.where(nid: node_id.to_i)
if node.any?
return node.first
end
end
end
end

end
return nil
end

def self.find_email(twitter_user_name)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, this seems like it could start to push the performance of the system. Are we finding the email address of the twitter user here, and what do we use it for?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Meaning fetching all user tags each time -- can we query any other way?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could narrow with:

UserTag.where.not(data: nil)

But i bet we could do even better...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UserTag.where('value LIKE (?)', 'oauth:twitter%').where.not(data: nil).count

shows 145 records currently

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apart from this, I think we'll eventually have to figure a way to look up by twitter handle, and to do it more efficiently. The email may not even be the same one, you know?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@namangupta01 would you mind opening a new issue for the optimization, and another to try to see if we can look up twitter users by a different system? Thank you!! And congrats on getting this merged! 🎉

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like, maybe we need to store a new column in UserTag that is the user's twitter handle, maybe handle? It's not 100% ideal but it will make a difference for optimization as the system grows.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And, it'll bypass the issue of the emails not matching. My email is not the same between Twitter and PL.org, actually!

UserTag.all.each do |user_tag|
data = user_tag["data"]
if data["info"]["nickname"].to_s == twitter_user_name
return data["info"]["email"]
end
end
end

def parse_quoted_text
match = body.match(/(.+)(On .+<.+@.+> wrote:)(.+)/m)
if match.nil?
Expand Down
3 changes: 2 additions & 1 deletion app/models/node.rb
Original file line number Diff line number Diff line change
Expand Up @@ -598,7 +598,8 @@ def add_comment(params = {})
thread: thread,
timestamp: DateTime.now.to_i,
comment_via: comment_via_status,
message_id: params[:message_id])
message_id: params[:message_id],
tweet_id: params[:tweet_id])
c.save
c
end
Expand Down
7 changes: 7 additions & 0 deletions config/initializers/twitter_client.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
require 'twitter'
::Client = Twitter::REST::Client.new do |config|
config.consumer_key = ENV["TWITTER_CONSUMER_KEY"]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, so do we need new keys for these and where should I look for them? Does it need to be the @publiclab twitter or could it be another? Thanks, just checking!!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can be of any account...

config.consumer_secret = ENV["TWITTER_CONSUMER_SECRET"]
config.access_token = ENV["TWITTER_ACCESS_TOKEN"]
config.access_token_secret = ENV["TWITTER_ACCESS_TOKEN_SECRET"]
end
11 changes: 11 additions & 0 deletions config/schedule.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@
env :REDIS_URL, ENV['REDIS_URL']

# Cron Job log file

set :bundle_command, 'bundle exec'
job_type :runner, "cd :path && :bundle_command rails runner -e :environment ':task' :output"

ENV.each { |k, v| env(k, v) }

set :output, "#{Dir.pwd}/public/cron_log.log"

# To simply print date into the log file for checking if cron job is working properly
Expand All @@ -34,6 +40,11 @@
command "date -u" #This will print utc time every 1 min in log/cron_log.log file
end

every 1.minutes do
runner "Comment.receive_tweet"
end


every 1.day do
runner "DigestMailJob.perform_async(0)"
end
Expand Down
6 changes: 6 additions & 0 deletions containers/docker-compose-production.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ services:
- OAUTH_FACEBOOK_APP_KEY=${OAUTH_FACEBOOK_APP_KEY}
- OAUTH_FACEBOOK_APP_SECRET=${OAUTH_FACEBOOK_APP_SECRET}
- REDIS_URL=redis://redis:6379/0
- TWITTER_CONSUMER_KEY=${TWITTER_CONSUMER_KEY}
- TWITTER_CONSUMER_SECRET=${TWITTER_CONSUMER_SECRET}
- TWITTER_ACCESS_TOKEN=${TWITTER_ACCESS_TOKEN}
- TWITTER_ACCESS_TOKEN_SECRET=${TWITTER_ACCESS_TOKEN_SECRET}
- WEBSITE_HOST_PATTERN=${WEBSITE_HOST_PATTERN}
- TWEET_SEARCH=${TWEET_SEARCH}
volumes:
- ..:/app
- /etc/passwd:/etc/passwd:ro
Expand Down
6 changes: 6 additions & 0 deletions containers/docker-compose-stable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ services:
- OAUTH_FACEBOOK_APP_KEY=${OAUTH_FACEBOOK_APP_KEY}
- OAUTH_FACEBOOK_APP_SECRET=${OAUTH_FACEBOOK_APP_SECRET}
- REDIS_URL=redis://redis:6379/0
- TWITTER_CONSUMER_KEY=${TWITTER_CONSUMER_KEY}
- TWITTER_CONSUMER_SECRET=${TWITTER_CONSUMER_SECRET}
- TWITTER_ACCESS_TOKEN=${TWITTER_ACCESS_TOKEN}
- TWITTER_ACCESS_TOKEN_SECRET=${TWITTER_ACCESS_TOKEN_SECRET}
- WEBSITE_HOST_PATTERN=${WEBSITE_HOST_PATTERN}
- TWEET_SEARCH=${TWEET_SEARCH}
volumes:
- ..:/app
- /etc/passwd:/etc/passwd:ro
Expand Down
6 changes: 6 additions & 0 deletions containers/docker-compose-unstable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ services:
- OAUTH_FACEBOOK_APP_KEY=${OAUTH_FACEBOOK_APP_KEY}
- OAUTH_FACEBOOK_APP_SECRET=${OAUTH_FACEBOOK_APP_SECRET}
- REDIS_URL=redis://redis:6379/0
- TWITTER_CONSUMER_KEY=${TWITTER_CONSUMER_KEY}
- TWITTER_CONSUMER_SECRET=${TWITTER_CONSUMER_SECRET}
- TWITTER_ACCESS_TOKEN=${TWITTER_ACCESS_TOKEN}
- TWITTER_ACCESS_TOKEN_SECRET=${TWITTER_ACCESS_TOKEN_SECRET}
- WEBSITE_HOST_PATTERN=${WEBSITE_HOST_PATTERN}
- TWEET_SEARCH=${TWEET_SEARCH}
volumes:
- ..:/app
- /etc/passwd:/etc/passwd:ro
Expand Down
5 changes: 5 additions & 0 deletions db/migrate/20180809125000_add_tweet_column_to_comment.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class AddTweetColumnToComment < ActiveRecord::Migration[5.2]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you update the timestamp of this file?

def change
add_column :comments, :tweet_id, :string
end
end
4 changes: 4 additions & 0 deletions db/schema.rb.example
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@
#
# It's strongly recommended that you check this file into your version control system.


ActiveRecord::Schema.define(version: 2019_01_04_173959) do


create_table "answer_selections", force: true do |t|
t.integer "user_id"
t.integer "aid"
Expand Down Expand Up @@ -51,6 +53,8 @@ ActiveRecord::Schema.define(version: 2019_01_04_173959) do
t.integer "aid", default: 0, null: false
t.integer "comment_via", limit: 4, default: 0
t.string "message_id", limit: 255
t.string "tweet_id"

end

add_index "comments", ["comment"], name: "index_comments_on_comment", type: :fulltext if ActiveRecord::Base.connection.adapter_name == 'Mysql2'
Expand Down
29 changes: 29 additions & 0 deletions doc/reply_by_tweet.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
## Reply By Tweet

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add a couple sentences summarizing what this all does -- i.e. Once a minute, this system scans tweets "to:PublicLab" (or specified search, see below) for URLs matching our shortlink pattern, i.e. "https://publiclab.org/n/_____", where "_____" is a node nid. Each of the returned tweets is added as a comment to the node it responded to.

Can you also post links to https://apps.twitter.com/ and listing the environment variables that have to be set? Thanks!

- We are using [twitter gem](https://github.com/sferik/twitter "twitter gem") which implements twitter apis and gives functions to easily implement twitter apis.

### Steps:

- Cron job for polling for getting new tweets to `publiclab` is defined in [config/schedule.rb](https://github.com/publiclab/plots2/blob/master/config/schedule.rb "config/schedule.rb") using whenever gem which will call `receive_tweet` function of [models/comment.rb](https://github.com/publiclab/plots2/blob/master/app/models/comment.rb "models/comment.rb") in the interval of one minute.

- `receive_tweet` method of [models/comment.rb](https://github.com/publiclab/plots2/blob/master/app/models/comment.rb "models/comment.rb") will look for if there is any comment already present that contains tweet_id if it does it will call `receive_tweet_using_since` otherwise it will call `receive_tweet_without_using_since `.

- `receive_tweet_using_since` will search for the tweets to the `publiclab` which are tweeted after that tweet with the `tweet_id` present in the database.

- After that check if that tweet is the reply of some other tweet. If it does then find the parent tweet otherwise ignore this tweet and check next tweets untill tweets end.

- After finding parent tweet, search for the links present in the tweets in the form of `https://publiclab.org/n/_____` and find the the node_id present in `https://publiclab.org/n/node_id` if there is any node present in the database with this node_id then search for the user's email who did the tweet using the username and email data present in the hash form in the `data` column otherwise ignore this tweet.

- If that twitter username is present in the `user_tags` column then add the tweet otherwise ignore the current tweet.

- Same process is with when there are no comment present with tweet_id in the comment table where we search for all the replied tweets to the `publiclab`.

To use this feature we have to set some environment variables which includes Twitter Api keys and Searching Query: For getting Twitter Keys go to [Twitter app keys](https://apps.twitter.com/)
Environment varible used for twitter keys are : `TWITTER_CONSUMER_KEY`, `TWITTER_CONSUMER_SECRET`, `TWITTER_ACCESS_TOKEN` and `TWITTER_ACCESS_TOKEN_SECRET`.

Other Environment variable used are : `WEBSITE_HOST_PATTERN` which can be like `//publiclab.org/n/` and `TWEET_SEARCH` which is used to search for the tweets and can be like `to:publiclab`.

### Summery

Once a minute, this system scans tweets "to:PublicLab" (or specified search, see below) for URLs matching our shortlink pattern, i.e. "https://publiclab.org/n/_____", where "_____" is a node nid. Each of the returned tweets is added as a comment to the node it responded to.