Twitter bot [総集編]
こんにちわkazutoです。
今回は、以前、作成した、Twitter bot (天気予報bot)の総集編になります。一つ一つの機能(クラス)を振り返っていきましょう。
Wetaherクラス
Wetaherクラスでは、OpenWeatherMapというサイトのAPIを取得して、天気予報を取得しました。APIKEYの漏洩するのを防ぐために、外部から参照ができない、環境変数をvimというテキストエディタを使って、APIKEYを格納しましたね。
環境変数の値を取得をするためにconfirg.rbファイルを作成しました。
WEATHER_API_KEY = ENV['WEATHER_API_KEY']
WEATHER_URL = ENV['WEATHER_URL']
require 'date'
require 'csv'
class Wetaher
def initialize(citys)
@citys=citys
end
def select_city
@citys.each do |city|
puts "#{city['id']}:#{city['city']}"
end
puts "天気情報を取得したい都市を選択してください"
input = gets.to_i-1
@citys[input]
end
def get_weather_forecast(city)
response = open(WEATHER_URL + "?q=#{city['english']}&appid=#{WEATHER_API_KEY}&units=metric&lang=ja")
data=JSON.parse(response.read)
json_to_parse(data['list'])
datas= {list: data['list'],city_name:data['city']['name']}
end
def display_weather_forecast(weathers)
CSV.open("#{weathers[:city_name]}.csv","w") do |data|
data<<["id","時間","天気","気温"]
puts "地区:#{weathers[:city_name]}"
id = 0
weathers[:list].each do|weather|
id+=1
puts "------------------------------"
puts "時間#{weather['dt_txt']}"
puts "天気:#{weather['weather'][0]['description']}"
puts "気温:#{weather['main']['temp']}℃"
puts "------------------------------"
data<<[id,Time.parse(weather['dt_txt']).strftime('%Y年%m月%d日 %H時%M分' ),weather['weather'][0]['description'],weather['main']['temp']]
end
end
end
def json_to_parse(weathers)
weather=JSON.pretty_generate(weathers)
File.open("tenki.json", mode = "w"){|f|
f.write(weather)
}
end
def display_weather_text(weathers)
File.open("tenki.txt", mode = "w"){|f|
f.write(
weathers[:list].each do|weather|
puts "------------------------------"
puts "時間#{weather['dt_txt']}"
puts "天気:#{weather['weather'][0]['description']}"
puts "気温:#{weather['main']['temp']}℃"
puts "------------------------------"
end
)
}
end
def current_time_weather(wethers)
wethers[:list].map{|wether|wether['dt_txt']= Time.parse(wether['dt_txt'])}
current_weather = wethers[:list].select{|wether|wether['dt_txt']>=Time.now}.first
dt_txt = current_weather['dt_txt'].strftime('%Y年%m月%d日 %H時%M分%S秒' )
data = {
city:wethers[:city_name] ,
dt_txt: dt_txt,
description:current_weather['weather'][0]['description'],
temperature: current_weather['main']['temp'],
icon:current_weather['weather'][0]['icon']
}
end
end
def wetah
er(num)
citys=CSV.read("./citys.csv",headers: true)
wether = Wetaher.new(citys)
city=wether.select_city
datas=wether.get_weather_forecast(city)
case num
when 1
wether.current_time_weather(datas)
when 2
wether.display_weather_forecast(datas)
end
end
OpenWeatherMapから取得した情報を
- 表示
- データを整形
をしました。
Wetherクラスのゴールは
- 5日間の天気情報をターミナルで表示
- 天気予報を取得してTweetクラスに整形済みのデータを渡す
となります。
関連記事:「天気予報APIを用いて5日間の天気情報を取得しよう」
Tweetクラス
Tweetクラスでは、
- アカウント認証
- ツイート機能
- プロフィール機能
と上記の3つの機能を実装していきました。その他にも、子クラスの動作を再現するためにいくつかメソッドを作成しました。
class Tweet
attr_accessor :client
def initialize(current_time_weather)
@client = Twitter::REST::Client.new do |config|
config.consumer_key = CONSUMER_KEY
config.consumer_secret = CONSUMER_SECRET
config.access_token = ACCESS_TOKEN
config.access_token_secret = ACCESS_TOKEN_SECRET
end
@current_time_weather = current_time_weather
end
def updata
text = "#{@current_time_weather[:dt_txt]}\n#{@current_time_weather[:city]}の天気\n天気:#{@current_time_weather[:description]}\n気温:#{@current_time_weather[:temperature]}"
@client.update(text)
end
def account_setting
puts "アカウント名:#{@client.user.name}"
puts "プロフィール文\n#{@client.user.description }"
puts "[1]アカウント名を変更する"
puts "[2]プロフィール文を変更する"
puts "[3]閉じる"
num = gets.to_i
case num
when 1
name =gets.chomp
@client.update_profile({name:name} )
when 2
description = gets.chomp
@client.update_profile({description:description} )
when 3
return
end
end
def search_account_information(user_name,analsis=false)
puts @client.user(user_name).screen_name # アカウントID
puts @client.user(user_name).name # アカウント名
puts @client.user(user_name).description # プロフィール
puts "こちらのアカウントのでお間違い無いですか?"
puts "[1]YES"
puts "[2]NO"
num = gets.to_i
case num
when 1
if analsis
nil
else
search_target(user_name)
end
when 2
return
else
puts"無効な値です"
end
end
def timeline_style(user_name)
puts "CSVファイルにデータを書き込みますか❓"
puts "[1]YES"
puts "[2]NO"
num = gets.to_i
case num
when 1
timeline_write(user_name)
when 2
timeline(user_name)
else
puts "無効の値です"
end
end
def timeline(user_name,write=false)
total_repetition = 0
likes = 0
retweets = 0
data=[] if write
@client.user_timeline(user_name).each do |tweet|
puts "---------------------------------------------"
# puts tweet.created_at.getlocal
puts "更新日#{tweet.created_at.getlocal.strftime('%Y年%m月%d日 %H時%M分%S秒' )}"
puts tweet.full_text
puts "いいね数:#{tweet.favorite_count}"
puts "リツイート数:#{tweet.retweet_count}"
puts "---------------------------------------------"
likes += tweet.favorite_count
retweets +=tweet.retweet_count
total_repetition +=1
data << [ total_repetition,tweet.created_at.getlocal.strftime('%Y年%m月%d日 %H時%M分%S秒' ),tweet.text,tweet.favorite_count.to_i,tweet.retweet_count] if write
end
puts "ツイート数:#{ total_repetition}"
puts "トータルいいね数:#{likes}"
puts "トータルリツイート数:#{retweets}"
data if write
end
def timeline_write(user_name)
puts "ss"
CSV.open("#{user_name}timeline.csv","w") do |data|
data << ["id","投稿日","投稿内容","いいね数","リツイート数"]
tweets=timeline(user_name,true)
tweets.each do |tweet|
data<<tweet
end
end
end
def get_all_tweets(user_name,pages,type )
total_repetition = 0
total_like=0
total_retweeet = 0
case type[:mode]
when "WRITE"
CSV.open("#{user_name}.csv","w") do |data|
data << ["id","投稿日","投稿内容","いいね数","リツイート数"]
pages.each do |page|
@client.user_timeline(user_name,{ count: 200 ,page:page} ).each do |timeline|
tweet = @client.status(timeline.id)
puts"--"*100
puts tweet.created_at.getlocal.strftime('%Y年%m月%d日 %H時%M分%S秒' )
puts tweet.text
puts "いいね数#{tweet.favorite_count}"
puts "リツート#{tweet.retweet_count}"
puts "--"*100
total_repetition+=1
total_like+=tweet.favorite_count
total_retweeet+=tweet.retweet_count
data << [timeline.id,tweet.created_at.getlocal.strftime('%Y年%m月%d日 %H時%M分%S秒' ),tweet.text,tweet.favorite_count,tweet.retweet_count]
end
end
puts "ツイート数#{@client.user(user_name).tweets_count}"
# puts "繰り返すた回数#{total_repetition}"
puts "トータルいいね数#{total_like}"
puts "トータルリツイート#{total_retweeet}"
data << ["", "",@client.user(user_name).tweets_count,total_like,total_retweeet]
end
when "COMPARISON"
pages.each do |page|
@client.user_timeline(user_name,{ count: 200 ,page:page} ).each do |timeline|
tweet = @client.status(timeline.id)
type[:item] =="like" ? total_like+=tweet.favorite_count : total_retweeet+=tweet.retweet_count
end
end
case type[:item]
when "like"
total_like
when "retweet"
total_retweeet
else
puts "無効の値です"
end
else
puts "無効の値です"
end
end
def number_of_pages_judgment(user_name)
pages =[]
if @client.user(user_name).tweets_count < 200
count = 1
elsif @client.user(user_name).tweets_count % 200==0
count = @client.user(user_name).tweets_count / 200
else
remainder = (@client.user(user_name).tweets_count / 200)
count = remainder+1
remainder_num=@client.user(user_name).tweets_count-200*remainder
end
1.upto(count) do |num|
pages.push(num)
end
pages
end
end
子クラスの共通するメソッドも作成しているので、かなり記述量が多く、大変でしたが、tweetクラスを実装した事により
- Searchクラス
- Analysisクラス
の実装がシンプルになりました。親クラスに共通する処理をまとめる事で、子クラスに重複した処理を記述する必要がなくなります。
関連記事
Searchクラス
Searchクラスでは、TwitterのアカウントIDを入力し、検索するという、検索機能を実装しました。また、検索したユーザと他のユーザを比較したくなった場合に対応できる様に、Analysisクラス(分析機能)と連結しました。と言っても、ただクラス内で、Analysisクラスのインスタンスを生成をし、Analysisクラスのメソッドを使える様に、しただけです。
以下の項目を検索&比較する事ができます。
- フォロー数
- フォロワー数
- いいね数
- リツイート数
- ツイート数
require './analysis.rb'
require 'csv'
class Search<Tweet
def initialize()
super(client)
end
def search()
puts "ユーザー名を入力してください。"
input = gets.chomp
search_account_information(input)
end
def search_target(user_name)
puts "#{@client.user(user_name).name}さんどの情報が知りたいですか?"
puts "[1]タイムライン"
puts "[2]フォロー数"
puts "[3]フォロワー数"
puts "[4]タイムライン情報全件取得"
puts "[5]比較する"
num = gets.to_i
case num
when 1
timeline_style(user_name)
when 2
puts "フォロー数:#{@client.user(user_name).friends_count}"
when 3
puts "フォローワー数#{client.user(user_name).followers_count}"
when 4
pages=number_of_pages_judgment(user_name)
get_all_tweets(user_name,pages,{mode: "WRITE"} )
when 5
target_user = user_to_compare()
analysis = Analysis.new()
analysis.comparison_user_menu(user_name,true,target_user)
end
end
def user_to_compare()
puts "比較したいユーザのidを入力してください。"
target_user = gets.chomp
search_account_information(target_user,true)
target_user
end
end
def search
search= Search.new
search.search()
end
重い処理をTweetクラスにまとめていたので、記述量が少なく、シンプルな実装になりました。クラスの継承を用いると、親クラスのメソッドも使えるので、便利ですね。
タイムラインという項目を選択した場合は、CSVファイルの有無を選択します。
- CSVファイルを生成する場合
→CSVファイルを生成 - CSVファイルを生成しない場合
→ターミナルに表示
という2つの挙動を取ります。
比較する場合は、シンプルにターミナル に比較結果を表示するだけです。
関連記事:「Twitter bot 検索機能[拡張編]」
Analysisクラス
Analysisクラスは、
マイアカウントを分析する、分析機能を実装しました。ややSearchクラスと被る部分がありますが、マイアカウントと他のユーザとで、メニュー欄を分けたかったので、この様な仕様になりました。
分析機能でも、以下の情報を検索&比較する事ができます。
- タイムライン
- フォロー数
- フォロワー数
- いいね数
- リツイート数
- ツイート数
まあ、ほぼ同じ様な内容になります。
class Analysis<Tweet
def initialize()
super(client)
end
def analysis_menu
puts "[1]タイムライン"
puts "[2]フォロー数"
puts "[3]フォロワー数"
puts "[4]タイムライン全件取得"
puts "[5]比較する"
num = gets.to_i
case num
when 1
timeline_style(@client.user.screen_name)
when 2
puts @client.user.friends_count
when 3
puts @client.user.followers_count
when 4
pages=number_of_pages_judgment(@client.user.screen_name)
get_all_tweets(nil,pages,{mode: "WRITE"} )
when 5
comparison_user_decision()
else
puts "無効の値です"
end
end
def comparison_user_decision()
puts "比較したいユーザのidを入力してください。"
user_name = gets.chomp
search_account_information(user_name,true)
comparison_user_menu(user_name)
end
def comparison_user_menu(user_name,search=false,search_target=nil)
puts "#{@client.user(user_name).name}さん"
puts "どの項目を比較しますか"
puts "[1]フォロー数"
puts "[2]フォロワー数"
puts "[3]ツイート数"
puts "[4]いいね数"
puts "[5]リツイート数"
num = gets.to_i
if search
case num
when 1
items={search_target_user:@client.user(search_target).friends_count,comparison_user:@client.user(user_name).friends_count}
search_target_user ,comparison_user= make_users_information(user_name,"search",items,search_target)
display_result(search_target_user,comparison_user,"フォロー数")
when 2
items={search_target_user:@client.user(search_target).followers_count,comparison_user:@client.user(user_name).followers_count}
search_target_user,comparison_user = make_users_information(user_name,"search",items,search_target)
display_result(search_target_user,comparison_user,"フォロワー数")
when 3
items={search_target_user:@client.user(search_target).tweets_count,comparison_user:@client.user(user_name).tweets_count}
search_target_user,comparison_user = make_users_information(user_name,"search",items,search_target)
display_result(search_target_user,comparison_user,"ツイート数")
when 4
comparison_user_like = get_retweets_or_likes(user_name,"like")
search_target_user_like = get_retweets_or_likes(search_target,"like")
items={search_target_user:search_target_user_like,comparison_user:comparison_user_like}
search_target_user,comparison_user = make_users_information(user_name,"search",items,search_target)
display_result(search_target_user,comparison_user,"いいね数")
when 5
comparison_user_retweet = get_retweets_or_likes(user_name,"retweet")
search_target_user_retweet = get_retweets_or_likes(search_target,"retweet")
items={search_target_user: search_target_user_retweet,comparison_user:comparison_user_retweet}
search_target_user,comparison_user = make_users_information(user_name,"search",items,search_target)
display_result(search_target_user,comparison_user,"リツイート")
else
puts "無効の値です"
end
else
case num
when 1
items={myself:@client.user.friends_count,comparison_user:@client.user(user_name).friends_count}
myself ,comparison_user= make_users_information(user_name,"analysis",items)
display_result(myself,comparison_user,"フォロー数")
when 2
items={myself:@client.user.followers_count,comparison_user:@client.user(user_name).followers_count}
myself,comparison_user = make_users_information(user_name,"analysis",items)
display_result(myself,comparison_user,"フォロワー数")
when 3
items={myself:@client.user.tweets_count,comparison_user:@client.user(user_name).tweets_count}
myself,comparison_user = make_users_information(user_name,"analysis",items)
display_result(myself,comparison_user,"ツイート数")
when 4
comparison_user_like = get_retweets_or_likes(user_name,"like")
myself_like = get_retweets_or_likes(nil,"like")
items={myself:myself_like,comparison_user:comparison_user_like}
myself,comparison_user = make_users_information(user_name,"analysis",items)
display_result(myself,comparison_user,"いいね数")
when 5
comparison_user_retweet = get_retweets_or_likes(user_name,"retweet")
myself_retweet = get_retweets_or_likes(nil,"retweet")
items={myself:myself_retweet,comparison_user:comparison_user_retweet}
myself,comparison_user = make_users_information(user_name,"analysis",items)
display_result(myself,comparison_user,"リツイート")
else
puts "無効の値です"
end
end
end
def display_result(user,comparison_user,item_name)
puts "-------------------------------"
puts "#{item_name}"
puts "#{user[:name]}:#{ user[:result]}"
puts "#{comparison_user[:name]}:#{ comparison_user[:result]}"
puts "-------------------------------"
result_of_comparison(user,comparison_user)
end
def make_users_information(user_name,type,items,search_target=nil)
case type
when "analysis"
myself=
{
name:@client.user.name,
result:items[:myself]
}
comparison_user=
{
name:@client.user(user_name).name,
result:items[:comparison_user]
}
return myself , comparison_user
when "search"
searchtarget=
{
name:@client.user(search_target).name,
result:items[:search_target_user]
}
comparison_user=
{
name:@client.user(user_name).name,
result: items[:comparison_user]
}
return searchtarget,comparison_user
else
puts "無効の値です"
end
end
def result_of_comparison(user,comparison_user)
puts user[:result] > comparison_user[:result] ? "#{ user[:name]}の方が#{user[:result]-comparison_user[:result]}多いです" : "#{ comparison_user[:name]}の方が#{comparison_user[:result]-user[:result]}多いです"
end
def get_retweets_or_likes(user_name,get)
pages=number_of_pages_judgment(user_name)
get_all_tweets(user_name,pages,{item: get,mode: 'COMPARISON'})
end
end
def analysis
analysis= Analysis.new
analysis.analysis_menu
end
検索機能と連結している処理の部分が重いですが、実装している内容は、シンプルですので、難しく考える必要はないです。
- データを作成
- データを渡す
- データーを加工
- データーを元に比較
- ターミナルに結果を表示
を繰り返しているだけです。
タイムラインという項目を選択した場合は、CSVファイルの有無を選択します。
- CSVファイルを生成する場合
→CSVファイルを生成 - CSVファイルを生成しない場合
→ターミナルに表示
という2つの挙動を取ります。
比較する場合は、シンプルにターミナル に比較結果を表示するだけです。
まとめ:Twitter bot [総集編]
今回は、Twitter bot シリーズのまとめ記事を書いてみました。まとめ記事を書いた経緯は、アウトプット量を増やすためです。
人は忘れる生き物です。
- 英単語を100個、暗記をした
- 難しい方程式の公式を暗記した
と一時的に覚えられたとしても、継続的にアウトプットを繰り返さないと、いつか忘れてしまいます。なので僕は、「忘れる事を前提」としてプログラミング学習を行っております。忘れるという問題点を解決するのは、物理的に不可能です。したがって、覚えるという行為(インプット)よりも、思い出す(アウトプット)という行為を効率的に行える
- 仕組み
- 構造
を作るのに時間を割いた方が合理的です。要するに、いかに覚えるかではなく、いかに速く思い出せるかの方が優先度が高いという事です。
なので僕は、kazugrammingというプログラミング特化サイトを開設をし、学んだ事をWEBテキストに定期的にまとめ、いつでもググれる様な仕組みを作りました。手書きノートよりもWEBテキストの方が機動力が高く、パソコンの恩恵を感じるので、ぜひ活用してみてください。
以上、kazutoでした。
関連記事
- 天気予報APIを用いて5日間の天気情報を取得しよう
- TwitterのAPIを使った天気予報botを作成してみよう
- Twitter bot プロフィール設定[拡張編]
- Twitter bot 検索機能[拡張編]
- Twitter bot 分析機能[拡張編]