【Bラボデジタルサイネージ15】（発展編）ツイートを表示する「TwitterRModule」

![](https://qiita-user-contents.imgix.net/https%3A%2F%2Fuser-images.githubusercontent.com%2F29194025%2F137117015-af05fe4f-7c32-4e38-876f-e153ff2f2f7d.png?ixlib=rb-4.0.0&auto=format&gif-q=60&q=75&w=1400&fit=max&s=2bbf46fb5c1d8dec16f2fcb5d089fde2)

https://github.com/ohayota/BLabo-digital-signage

本記事は、[株式会社 函館ラボラトリ](http://www.hakolab.co.jp)が運営する「[Bラボ](http://www.hakolab.co.jp/b-lab/index.html)」における、大人向け「看板アプリ（デジタルサイネージ）を作るコース」用教材テキストです。

http://www.hakolab.co.jp/b-lab/index.html

1. [【Bラボデジタルサイネージ1】Processing+ラズパイでデジタルサイネージを作る！](https://qiita.com/ohayota/items/f4aa3e1b452060eb52ad)
2. [【Bラボデジタルサイネージ2】ラズパイ初期設定](https://qiita.com/ohayota/items/88fbcf177e6606311efb)
3. [【Bラボデジタルサイネージ3】準備プログラム作成＆機能実現の方針決定](https://qiita.com/ohayota/items/41a746d2258673726541)
4. [【Bラボデジタルサイネージ4】レイアウトの基準になるグリッドを表示する「GridModule」](https://qiita.com/ohayota/items/df704f523b5aa4dbb180)
5. [【Bラボデジタルサイネージ5】レイアウトの基準になる枠を表示する「PlaceholderModule」](https://qiita.com/ohayota/items/f52a10660009794afd9f)
6. [【Bラボデジタルサイネージ6】画像を全画面表示する「FullImageModule」](https://qiita.com/ohayota/items/4a9c8423459c251f4911)
7. [【Bラボデジタルサイネージ7】ページの自動切り替え](https://qiita.com/ohayota/items/9d2c28f9cbf2f559be31)
8. [【Bラボデジタルサイネージ8】設置されている場所の名前を表示する「LocationModule」](https://qiita.com/ohayota/items/046b361d945a709c4605)
9. [【Bラボデジタルサイネージ9】現在の時間を表示する「DateModule」](https://qiita.com/ohayota/items/6b80137e33bfb4172de6)
10. [【Bラボデジタルサイネージ10】ページ切り替えの時間が分かる「ProgressBarModule」](https://qiita.com/ohayota/items/9f3b6f9f32b7d93ef2a7)
11. [【Bラボデジタルサイネージ11】現在の表示中のページが分かる「PageControlModule」](https://qiita.com/ohayota/items/23acc993dca47286fffb)
12. [【Bラボデジタルサイネージ12】現在の天気を表示する「WeatherRModule」](https://qiita.com/ohayota/items/d9b1d21037dcedcfd78c)
13. [【Bラボデジタルサイネージ13】直近2件のバス時刻表を表示する「BusRModule」](https://qiita.com/ohayota/items/93d7c9b5328188b3c5b6)
14. [【Bラボデジタルサイネージ14】ごみ出しカレンダーを表示する「GomiRModule」](https://qiita.com/ohayota/items/40ed5db352a688637f83)
15. **【Bラボデジタルサイネージ15】（発展編）ツイートを表示する「TwitterRModule」（本記事）**
16. [【Bラボデジタルサイネージ16】開店／閉店を表示する「OpenCloseRModule」](https://qiita.com/ohayota/items/284b5c7e7101f602016f)
17. [【Bラボデジタルサイネージ17】部屋の温度を表示する「TemperatureRModule」](https://qiita.com/ohayota/items/386c27a021601fb3ccfd)
18. [【Bラボデジタルサイネージ18】部屋の明るさを表示する「BrightnessRModule」](https://qiita.com/ohayota/items/76279927b4ef35eaf628)
19. [【Bラボデジタルサイネージ19】起動画面を表示する「LaunchingScreenModule」](https://qiita.com/ohayota/items/0b6bdeda68c36707f762)
20. [【Bラボデジタルサイネージ20】RModuleの影を実装](https://qiita.com/ohayota/items/57242f18653ccf7eb83b)


# 前提

表示したいツイートを用意してください。
ツイートには画像を3枚添付し投稿してください。

TwitterRModuleでは、Twitter APIを使用します。
Twitter APIの利用申請が承認された状態で、実装を始めてください。


# 作るもの

Twitter APIを介して、画像付きのツイートを取得し表示します。

![0013.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/368192/d9fd914f-1b34-12aa-1865-82bfbe421ac1.png)


# ライブラリ導入

Twitterからツイートを取得するときに、ライブラリ「Twitter4J」を使用します。

https://twitter4j.org/ja/index.html#download

ライブラリをダウンロードし、Processingに導入してください。
下記の記事を参考に、ライブラリ導入まで進めてください。

https://qiita.com/Panya_2342/items/4f76a128f98109fbb041


# 画像の準備

TwitterRModuleでは、Twitterの公式ロゴを1つ使用します。
下記リンク先のページでダウンロードし、中身の「2021 Twitter logo - blue.png」を使用します。

https://about.twitter.com/en/who-we-are/brand-toolkit

Processingの画面上のタブで、「スケッチ > スケッチフォルダーを開く」を選ぶと、開いている.pdeファイルが一覧で表示されます。
「data」フォルダの中に、新たに「twitter」フォルダを作ってください。
「2021 Twitter logo - blue.png」を「data/twitter」フォルダの直下においてください。（ドラッグ＆ドロップ）

ここで新しく作った「data/twitter」フォルダの位置を示すパスを、定数として定義します。

``` java:DigitalSignage.pde
/* 略 */

final String AD_PATH = "ad/";
final String WEATHER_PATH = "weather/";
final String BUS_PATH = "bus/";
final String GOMI_PATH = "gomi/";
final String TWITTER_PATH = "twitter/"; /* 追加 */

/* 略 */
```

TwitterRModuleの背景を保持する変数を定義します。

``` java:DigitalSignage.pde
/* 略 */

/* ここから追加 */
// TwitterRModule
PGraphics twitterBackground;
/* ここまで追加 */

/* 略 */
```

RModuleのリストに、TwitterRModuleを表す「Twitter」を追加します。

``` java:DigitalSignage.pde
/* 略 */

enum RModule {
  Weather,
  Bus,
  Gomi,
  Twitter /* 追加 */
}

/* 略 */
```

モジュールの背景画像を生成します。
`initializeRModuleBackground()`内で実装します。
白い角丸四角から実装します。

``` java:Initialize.pde
/* 略 */

void initializeRModuleBackground() {
  /* 中略 */
  gomiBackground.rect(0, 0, w, h);
  gomiBackground.endDraw();
  gomiBackground.mask( sizeToModuleMask( moduleSize(module) ) );

  /* ここから追加 */  
  module = RModule.Twitter;
  w = moduleWidth( moduleSize(module) );
  h = moduleHeight( moduleSize(module) );
  twitterBackground = createGraphics(w, h);
  twitterBackground.beginDraw();
  twitterBackground.colorMode(HSB, 360, 100, 100, 100);
  twitterBackground.background(0, 0, 100);
  twitterBackground.endDraw();
  twitterBackground.mask( sizeToModuleMask( moduleSize(module) ) );
  /* ここまで追加 */

/* 略 */
```

Twitterのロゴを読み込み、縮小して`twitterBackground`の右上あたりに表示します。

``` java:Initialize.pde
/* 略 */

void initializeRModuleBackground() {
  /* 中略 */
  gomiBackground.rect(0, 0, w, h);
  gomiBackground.endDraw();
  gomiBackground.mask( sizeToModuleMask( moduleSize(module) ) );

  module = RModule.Twitter;
  w = moduleWidth( moduleSize(module) );
  h = moduleHeight( moduleSize(module) );
  twitterBackground = createGraphics(w, h);
  twitterBackground.beginDraw();
  twitterBackground.colorMode(HSB, 360, 100, 100, 100);
  twitterBackground.background(0, 0, 100);
  /* ここから追加 */
  PImage twitterLogo = loadImage(TWITTER_PATH + "2021 Twitter logo - blue.png");
  final float logoRatio = twitterLogo.height / float(twitterLogo.width); // 縦横比
  final float logoWidth = 80.0;
  final float logoHeight = logoWidth * logoRatio;
  twitterBackground.image(twitterLogo, w-logoWidth-50, 50, logoWidth, logoHeight);
  /* ここまで追加 */
  twitterBackground.endDraw();
  twitterBackground.mask( sizeToModuleMask( moduleSize(module) ) );

/* 略 */
```

ロゴを縮小表示するときの縦横比を保つために、変数`logoRatio`を定義し、その値を使ってロゴ画像の高さを定義しています。

ここまでの処理を実装した時点で、`twitterBackground`の中身は下図のような画像になっています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/368192/21987943-e52f-d42a-bc9c-1b0098699c46.png)

次に、ツイートから画像を正しく取得できなかったときに表示する、ダミーの画像を読み込みます。
下の画像をダミー画像として使います。

![360x360.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/368192/132d1bf2-0c78-d886-c745-f84642dcaeb5.jpeg)

画像を保存し、「360x360.jpg」という名前をつけます。
（.png形式など別の形式の場合は、.jpg形式に変換してください。）
Processingの画面上のタブで、「スケッチ > スケッチフォルダーを開く」を選ぶと、開いている.pdeファイルが一覧で表示されます。
「data」フォルダの中に、新たに「dummy」フォルダを作ってください。
先ほど名前をつけて保存した「360x360.jpg」を「data/dummy」フォルダの直下においてください。（ドラッグ＆ドロップ）
ここで新しく作った「data/dummy」フォルダの位置を示すパスを、定数として定義します。

``` java:DigitalSignage.pde
/* 略 */

final String AD_PATH = "ad/";
final String WEATHER_PATH = "weather/";
final String BUS_PATH = "bus/";
final String GOMI_PATH = "gomi/";
final String TWITTER_PATH = "twitter/";
final String DUMMY_PATH = "dummy/"; /* 追加 */

/* 略 */
```

読み込んだダミー画像を保存するための変数を定義します。

``` java:DigitalSignage.pde
// TwitterRModule
PGraphics twitterBackground;
PImage dummy360x360; /* 追加 */
```

関数`initializeImage()`内で、ダミー画像を読み込みます。

``` java:Initialize.pde
/* 略 */

void initializeImage() {
  busMap = pImageCut(loadImage(BUS_PATH + "bus_map.jpg"), CENTER, CENTER, 1280, 720);
  adImage = new PImage[AD_IMAGE_COUNT];
  for (int i = 0; i < AD_IMAGE_COUNT; i++) {
    adImage[i] = loadImage(AD_PATH + "ad" + i + ".jpg");
  }
  dummy360x360 = loadImage(DUMMY_PATH + "360x360.jpg"); /* 追加 */
}

/* 略 */
```


# TwitterRModule用関数の準備

新しいファイル`RM_Twitter.pde`を作ります。
GomiRModuleの描画用関数`drawTwitterRModule()`、ごみカレンダー更新用の関数`updateTwitter()`を作ります。

``` java:RM_Twitter.pde
void drawTwitterRModule(Area area) {

}

boolean updateTwitter() {
  return false;
}
```


# キーの取得

ツイートの取得でTwitter APIを使用するために、事前に利用許諾申請したTwitterアカウントで、「キー」という文字列を取得します。
「キー」は4つあり、これらすべてがAPI利用時の認証のために必要です。

- Consumer Key (API Key)
- Consumer Key Secret (API Key Secret)
- Access Token
- Access Token Secret

4つのキーを保持しておくための変数を定義します。

``` java:DigitalSignage.pde
/* 略 */

final String GOMI_API_URL = "取得したURL";
/* ここから追加 */
final String CONSUMER_KEY = "";
final String CONSUMER_KEY_SECRET = "";
final String ACCESS_TOKEN = "";
final String ACCESS_TOKEN_SECRET = "";
/* ここまで追加 */

/* 略 */
```

利用許諾時のメールから、Developer Accountのページにログインしてください。
ログイン後、Twitter Developer Appを作成してください。
画像の例では、`digital-signage-processing`という名前で作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/368192/c47ef761-3525-0f98-6d4e-38c1e2a6293a.png)

作成後Dashboardに戻ると、PROJECT APPという項目に「digital-signage-processing」が作成されています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/368192/a857ce8d-bfd3-9117-3154-8d19478a4668.png)

ここで右下の鍵のマークをクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/368192/afd132d7-fc65-498f-badf-7324b2600dad.png)

Keys and tokensというタブを開き、「API Key and Secret」で2つのキー、「Access Token and Secret」で2つのキーを生成します。
「API Key and Secret」の横にある「Regenerate」ボタンをクリックしてください。

![スクリーンショット 2021-11-07 20.01.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/368192/edcd8e22-697e-6433-19dd-69a5392c5387.png)

キーが生成されたので、2つを変数に代入します。

``` java:DigitalSignage.pde
/* 略 */

final String CONSUMER_KEY = "取得したAPI Key"; /* 変更 */
final String CONSUMER_KEY_SECRET = "取得したAPI Key Secret"; /* 変更 */
final String ACCESS_TOKEN = ""; /* 変更 */
final String ACCESS_TOKEN_SECRET = ""; /* 変更 */

/* 略 */
```

次に、「Access Token and Secret」の横にある「Regenerate」ボタンをクリックしてください。

![スクリーンショット 2021-11-07 20.12.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/368192/a111d45c-421a-eaaf-9bc2-8e717adc6bde.png)

キーが生成されたので、2つを変数に代入します。

``` java:DigitalSignage.pde
/* 略 */

final String CONSUMER_KEY = "取得したAPI Key";
final String CONSUMER_KEY_SECRET = "取得したAPI Key Secret";
final String ACCESS_TOKEN = "取得したAccess Token";
final String ACCESS_TOKEN_SECRET = "取得したAccess Token Secret";

/* 略 */
```


# ツイート取得処理

ツイートの取得には、「Twitter4J」というライブラリを使用します。
`DigitalSignage.pde`内で、ライブラリのimport文を記述します。

``` java:DigitalSignage.pde
import java.net.*;
import java.io.*;

/* ここから追加 */
import twitter4j.*;
import twitter4j.api.*;
import twitter4j.auth.*;
import twitter4j.conf.*;
import twitter4j.json.*;
import twitter4j.management.*;
import twitter4j.util.*;
import twitter4j.util.function.*;
import twitter4j.Status;
import twitter4j.Twitter;
import twitter4j.TwitterException;
import twitter4j.TwitterFactory;
/* ここまで追加 */

/* 略 */
```

次に、`RM_Twitter.pde`内の`updateTwitter()`を実装します。

すでに取得してある4つのキーを使い、初期設定を記述します。

``` java:RM_Twitter.pde
/* 略 */

boolean updateTwitter() {
  /* ここから追加 */
  // API関連の設定
  ConfigurationBuilder cb = new ConfigurationBuilder();
  cb.setDebugEnabled(true)
    .setOAuthConsumerKey(CONSUMER_KEY)
    .setOAuthConsumerSecret(CONSUMER_KEY_SECRET)
    .setOAuthAccessToken(ACCESS_TOKEN)
    .setOAuthAccessTokenSecret(ACCESS_TOKEN_SECRET);
    
  // TwitterAPIインスタンス取得
  Twitter twitter = new TwitterFactory(cb.build()).getInstance();
  /* ここまで追加 */

  return false;
}

```

TwitterAPIインスタンスには、ツイートを取得するための関数などが含まれています。
これ以降は`twitter`内の関数を多く使います。

ツイートが取得できたときとできなかった時の処理を分けて記述するために、try-catch文を記述します。

``` java:RM_Twitter.pde
/* 略 */

boolean updateTwitter() {
  tweetImages = new PImage[3];
  
  // API関連の設定
  ConfigurationBuilder cb = new ConfigurationBuilder();
  cb.setDebugEnabled(true)
    .setOAuthConsumerKey(CONSUMER_KEY)
    .setOAuthConsumerSecret(CONSUMER_KEY_SECRET)
    .setOAuthAccessToken(ACCESS_TOKEN)
    .setOAuthAccessTokenSecret(ACCESS_TOKEN_SECRET);
    
  // TwitterAPIインスタンス取得
  Twitter twitter = new TwitterFactory(cb.build()).getInstance();

  /* ここから追加 */
  try {
    println("updateTwitter(): ツイートを取得しました。");
  } catch (Exception e) {
    println("updateTwitter(): ツイートを取得できませんでした。" + e);
    return false;
  }
  
  return true;
  /* ここまで追加 */
}
```

指定のツイートを取得する処理を実装します。
ここでの指定のツイートの例として、ohayotaのツイートを挙げて説明します。
（手元で試すときは、かならずご自身の画像付きツイートを使用してください。）

https://twitter.com/ohayoooota/status/1323562540064862208

このツイートを指定するには、ツイートのIDを取得します。
ツイートのURLは以下のようになっています。

```
https://twitter.com/ohayoooota/status/1323562540064862208
```

このうち、ツイートのIDにあたるのは、末尾の数字列`1323562540064862208`です。
IDを保持しておく変数を定義します。

``` java:DigitalSignage.pde
/* 略 */

final String CONSUMER_KEY = "取得したAPI Key";
final String CONSUMER_KEY_SECRET = "取得したAPI Key Secret";
final String ACCESS_TOKEN = "取得したAccess Token";
final String ACCESS_TOKEN_SECRET = "取得したAccess Token Secret";

final long TWEET_ID = Long.parseLong("1323562540064862208"); /* 追加 */

/* 略 */
```

APIから取得した値を保持するための変数を定義します。

``` java:DigitalSignage.pde
/* 略 */

// TwitterRModule
PGraphics twitterBackground;
/* ここから追加 */
boolean isUpdatedTwitter = false;
Status tweetStatus;
PImage twitterUserIcon;
String twitterUserName = "";
String twitterUserScreenName = "";
String tweetText = "";
PImage[] tweetImages = new PImage[3];
/* ここまで追加 */

/* 略 */
```

「Status」は、ツイートのことを指しています。

定義した`TWEET_ID`を使い、ツイートを取得します。
ツイートから、ユーザの名前なども取得します。

``` java:RM_Twitter.pde
/* 略 */

boolean updateTwitter() {
  // API関連の設定
  ConfigurationBuilder cb = new ConfigurationBuilder();
  cb.setDebugEnabled(true)
    .setOAuthConsumerKey(CONSUMER_KEY)
    .setOAuthConsumerSecret(CONSUMER_KEY_SECRET)
    .setOAuthAccessToken(ACCESS_TOKEN)
    .setOAuthAccessTokenSecret(ACCESS_TOKEN_SECRET);
    
  // TwitterAPIインスタンス取得
  Twitter twitter = new TwitterFactory(cb.build()).getInstance();

  try {
    /* ここから追加 */
    // 指定IDをもつツイートを取得する
    try {
      tweetStatus = twitter.showStatus(TWEET_ID);
      twitterUserName = tweetStatus.getUser().getName();
      twitterUserScreenName = "@" + tweetStatus.getUser().getScreenName();
      tweetText = tweetStatus.getText();
    } catch (Exception e) {
      println("updateTwitter(): 該当のツイート(id:" + TWEET_ID + ")を取得できませんでした。" + e);
      return false;
    }
    /* ここまで追加 */
    println("updateTwitter(): ツイートを取得しました。");
  } catch (Exception e) {
    println("updateTwitter(): ツイートを取得できませんでした。" + e);
    return false;
  }
  
  return true;
}
```

このあと取得する画像を正方形に加工するために、関数`pImageToSquare`を定義します。

``` java:DigitalSignage.pde
/* 略 */

PImage pImageToSquare(PImage image, int mode) {
  int w = image.width;
  int h = image.height;
  if (w == h) return image;
  
  boolean isLongerHeight = (w < h);
  if (isLongerHeight) { // 画像が縦長のときの切り取り方
    if (mode == TOP) image = image.get(0, 0, w, w);
    if (mode == CENTER) image = image.get(0, h/2-w/2, w, w);
    if (mode == BOTTOM) image = image.get(0, h-w, w, w);
  } else { // 画像が横長のときの切り取り方
    if (mode == LEFT) image = image.get(0, 0, h, h);
    if (mode == CENTER) image = image.get(w/2-h/2, 0, h, h);
    if (mode == RIGHT) image = image.get(w-h, 0, h, h);
  }
  return image;
}
```

ツイートから3枚の画像を取得します。
ツイート内の画像URLは、「MediaEntity」という型の中に格納されています。

``` java:RM_Twitter.pde
/* 略 */

boolean updateTwitter() {
  // API関連の設定
  ConfigurationBuilder cb = new ConfigurationBuilder();
  cb.setDebugEnabled(true)
    .setOAuthConsumerKey(CONSUMER_KEY)
    .setOAuthConsumerSecret(CONSUMER_KEY_SECRET)
    .setOAuthAccessToken(ACCESS_TOKEN)
    .setOAuthAccessTokenSecret(ACCESS_TOKEN_SECRET);
    
  // TwitterAPIインスタンス取得
  Twitter twitter = new TwitterFactory(cb.build()).getInstance();

  try {
    // 指定IDをもつツイートを取得する
    try {
      tweetStatus = twitter.showStatus(TWEET_ID);
      twitterUserName = tweetStatus.getUser().getName();
      twitterUserScreenName = "@" + tweetStatus.getUser().getScreenName();
      tweetText = tweetStatus.getText();
    } catch (Exception e) {
      println("updateTwitter(): 該当のツイート(id:" + TWEET_ID + ")を取得できませんでした。" + e);
      return false;
    }
    
    /* ここから追加 */
    // 指定ツイートに含まれる画像を取得する（3枚まで）
    MediaEntity[] tweetMedias = tweetStatus.getMediaEntities();
    for (int i = 0; i < tweetImages.length; i++) {
      try {
        tweetImages[i] = pImageToSquare(loadImage(tweetMedias[i].getMediaURLHttps()), CENTER);
      } catch (Exception e) {
        tweetImages[i] = dummy360x360;
        println("updateTwitter(): ツイートに含まれる画像が3枚未満です。" + e);
      }
    }
    /* ここまで追加 */
    
    println("updateTwitter(): ツイートを取得しました。");
  } catch (Exception e) {
    println("updateTwitter(): ツイートを取得できませんでした。" + e);
    return false;
  }
  
  return true;
}
```

アカウントのプロフィール画像を取得します。

``` java:RM_Twitter.pde
/* 略 */

boolean updateTwitter() {
  // API関連の設定
  ConfigurationBuilder cb = new ConfigurationBuilder();
  cb.setDebugEnabled(true)
    .setOAuthConsumerKey(CONSUMER_KEY)
    .setOAuthConsumerSecret(CONSUMER_KEY_SECRET)
    .setOAuthAccessToken(ACCESS_TOKEN)
    .setOAuthAccessTokenSecret(ACCESS_TOKEN_SECRET);
    
  // TwitterAPIインスタンス取得
  Twitter twitter = new TwitterFactory(cb.build()).getInstance();

  try {
    // 指定IDをもつツイートを取得する
    try {
      tweetStatus = twitter.showStatus(TWEET_ID);
      twitterUserName = tweetStatus.getUser().getName();
      twitterUserScreenName = "@" + tweetStatus.getUser().getScreenName();
      tweetText = tweetStatus.getText();
    } catch (Exception e) {
      println("updateTwitter(): 該当のツイート(id:" + TWEET_ID + ")を取得できませんでした。" + e);
      return false;
    }
    
    // 指定ツイートに含まれる画像を取得する（3枚まで）
    MediaEntity[] tweetMedias = tweetStatus.getMediaEntities();
    for (int i = 0; i < tweetImages.length; i++) {
      try {
        tweetImages[i] = pImageToSquare(loadImage(tweetMedias[i].getMediaURLHttps()), CENTER);
      } catch (Exception e) {
        tweetImages[i] = dummy360x360;
        println("updateTwitter(): ツイートに含まれる画像が3枚未満です。" + e);
      }
    }

    /* ここから追加 */
    // ツイートからアカウント画像を取得する
    twitterUserIcon = loadImage(tweetStatus.getUser().get400x400ProfileImageURL());
    PGraphics maskLayer = createGraphics(twitterUserIcon.width, twitterUserIcon.width);
    maskLayer.beginDraw();
    maskLayer.noStroke();
    maskLayer.fill(255);
    maskLayer.circle(maskLayer.width/2, maskLayer.height/2, maskLayer.width);
    maskLayer.endDraw();
    // 画像を丸く切り抜く
    twitterUserIcon.mask(maskLayer);
    /* ここまで追加 */
    
    println("updateTwitter(): ツイートを取得しました。");
  } catch (Exception e) {
    println("updateTwitter(): ツイートを取得できませんでした。" + e);
    return false;
  }
  
  return true;
}
```

関数`updateTwitter()`が実装できたので、この関数を呼び出すようにします。
ツイートを更新するタイミングは、以下の2つとします。

- デジタルサイネージを起動したとき
- 1日経過したとき

``` java:DigitalSignage.pde
/* 略 */

void updateDatas() {
  updateDate();
  
  final boolean isUpdatedDay = (day != beforeDay);
  final boolean isUpdatedSecond = (second != beforeSecond);

  if (isUpdatedDay) {
    println("日付が変わりました。");

    if (!updateIsHoliday()) {
      println("ネットワークに接続できていない可能性があります。接続できているか確認してください。");
    }

    youbi = calcYoubi(year, month, day);
    youbiString = youbiToString(youbi);
    isUpdatedBus = updateBus();
    isUpdatedGomi = updateGomi();
    isUpdatedTwitter = updateTwitter(); /* 追加 */
    
    beforeDay = day;
  }
  
  if (isUpdatedSecond) {
    if (second % STAY_SECOND == 0) {
      updateNowPageID(true);
    }
    if (minute + second == 0) {
      println(hour + "時になりました。");
    }
    
    beforeSecond = second;
  }
}

/* 略 */
```

``` java:Initialize.pde
void initialize() {
  initializeDate();
  initializeImage();
  initializeGrid();
  initializePlaceholder();
  initializeRModuleBackground();
  
  isUpdatedWeather = updateWeather();
  isUpdatedBus = updateBus();
  isUpdatedGomi = updateGomi();
  isUpdatedTwitter = updateTwitter(); /* 追加 */
}

/* 略 */
```

# TwitterRModuleの描画

データが変数に代入できている状態になったので、画面上に表示するところまで実装します。

置換可能な**ReplaceableModule**を実装するとき、最低限定めなければいけないことは以下です。

- 描画時の基準となるエリア（Area.area1〜Area.area8のいずれか）
- 描画時のサイズ（Size.S〜Size.Lのいずれか）

まずはTwitterRModuleの描画時の基準となるエリアについて説明します。
TwitterRModuleの描画の基準になるのは、8つある表示エリアのうち`Area.area3`です。

![0038e.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/368192/fbc1ffa1-5fa7-67eb-caee-6d9a0d1efa53.png)

![0013.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/368192/d9fd914f-1b34-12aa-1865-82bfbe421ac1.png)

関数`moduleSize()`をTwitterRModuleに対応させます。
TwitterRModuleのサイズは`Size.L`です。

``` java:DigitalSignage.pde
Size moduleSize(RModule module) {
  if (module == RModule.Weather) return Size.M;
  if (module == RModule.Bus) return Size.L;
  if (module == RModule.Gomi) return Size.M;
  if (module == RModule.Twitter) return Size.L; /* 追加 */
  return Size.S;
}
```

テキスト表示のために定義した関数`drawText`の、テキストの自動改行に対応させたバージョンを新たに作ります。
テキストを表示するエリアの幅と高さを、関数の引数として受け取るようにします。

``` java:DigitalSignage.pde
void drawText(int alignX, int alignY, color c, int size, String text, int x, int y) {
  pushStyle();
  textAlign(alignX, alignY);
  textSize(size);
  fill(c);
  if (alignY == BASELINE) {
    text(text, x, y+size);
  } else {
    text(text, x, y);
  }
  popStyle();
}

/* ここから追加 */
void drawText(int alignX, int alignY, color c, int size, String text, int x, int y, int w, int h) {
  pushStyle();
  textAlign(alignX, alignY);
  textSize(size);
  fill(c);
  if (alignY == BASELINE) {
    text(text, x, y+size, w, h);
  } else {
    text(text, x, y, w, h);
  }
  popStyle();
}
/* ここまで追加 */
```

次に関数`drawTwitterRModule()`を実装します。
`Area.area3`の左上の座標、TwitterRModuleの幅と高さを取得します。

``` java:RM_Twitter.pde
void drawTwitterRModule(Area area) {
  /* ここから追加 */
  RModule module = RModule.Twitter;
  Size size = moduleSize(module);
  
  int x = layoutGuideX(area);
  int y = layoutGuideY(area);
  int w = moduleWidth(size);
  int h = moduleHeight(size);
  /* ここまで追加 */
}

/* 略 */
```

TwitterRModuleの背景画像、ツイートを取得できなかったときの表示を実装します。

``` java:RM_Twitter.pde
void drawTwitterRModule(Area area) {
  RModule module = RModule.Twitter;
  Size size = moduleSize(module);
  
  int x = layoutGuideX(area);
  int y = layoutGuideY(area);
  int w = moduleWidth(size);
  int h = moduleHeight(size);

  image(twitterBackground, x, y, w, h); /* 追加 */

  /* ここから追加 */
  if (isUpdatedTwitter) {
    
  } else {
    fill(0, 0, 0, 50);
    noStroke();
    rect(x, y, w, h, MODULE_RECT_ROUND);
    
    drawText(CENTER, CENTER, WHITE_COLOR, 24, "TwitterModule\nデータを取得できません", x+w/2, y+h/2);
  }
  /* ここまで追加 */
}

/* 略 */
```

ツイートのアカウントのアイコン、表示名、アカウント名、ツイート文章を表示します。

``` java:RM_Twitter.pde
void drawTwitterRModule(Area area) {
  RModule module = RModule.Twitter;
  Size size = moduleSize(module);
  
  int x = layoutGuideX(area);
  int y = layoutGuideY(area);
  int w = moduleWidth(size);
  int h = moduleHeight(size);

  image(twitterBackground, x, y, w, h);

  if (isUpdatedTwitter) {
    /* ここから追加 */
    // Twitterアカウントのアイコン表示
    image(twitterUserIcon, x+50, y+50, 80, 80);
    
    // Twitterアカウントの表示名、アカウント名（@hogehoge）を表示
    drawText(LEFT, BASELINE, BLACK_COLOR, 32, twitterUserName, x+150, y+50);
    drawText(LEFT, BASELINE, GRAY_COLOR, 24, twitterUserScreenName, x+150, y+90);
    
    // ツイートのテキストを表示
    drawText(LEFT, BASELINE, BLACK_COLOR, 20, tweetText, x+50, y+150, w-100, h-50-((w-100)*2/3)-200);
    /* ここまで追加 */
  } else {
    fill(0, 0, 0, 50);
    noStroke();
    rect(x, y, w, h, MODULE_RECT_ROUND);
    
    drawText(CENTER, CENTER, WHITE_COLOR, 24, "TwitterModule\nデータを取得できません", x+w/2, y+h/2);
  }
}

/* 略 */
```

画像を3枚表示します。

``` java:RM_Twitter.pde
void drawTwitterRModule(Area area) {
  RModule module = RModule.Twitter;
  Size size = moduleSize(module);
  
  int x = layoutGuideX(area);
  int y = layoutGuideY(area);
  int w = moduleWidth(size);
  int h = moduleHeight(size);
  
  image(twitterBackground, x, y, w, h);

  if (isUpdatedTwitter) {
    // Twitterアカウントのアイコン表示
    image(twitterUserIcon, x+50, y+50, 80, 80);
    
    // Twitterアカウントの表示名、アカウント名（@hogehoge）を表示
    drawText(LEFT, BASELINE, BLACK_COLOR, 32, twitterUserName, x+150, y+50);
    drawText(LEFT, BASELINE, GRAY_COLOR, 24, twitterUserScreenName, x+150, y+90);
    
    // ツイートのテキストを表示
    drawText(LEFT, BASELINE, BLACK_COLOR, 20, tweetText, x+50, y+150, w-100, h-50-((w-100)*2/3)-200);
    
    /* ここから追加 */
    // ツイートから取得した画像を3枚表示
    image(tweetImages[0], x+50, y+h-50-((w-100)*2/3), (w-100)*2/3, (w-100)*2/3);
    image(tweetImages[1], x+50+(w-100)*2/3, y+h-50-((w-100)*2/3), (w-100)/3, (w-100)/3);
    image(tweetImages[2], x+50+(w-100)*2/3, y+h-50-((w-100)/3), (w-100)/3, (w-100)/3);
    strokeWeight(1);
    stroke(WHITE_COLOR);
    noFill();
    // 画像どうしの境界をわかりやすくするために白い枠をつける
    rect(x+50, y+h-50-((w-100)*2/3), (w-100)*2/3, (w-100)*2/3);
    rect(x+50+(w-100)*2/3, y+h-50-((w-100)*2/3), (w-100)/3, (w-100)/3);
    rect(x+50+(w-100)*2/3, y+h-50-((w-100)/3), (w-100)/3, (w-100)/3);
    /* ここまで追加 */
  } else {
    fill(0, 0, 0, 50);
    noStroke();
    rect(x, y, w, h, MODULE_RECT_ROUND);
    
    drawText(CENTER, CENTER, WHITE_COLOR, 24, "TwitterModule\nデータを取得できません", x+w/2, y+h/2);
  }
}

/* 略 */
```

これで表示が実装できたので、`drawTwitterRModule()`を呼び出します。
1ページ目（`nowPageID`は0）は埋まっているので、2ページ目（`nowPageID`は1）を作成し、2ページ目にTwitterRModuleを表示します。

``` java:DigitalSignage.pde
void drawModules() {
  if (nowPageID == 0) {
    drawFullImageModule(background);
    drawGridModule();
    drawPlaceholderModule();
    drawWeatherRModule(Area.area1);
    drawBusRModule(Area.area3);
    drawGomiRModule(Area.area5);
  /* ここから変更 */
  } else if (nowPageID == 1) {
    drawFullImageModule(background);
    drawGridModule();
    drawPlaceholderModule();
    drawTwitterRModule(Area.area3); 
  } else if (nowPageID == 2) {
  /* ここまで変更 */
    drawFullImageModule(adImage[0]);
  }
  drawDateModule();
  drawLocationModule();
  drawProgressBarModule();
  drawPageControlModule();
}
```

ページの数が変わったので、全ページ数を表す変数の値を変更します。

``` java:DigitalSignage.pde
/* 略 */

final int PAGE_ALL_COUNT = 3; /* 変更 */
final int AD_IMAGE_COUNT = 1;

/* 略 */
```

ここまで記述したら、動かしてみましょう。
10秒くらい待つと2ページ目に遷移し、指定したツイートが表示されるようになっているはずです。

![0013.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/368192/d9fd914f-1b34-12aa-1865-82bfbe421ac1.png)

# 最後に

これで、（発展編）ツイートを表示する「TwitterRModule」が実装できました。
次はいよいよ電子部品を使います！
**[開店／閉店を表示する「OpenCloseRModule」](https://qiita.com/ohayota/items/284b5c7e7101f602016f)**を作ってみましょう。

https://qiita.com/ohayota/items/284b5c7e7101f602016f