【Rails】Twitter APIとLINE APIでジャニーズの私物を特定して、すぐに購入できるサービスを作ってみた!!! PF [Twitter API部分]

#はじめに
PFで**ジャニーズの私物を特定して、すぐに購入できるサービス**を作成したのでまとめます。
あまりにも長くなりそうなので、まず`Twitter API`を使用した部分についてまとめます。

#サービスの概要

ジャニーズファンは推しと同じものを日常生活やライブで身につけたいという心理があります。
twitte上にファンの人が私物を特定してツイートをするので、そのツイートをキャッチして、LINEで通知・購入できるサービスになっています。

#なぜこのサービスを作成したか
私物の特定のツイートがされるとすぐファンが購入し、売り切れてしまうことがよくあります。
私自身、私物特定のツイートを発見し、サイトを検索してももう売り切れてしまっていたということが何回もあります。
他のファンよりも早く購入できるサービスがあればいいなと思い、このサービスを作成しました。

##完成したサービス
Webサイトは以下になります↓

https://www.findgoodsofstan.jp/

LINEのQRコードになります。
ぜひ動かしてみてください!!
<img width="150" alt="a163e5fa2b2098ef30926fb607faa8ff.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2133440/a14102d3-51c9-2d22-8d96-89396b192b6d.png">

サービスのイメージがしづらいと思いますがこんな流れになります
1.Twitterで私服特定ツイートがされます。
<img width="300" alt="S__37232668.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2133440/53b8694a-7863-28c2-f542-724b2666d617.jpeg">

2.すぐLINEにこのような通知が届きます。
<img width="300" alt="S__37232670.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2133440/655e3495-461c-9893-14ef-fbb72d2fd061.jpeg">

3.`商品検索ボタン`から検索可能です。
<img width="300" alt="S__37232676.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2133440/0cac9a6b-b08e-177b-f1ba-8fc533464d59.jpeg">

4.`To tweetボタン`からソース元に遷移可能です。
<img width="300" alt="S__37232678aaa.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2133440/eb20d86e-88f7-1fb5-41fe-7d4415d933ab.jpeg">

**LINE友達登録後できること**
1.最新の情報を確認できます。
リッチメニュー右上の`最新の情報ボタン`を押す→情報を確認したい`メンバーを選択`する→`最新の情報`が表示される
<img width="300" alt="S__37232679ss.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2133440/8356275b-8543-573c-7977-8c0be57320cc.jpeg">

<img width="300" alt="S__37232680ss.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2133440/5fd3ae36-11f3-2ab2-dee2-a141e96f05eb.jpeg">

2.情報を受け取るメンバーを選択できます。
リッチメニュー左上の`メンバー設定を変更する`を選択する→設定した`いメンバーを選択`する→通知を`受け取る`か、`受け取らない`かを選択する

<img width="300" alt="S__37232682b.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2133440/4ff385cc-e26b-8c41-82b2-d8193d26bca5.jpeg">

<img width="300" alt="S__37232683b.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2133440/bccb87c7-e4d8-4915-8ead-9572dff76465.jpeg">

もちろん、現在の通知設定についてもリッチメニュー左下の`メンバー設定を確認する` から確認可能です。

<img width="300" alt="S__37232681b.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2133440/993e74a7-e425-3671-d2c8-32306ad510af.jpeg">


##使用した機能
Twitter API
LINE Messaging API
dotenv-rails'
config gem
ransack gem
sorcery gem
kaminari gem
enum_help gem
rails-i18n gem
AWS
heroku

##ER図
![3917056f5181a1ce73694684e344b358.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2133440/c94b651d-f7d3-2072-fdc5-fd44e049b93d.png)

https://drive.google.com/file/d/10gO9reC8SdPK9OE3ZZntfC9CflYIkJHv/view?usp=sharing


##画面遷移図

![d657ee3591bde2b85796fbee48567573.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2133440/26fd44d1-3ee1-0043-31c7-63dc3a8e16b1.png)

https://www.figma.com/file/pFrYU4hSwDQFn4da2XSTyh/Untitled?node-id=32%3A2


##Twiiter API 部分について

**Twiiter API**を使用して、特定のアカウントが私物に関するツイートをした際にツイート内容や画像等を取得します。

#### 1.gemを取得します。
```ruby:Gemfile
# Twitter API用
gem 'twitter'
# .env 管理用
gem 'dotenv-rails'
```

#####❶`twitter gem`を取得

`gem 'twitter'`

Twitter APIを使用するので取得します。

https://github.com/sferik/twitter

#####❷`dotenv-rails gem`を取得します。

`gem 'dotenv-rails'`

環境変数(公開させたくない情報)を管理することができるgemです。
自身のアプリケーションの直下に.envファイルを作成することで、パスワードなどネット上に公開させたくない情報を扱い、自動で読み込むことが可能です。

これを使用しない場合は、自分でシェルを使用して設定しなければならないです。

https://mmm-st.hatenablog.com/entry/2021/11/28/143639

https://qiita.com/4geru/items/33c123f853f72a758866

https://github.com/bkeepers/dotenv





設定したアクセストークン等を`.env`ファイルに設定しておく。

```ruby:.env
#Twitter
CONSUMER_KEY=""
CONSUMER_SECRET=""
ACCESS_TOKEN=""
ACCESS_SECRET=""
```

git管理しているので、`.gitignore`に`.env`を追加しておく。
`gitignore`はGitで使われる特殊なファイル名で、このファイルに書かれたファイルは上から順に処理されて、**Gitのトラッキングの対象外**になります。


```ruby:.gitignore
.env
```

https://qiita.com/rarrrrsQ/items/1b1277499819659e5601



####2.メソッドを定義

controllerのfat化を防ぐためにメソッドは`article.rb`に作成しました。
まずは、全体を載せます。

```ruby:article.rb

# Twitterclient認証
  def twitter_client
    Twitter::REST::Client.new do |config|
      config.consumer_key        = ENV["CONSUMER_KEY"]
      config.consumer_secret     = ENV["CONSUMER_SECRET"]
      config.access_token        = ENV["ACCESS_TOKEN"]
      config.access_token_secret = ENV["ACCESS_SECRET"]
    end
  end

  # 指定したidのアカウントのツイート検索
  def search(id)
    @tweets = twitter_client.user_timeline(user_id: id, count: 1, exclude_replies: false, include_rts: false, contributor_details: false, result_type: "recent", locale: "ja", tweet_mode: "extended")
  end

  # 検索したツイートが私物関連のものであるか判断
  def set_article(tag)
    @for_article_tweets = []
    @tweets.each do |tweet|
      @for_article_tweets << tweet if tag.any?{|t| tweet.text.include?(t)}
    end
  end

  # 関係するメンバーを判断
  def check_member(tweet_content)
    member_ids = []
      a = ["#高地優吾"]
      b = ["#京本大我"]
      c = ["#田中樹"]
      
      member_ids << 1 if a.any?{ |a| tweet_content.include?(a) }
      member_ids << 2 if b.any?{ |b| tweet_content.include?(b) }
      member_ids << 3 if c.any?{ |c| tweet_content.include?(c) }
      members << Member.find(member_ids)
  end

  
  # ツイートに含まれる画像を保存
  def set_images(medias)
    image_urls = medias.map{ |h| h.media_url_https }
    # LINE送信用の画像url作成
    imgae_url_for_line = image_urls.first
    # imgae_url_for_line = image_urls.last
    @imgae_url_for_line_small = "#{imgae_url_for_line}:small"
    # 画像をActiveStorageに保存
    image_urls.each_with_index do |image_url, i|
      image_url_small = "#{image_url}:small"
      io = open(image_url_small)
      self.images.attach(io: io, filename: "image_#{i}")
    end
  end

    #  GoodsFindというアカウントのツイート検索
    def make_GoodsFind_article
      search("100000000000")
      tag = ["#findgoodsofstan"]
      set_article(tag)
      @for_article_tweets.each do |tweet|
        tweet_content = tweet.text.gsub(/[\r\n]/,"")
        self.tweet_url = tweet.url
        self.price = tweet_content.scan(/¥.+?-/).join(',')
        self.brand = tweet_content.scan(/(?<=\【).+?(?=\】)/).join(',')
        self.item = tweet_content.scan(/(?<=\】).+?(?=\¥)/).join(',')
        check_member(tweet_content)
        set_images(tweet.media)
        if save
          send_line(member_ids, tweet_url, @imgae_url_for_line_small)
        end
      end
    end

```


####分解してみていきます。

#####❶`Twitter API`の認証をします。

```ruby:article.rb

# Twitterclient認証
  def twitter_client
    Twitter::REST::Client.new do |config|
      config.consumer_key        = ENV["CONSUMER_KEY"]
      config.consumer_secret     = ENV["CONSUMER_SECRET"]
      config.access_token        = ENV["ACCESS_TOKEN"]
      config.access_token_secret = ENV["ACCESS_SECRET"]
    end
  end
```

ここの部分で、認証([gemのgithub](https://github.com/sferik/twitter#configuration)を参考)させます。

あらかじめ1-❷で`.env`に設定したアクセストークン等を
`consumer key (API key)` `consumer secret (API secret)` `access token` `access secret`に定義します。

https://qiita.com/nyappy15th/items/80682c2e9db82c084336

https://blog.palettecms.jp/article/20103

#####❷`user_timeline`メソッドを使用して、指定した`id`のアカウントのツイートを取得するインスタンスメソッドを作成します。

```ruby:article.rb
# 指定したidのアカウントのツイート検索
  def search(id)
    @tweets = twitter_client.user_timeline(user_id: id, count: 1, exclude_replies: false, include_rts: false, contributor_details: false, locale: "ja", tweet_mode: "extended")
  end
```

`twitter_client` → 2-❶で認証した際に設定しました。

`user_timeline` screen_nameパラメータや user_idパラメータで指定したユーザーが投稿した 最新のツイート集を取得します。

`user_id: id` → 検索結果を取得するユーザーのID

`count: 1` → 取得するツイート数を設定します。

`exclude_replies: false`→ リプライを含まないように設定します。

`include_rts: false` → falseを設定すると、タイムラインはリツイートを表示しません。

`contributor_details: false` → ライター機能(複数人による投稿)を利用したツイートの場合、contributorsプロパティにユーザー情報の詳細を含めた内容にします。

`locale: "ja"` → 日本語のツイートを取得します。

`tweet_mode: "extended"` → 140文字以上のツイートを取得します。(ツイートの前内容を取得したかったので!)



http://westplain.sakuraweb.com/translate/twitter/Documentation/REST-APIs/Public-API/GET-statuses-user_timeline.cgi

https://qiita.com/hitsumabushi845/items/f7fd87106381fc65fc86

#####❸ ❷で取得したツイートが私物関連のものであるのか判断するメソッドを作成します。tagにはツイートに必ず含まれている`#`から始まるタグを配列にして引数に渡します。


```ruby:article.rb
# 検索したツイートが私物関連のものであるか判断
  def set_article(tag)
    @for_article_tweets = []
    @tweets.each do |tweet|
      @for_article_tweets << tweet if tag.any?{|t| tweet.text.include?(t)}
    end
  end
```
#####ここからまた分解してみていきます

(1)私物関連のツイートであった場合、配列の形で次のメソッドで使用したいので空の配列を用意しておく。

```ruby
@for_article_tweets = []
```
(2)`any?メソッド`と`eachメソッド`と`三項演算子`を使用して判断していきます。
2-❷で`@tweets`の中に取得したツイートを代入しているので使用していきます。


```ruby
@tweets.each do |tweet|
  @for_article_tweets << tweet if tag.any?{|t| tweet.text.include?(t)}
end
```

`any?`メソッド → 
>すべての要素が偽である場合に false を返します。真である要素があれば、ただちに true を返します。

https://docs.ruby-lang.org/ja/latest/method/Enumerable/i/any=3f.html

このような形で、1つでも`真`である要素があれば`true`を返します。

```ruby:ターミナル
irb(main):095:0>  [100, 200, 300].any? { |v| v > 200 }
=> true
irb(main):096:0> ["aaa","bbb","ccc"].any? {|v| v.include?("a")}
=> true
```

これに`三項演算子`を使用すると...

まず上のターミナルで試したものに`三項演算子`を加えてみます。
このような形で、`true`であれば**if**の前の処理を行ってくれます。

```ruby:ターミナル
irb(main):107:0> puts "ok" if [100, 200, 300].any? { |v| v > 200 }
ok
=> nil
irb(main):108:0> puts "YES!" if ["aaa","bbb","ccc"].any? {|v| v.include?("a")}
YES!
=> nil
```

配列部分をインスタンスに代入して使用してみます。
`tweet_contents`に`tag`が含まれているかどうか...

```ruby:ターミナル
#インスタンスに入れてみる
irb(main):125:0> tweet_contents = ["aaa","bbb","ccc"]
=> ["aaa", "bbb", "ccc"]
irb(main):126:0> tag = ["ccc"]
=> ["ccc"]
#any?メソッドが正しく使用できるか試してみる
irb(main):127:0> tag.any?{ |t| tweet_contents.include?(t)}
=> true
#次は三項演算子も使用してみる
irb(main):128:0> puts "ok" if tag.any?{ |t| tweet_contents.include?(t)}
ok
=> nil
```

こんな感じで使用できました。

ですがこれだと、「取得したツイートの配列の中に指定したtagが含まれている。」と言うことしかわからず、「取得したツイートの配列の中のどれに含まれているのかわからないです。

なので、`eachメソッド`を使用して`@tweets`の要素を一つずつ渡し、判断していきます。

```ruby:ターミナル
tweets = ["aaa","bbb","ccc"]
tag = ["ccc"]
results = []

#tweetsに入っている要素を順番に取得し、変数tweetに格納しブロック内の処理を実行する。変数tweetにtag内の文字が含まれていれば、resultsの中に追加している。
tweets.each do |tweet|
 results << tweet if tag.any?{ |t| tweet.include?(t)}
end

puts results
=>ccc
```

最終的に私が作成したメソッドだと...
[1]2-❷で定義した`@tweets`(取得したツイート)を`eachメソッド`を使用し、`@tweets`に入っている要素を順番に取得し、`変数(tweet)`に格納しブロック内の処理を実行する。
[2]ブロック内では、要素を格納した`変数(tweet)`に対し`引数(tag)`が含まれているかどうかを`any?`メソッドを使用して判断する。
[3]`true(含まれている)`であれば、`@for_article_tweets`に追加していく。

```ruby:article.rb
def set_article(tag)
    @for_article_tweets = []
    @tweets.each do |tweet|
      @for_article_tweets << tweet if tag.any?{|t| tweet.text.include?(t)}
    end
  end
```
このような形になってます。

#####❸ツイートに関係しているメンバーを判断していきます。名前は変えてます。

```ruby:article.rb
# 関係するメンバーを判断
  def check_member(tweet_content)
  #引数に、取得したツイートの内容を渡します。
    member_ids = []
      a = ["#高地優吾"]
      b = ["#京本大我"]
      c = ["#田中樹"]
      
      member_ids << 1 if a.any?{ |a| tweet_content.include?(a) }
      member_ids << 2 if b.any?{ |b| tweet_content.include?(b) }
      member_ids << 3 if c.any?{ |c| tweet_content.include?(c) }
      #取得したツイートの内容に、メンバーの名前のタグが含まれていれば、seedファイルで初期に登録したメンバーのid(1,2,3)がそれぞれ追加されるようになっている。
      (self.は略)members << Member.find(member_ids)
      #メンバーのidを格納したmember_idsを使用してMemberを取得し、membersに追加する。
  end
```

`any?`メソッド → 2❸(2)でまとめてあります。

`<<`メソッド → 
関連付けをしたので、追加されたメソッド

>collection<<メソッドは、1つ以上のオブジェクトをコレクションに追加します。このとき、追加されるオブジェクトの外部キーは、呼び出し側モデルの主キーに設定されます。

https://railsguides.jp/association_basics.html#has-many%E3%81%A7%E8%BF%BD%E5%8A%A0%E3%81%95%E3%82%8C%E3%82%8B%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89-collection-object


#####❹ ツイートに含まれる画像を保存します

```ruby:article.rb

# ツイートに含まれる画像を保存
  def set_images(medias)
    image_urls = medias.map{ |h| h.media_url_https }
    # LINE送信用の画像url作成
    imgae_url_for_line = image_urls.first
    @imgae_url_for_line_small = "#{imgae_url_for_line}:small"
    # 画像をActiveStorageに保存
    image_urls.each_with_index do |image_url, i|
      image_url_small = "#{image_url}:small"
      io = open(image_url_small)
      self.images.attach(io: io, filename: "image_#{i}")
    end
  end
```
ここが割と詰んだ部分です。
#####また分解してみていきます。
(0)前提として...
例えば2-❶のように認証した後、

```ruby:ターミナル
@tweets = twitter_client.user_timeline(user_id: "(ユーザーID)", count: 1, 〜2-❷と同様)
=> [#<Twitter::Tweet id=1461747303736242176>]

#@tweetは配列(array)なので、`first`で一つ抜き出して試してみます。
irb(main):019:0> @tweet = @tweets.first
=> #<Twitter::Tweet id=1461747303736242176>

#.textを使用すると、ツイートの内容を取得できます。
irb(main):021:0> puts @tweet.text
2021/11/20 YouTube等
【adidas】
ジャージジャケット　ポルトガル代表
¥不明
※画像使わせて頂きました
 https://t.co/xvYNvE45sX
=> nil
```
ここからが本題です。`media`を使用すると何ができるかをまとめます。`@tweet`に関しては↑のターミナルと続いているものとします。

取得したツイート`@tweet`に対して`@tweet.media`と使用すると`Twitter::Media::Photo`オブジェクトが取得できます。(`@tweet`に含まれている画像についての情報が取得できます。)

`Twitter::Media::Photo`オブジェクト↓のようにこれも配列(array)です。

```ruby:ターミナル
irb(main):023:0> @tweet.media
=> [#<Twitter::Media::Photo id=1461747300020420608>, #<Twitter::Media::Photo id=1461747300016222208>]
```

`Twitter::Media::Photo`オブジェクトのメソッドは`attrs`, `id`, `media_url`, `media_url_https`, `sizes`などがあります。

`attrs`メソッド → `Twitter::Media::Photo`の中身を見れます。ハッシュとして扱えます。

```ruby:ターミナル
#配列なのでわかりやすくするためにfirstで最初の画像を取得します。
irb(main):024:0> @tweet.media.first
=> #<Twitter::Media::Photo id=1461747300020420608>

irb(main):025:0> @tweet.media.first.attrs
=> {:id=>1461747300020420608, 
:id_str=>"1461747300020420608", 
:indices=>[141, 164], 
:media_url=>"http://pbs.twimg.com/media/FEkrbLkagAAFXVH.jpg", 
:media_url_https=>"https://pbs.twimg.com/media/FEkrbLkagAAFXVH.jpg", 
:url=>"https://t.co/xvYNvE45sX", 
:display_url=>"pic.twitter.com/xvYNvE45sX", 
:expanded_url=>"https://twitter.com/GoodsFind/status/1461747303736242176/photo/1",
:type=>"photo",
:sizes=>{
  :thumb=>{:w=>150, :h=>150, :resize=>"crop"},
  :medium=>{:w=>784, :h=>732, :resize=>"fit"},
  :large=>{:w=>784, :h=>732, :resize=>"fit"},
  :small=>{:w=>680, :h=>635, :resize=>"fit"}
 }
}
```
`id`, `media_url`, `media_url_https`, `sizes`を使用してみます。

```ruby:ターミナル
#idを取得できる
irb(main):026:0> @tweet.media.first.id
=> 1461747300020420608

#画像のurlを取得できる(httpから連なる)
irb(main):027:0> @tweet.media.first.media_url
=> #<Addressable::URI:0x3fce24c8e664 URI:http://pbs.twimg.com/media/FEkrbLkagAAFXVH.jpg>

#画像のurlを取得できる(httpsから連なる)
irb(main):028:0> @tweet.media.first.media_url_https
=> #<Addressable::URI:0x3fce1ffa571c URI:https://pbs.twimg.com/media/FEkrbLkagAAFXVH.jpg>

#画像のサイズを取得できる
irb(main):030:0> @tweet.media.first.sizes
=> {:thumb=>#<Twitter::Size h=150 w=150>, :medium=>#<Twitter::Size h=732 w=784>, :large=>#<Twitter::Size h=732 w=784>, :small=>#<Twitter::Size h=635 w=680>}
```
`media_url`の返値である`Addressable::URI`オブジェクトはURIオブジェクトみたいなもので、`to_s`でURL文字列になります。

https://www.rubydoc.info/gems/addressable/Addressable/URI

前提が長くなりましたが
このインスタンスメソッドの引数の(medias)には、

```ruby
def set_images(medias)
```

使用するときに、このように取得したツイートに対して`.media`を使用して`Twitter::Media::Photo`オブジェクトを渡します。

```ruby
set_images(tweet.media)
```

次に行きます。

(1)`media_url_https`メソッド(画像のurlを取得できる(httpsから連なる))

(2)の最後に書いたように、引数の`medias`ここには`Twitter::Media::Photo`オブジェクトが渡されています。

[`medias` = `Twitter::Media::Photo`オブジェクト]に対して`map`メソッドを使用し各要素に対してブロック`{}`内の処理を実行させ、結果を全て含む配列を返します。

もっと砕いた言葉にすると
→`map`メソッドを使用し、`medias`に含まれている`Twitter::Media::Photo`オブジェクト一つ一つに対して、ブロック内の`media_url_https`メソッドを使用し、httpsから連なる画像のURLの配列を返し、`image_urls`に代入します。

```ruby
image_urls = medias.map{ |h| h.media_url_https }
```

`map`メソッド

>各要素に対してブロックを評価した結果を全て含む配列を返します。

https://docs.ruby-lang.org/ja/latest/method/Enumerable/i/collect.html

(2)LINE Messaging APIで使用する画像を準備

LINE Messaging APIで使用する画像を`imgae_url_for_line`に代入します。
今回は最初の画像を使用します。

```ruby
imgae_url_for_line = image_urls.first
```

(3)LINE Messaging APIで使用する画像のサイズを指定します。

今回は一番小さいサイズを指定したいので、`:small`を使用します。
ここは単純で、`media_url_https`メソッドを使用し、httpsから連なる画像のURLの最後に`:small`をつければサイズ調整ができます。

ここでは、(2)で定義した`imgae_url_for_line`の最後に`:small`を結合させています。

```ruby
@imgae_url_for_line_small = "#{imgae_url_for_line}:small"
```
https://code-macchiato.com/tips/twitter-api-tweet-media-thumb

2-❹(0)の再記載になりますが、ここに`:small`が含まれています。

```ruby:ターミナル
irb(main):025:0> @tweet.media.first.attrs
=> {:id=>1461747300020420608, 
:id_str=>"1461747300020420608", 
:indices=>[141, 164], 
:media_url=>"http://pbs.twimg.com/media/FEkrbLkagAAFXVH.jpg", 
:media_url_https=>"https://pbs.twimg.com/media/FEkrbLkagAAFXVH.jpg", 
:url=>"https://t.co/xvYNvE45sX", 
:display_url=>"pic.twitter.com/xvYNvE45sX", 
:expanded_url=>"https://twitter.com/GoodsFind/status/1461747303736242176/photo/1",
:type=>"photo",
#ここの
:sizes=>{
  :thumb=>{:w=>150, :h=>150, :resize=>"crop"},
  :medium=>{:w=>784, :h=>732, :resize=>"fit"},
  :large=>{:w=>784, :h=>732, :resize=>"fit"},
#ここです!!!
  :small=>{:w=>680, :h=>635, :resize=>"fit"}
 }
}
```


(4)画像をActiveStorageに保存

まず説明を書いて、その後に詳しくまとめます。

```ruby
image_urls.each_with_index do |image_url, i|
#image_urls → httpsから連なる画像のURLの配列(2-❹(1)(2)で設定済み)
  image_url_small = "#{image_url}:small"
  #サイズをsmallに指定
  io = open(image_url_small)
  #openメソッドを使用し、取得しているURLをファイルオブジェクトとして返す。
  self.images.attach(io: io, filename: "image_#{i}")
  #attachメソッドを使用してimagesに取得した画像を追加する
end
```
(4)-1

```ruby
image_urls.each_with_index do |image_url, i|
#image_urls → httpsから連なる画像のURLの配列(2-❹(1)(2)で設定済み)
  image_url_small = "#{image_url}:small"
  #サイズをsmallに指定
```

`each_with_index`メソッド → eachループで回しつつ、それぞれのデータに番号を振りたいのでこのメソッドを使用しました。(ファイル名が同じ名前だと保存されない。)

```ruby:ターミナル
["いちご", "なし" , "ブドウ"].each_with_index do |item, i|
  puts "#{i}番目のフルーツは、#{item}です"
end

=>0番目のフルーツは、いちごです
=>1番目のフルーツは、なしです
=>2番目のフルーツは、ブドウです

```

https://docs.ruby-lang.org/ja/latest/method/Enumerable/i/each_with_index.html

https://qiita.com/tsuchinoko_run/items/5cef7dd9d8baf48ffde7

(4)-2
問題はこの部分ですね...ここは2行をまとめて書いた方がわかりやすいと思うのでまとめます。

```ruby
io = open(image_url_small)
#openメソッドを使用し、取得しているURLをファイルオブジェクトとして返す。
self.images.attach(io: io, filename: "image_#{i}")
#attachメソッドを使用してimagesに取得した画像を追加する
```

まず、`self.images.attach(io: io, filename: "image_#{i}")`は以下を参照に作成しています。

>HTTPリクエスト経由では配信されないファイルをアタッチする必要が生じる場合があります。たとえば、ディスク上で生成したファイルやユーザーが送信したURLからダウンロードしたファイルをアタッチしたい場合や、モデルのテストでfixtureファイルをアタッチしたい場合などが考えられます。これを行うには、オープンIOオブジェクトとファイル名を1つ以上含むハッシュを渡します。
>
```ruby
@message.image.attach(io: File.open('/path/to/file'), filename: 'file.pdf')
```


https://railsguides.jp/active_storage_overview.html#file-io-objects%E3%82%92%E3%82%A2%E3%82%BF%E3%83%83%E3%83%81%E3%81%99%E3%82%8B

以上から、`io = open(image_url_small)`では、`self.images.attach(io: io, filename: "image_#{i}")`の`io: io`の部分に渡すものを作成します。

`open-uriライブラリ`を使用します。
http/ftp上のURLを普通のファイルのように開き、開いたファイルオブジェクトは StringIO もしくは Tempfile になります。

https://docs.ruby-lang.org/ja/latest/library/open=2duri.html


```ruby
io = open(image_url_small)
```
`image_url_small`に入った`Addressable::URI`オブジェクトに対して`open`メソッドを実行します。

`open`メソッド →
>name が http:// や https://、ftp:// で始まっている文字列なら URI のリソースを取得した上で StringIO オブジェクトまたは Tempfile オブジェクトとして返します。返されるオブジェクトは OpenURI::Meta モジュールで extend されています。

https://docs.ruby-lang.org/ja/latest/method/URI/s/open.html

ターミナルで中身を確認してみると(サイズはsmallにしてない状態です)

```ruby:ターミナル
irb> image_url
=> #<Addressable::URI:0x3fce24c8e664 URI:http://pbs.twimg.com/media/FEkrbLkagAAFXVH.jpg>

irb> io = open(image_url)
=> #<Tempfile:/var/folders/gp/jc5cbzvd0v77std1f228cw080000gn/T/open-uri20211130-72880-1f2t1e>

irb> p io
#<Tempfile:/var/folders/gp/jc5cbzvd0v77std1f228cw080000gn/T/open-uri20211130-72880-1f2t1e>
#Tempfileについて(テンポラリファイル(一時的なファイル)を操作するクラスです。画像などのダウンロードやアップロード処理の中で、一時的にファイルとして扱いたい場合などに利用できます。)
```

参照

https://qiita.com/nogtk_/items/ac53da197f20301c4241


#####❺ ❶❷❸❹で作成したメソッドとツイートIDを使用してツイートと画像を取得します。

```ruby:article.rb
#  GoodsFindというアカウントのツイート検索
    def make_GoodsFind_article
      search("100000000000")
      #searchメソッドにアカウントの idを渡して最新のツイートを取得させる。
      tag = ["#findgoodsofstan"]
      set_article(tag)
      #tagにそのアカウントが私物特定の時に使用するタグを渡し、set_articleメソッドを使用して私物特定のツイートかどうかを確認する。

#以下↓はアカウントごとに仕様が異なるのでメソッドにはしなかった。
#それぞれ正規表現を使用してツイート内容から必要な文字を抜き取る。
      @for_article_tweets.each do |tweet|
        tweet_content = tweet.text.gsub(/[\r\n]/,"")
        self.tweet_url = tweet.url
        self.price = tweet_content.scan(/¥.+?-/).join(',')
        self.brand = tweet_content.scan(/(?<=\【).+?(?=\】)/).join(',')
        self.item = tweet_content.scan(/(?<=\】).+?(?=\¥)/).join(',')
        check_member(tweet_content)
        #どのメンバーの私物か判断させる。
        set_images(tweet.media) 
        #tweet.mediaを引数にし、set_imageメソッドでがそうを登録させる。
        if save  #saveできれば、lineで送信する。
          send_line(member_ids, tweet_url, @imgae_url_for_line_small)
        end
      end
    end
```
#####❻ saveできたらLINEを送信する

**Twitter APIで取得した情報をLINE APIで送信する。**
ここの部分です。

```ruby:article.rb
if save  #saveできれば、lineで送信する。
  send_line(member_ids, tweet_url, @imgae_url_for_line_small)
end
```

`LineUserモデル`に`line_user_id`カラムを作成し、そこで`Lineのuser_id`を管理しているので、そのデータを使用し、送信先を判断します。(LINE API部分については、次の記事についてまとめます。)

```ruby:article.rb

def send_line(member_ids, tweet_url, @imgae_url_for_line_small)
   #Line認証
    client = Line::Bot::Client.new { |config|
      config.channel_secret = ENV["LINE_CHANNEL_SECRET"]
      config.channel_token = ENV["LINE_CHANNEL_TOKEN"]
    }
    #記事に該当するメンバー判断
    unless member_ids.empty?
      ids = member_ids.map(&:to_s)
    end
    
    #通知onにしているユーザーのline_user_idをにまとめる
    names = []
    user_ids = []

    if ids.include?("1")
      names << "高地優吾さん" 
      LineUser.yugo_on.each {|lineuser| user_ids << lineuser.line_user_id unless user_ids.include?(lineuser.line_user_id)}
    end
    if ids.include?("2")
      names << "京本大我さん" 
      LineUser.taiga_on.each {|lineuser| user_ids << lineuser.line_user_id unless user_ids.include?(lineuser.line_user_id)}
    end

    if ids.include?("3")
      names << "田中樹さん" 
      LineUser.juri_on.each {|lineuser| user_ids << lineuser.line_user_id unless user_ids.include?(lineuser.line_user_id)}
    end

    if ids.include?("4")
      names << "松村北斗さん"
      LineUser.hokuto_on.each {|lineuser| user_ids << lineuser.line_user_id unless user_ids.include?(lineuser.line_user_id)}
    end

    if ids.include?("5")
      names << "ジェシーさん"
      LineUser.jess_on.each {|lineuser| user_ids << lineuser.line_user_id unless user_ids.include?(lineuser.line_user_id)}
    end

    if ids.include?("6")
      names << "森本慎太郎さん"
      LineUser.shintarou_on.each {|lineuser| user_ids << lineuser.line_user_id unless user_ids.include?(lineuser.line_user_id)}
    end


    word = "#{self.brand}　#{self.price}　#{self.item}"
    enc = URI.encode_www_form_component(word)
    url = "https://www.google.co.jp/search?q="
    search_url = url+enc

    self.brand.present? ? brand = self.brand : brand = ""
    self.price.present? ? price = self.price : price = "  "

    message = 
      {
      "type": "flex",
      "altText": "#{names.join('と')}の私物が特定されました！(Twitter)",
      "contents": {
        "type": "bubble",
        "hero": {
          "type": "image",
          "url": tweet_image_url,
          "size": "full",
          "aspectRatio": "20:28",
          "aspectMode": "cover"
        },
        "body": {
          "type": "box",
          "layout": "vertical",
          "spacing": "sm",
          "contents": [
            {
              "type": "text",
              "wrap": true,
              "weight": "bold",
              "size": "xl",
              "text": "#{names.join('と')}着用"
            },
            {
              "type": "box",
              "layout": "vertical",
              "contents": [
                {
                  "type": "text",
                  "text": "#{brand}",
                  "wrap": true,
                  "weight": "bold",
                  "size": "xl",
                  "flex": 0
                },
                {
                  "type": "text",
                  "text": "#{price}",
                  "wrap": true,
                  "weight": "bold",
                  "size": "sm",
                  "flex": 0
                }
              ]
            }
          ]
        },
        "footer": {
          "type": "box",
          "layout": "vertical",
          "spacing": "sm",
          "contents": [
            {
              "type": "button",
              "style": "secondary",
              "action": {
                "type": "uri",
                "label": "商品検索",
                "uri": "#{search_url}"
              },
              "color": "#FCE3E7",
              "height": "md"
            },
            {
              "type": "button",
              "action": {
                "type": "uri",
                "label": "To tweet",
                "uri": "#{tweet_url}"
              }
            }
          ]
        }
      }
    }
    client.multicast(user_ids, message)
  end
```

`user_ids`には、情報が必要なユーザーのLINE user_idが入るので、`multicast`メソッドを使用して送信します。

LINE　API部分は次の記事でまとめています。