commit df4ee0a0d080ba805c91215c1da941217ddd09ab
Author: みぞ@CrazyBeatCoder <mizo0203@mizo0203.com>
Date:   Thu Nov 23 18:47:50 2017 +0900

    Add Source Code. fixes #236 @8h

diff --git a/.classpath b/.classpath
new file mode 100644
index 0000000..91e3972
--- /dev/null
+++ b/.classpath
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7">
+		<attributes>
+			<attribute name="maven.pomderived" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
+		<attributes>
+			<attribute name="maven.pomderived" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry including="**/*.java" kind="src" output="target/classes" path="src">
+		<attributes>
+			<attribute name="optional" value="true"/>
+			<attribute name="maven.pomderived" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="output" path="target/classes"/>
+</classpath>
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ea8c4bf
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+/target
diff --git a/.project b/.project
new file mode 100644
index 0000000..ccf5407
--- /dev/null
+++ b/.project
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>TwitterTimelineTalker</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.m2e.core.maven2Builder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+		<nature>org.eclipse.m2e.core.maven2Nature</nature>
+	</natures>
+</projectDescription>
diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs
new file mode 100644
index 0000000..7a53139
--- /dev/null
+++ b/.settings/org.eclipse.core.resources.prefs
@@ -0,0 +1,3 @@
+eclipse.preferences.version=1
+encoding/<project>=UTF-8
+encoding/src=UTF-8
diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..ec4300d
--- /dev/null
+++ b/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,5 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
+org.eclipse.jdt.core.compiler.compliance=1.7
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
+org.eclipse.jdt.core.compiler.source=1.7
diff --git a/.settings/org.eclipse.m2e.core.prefs b/.settings/org.eclipse.m2e.core.prefs
new file mode 100644
index 0000000..f897a7f
--- /dev/null
+++ b/.settings/org.eclipse.m2e.core.prefs
@@ -0,0 +1,4 @@
+activeProfiles=
+eclipse.preferences.version=1
+resolveWorkspaceProjects=true
+version=1
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..b4ba65d
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,49 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+
+	<groupId>com.mizo0203</groupId>
+	<artifactId>TwitterTimelineTalker</artifactId>
+	<version>0.0.1-SNAPSHOT</version>
+	<packaging>jar</packaging>
+
+	<properties>
+		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+	</properties>
+
+	<build>
+		<sourceDirectory>src</sourceDirectory>
+		<outputDirectory>target/classes</outputDirectory>
+		<resources>
+			<resource>
+				<directory>src</directory>
+				<excludes>
+					<exclude>**/*.java</exclude>
+				</excludes>
+			</resource>
+		</resources>
+		<plugins>
+			<plugin>
+				<artifactId>maven-compiler-plugin</artifactId>
+				<version>3.1</version>
+				<configuration>
+					<source>1.7</source>
+					<target>1.7</target>
+				</configuration>
+			</plugin>
+		</plugins>
+	</build>
+
+	<dependencies>
+		<dependency>
+			<groupId>org.twitter4j</groupId>
+			<artifactId>twitter4j-core</artifactId>
+			<version>[4.0,)</version>
+		</dependency>
+		<dependency>
+			<groupId>org.twitter4j</groupId>
+			<artifactId>twitter4j-stream</artifactId>
+			<version>[4.0,)</version>
+		</dependency>
+	</dependencies>
+</project>
diff --git a/src/com/mizo0203/twitter/timeline/talker/Application.java b/src/com/mizo0203/twitter/timeline/talker/Application.java
new file mode 100644
index 0000000..79f51a3
--- /dev/null
+++ b/src/com/mizo0203/twitter/timeline/talker/Application.java
@@ -0,0 +1,79 @@
+package com.mizo0203.twitter.timeline.talker;
+
+import twitter4j.conf.Configuration;
+import twitter4j.conf.ConfigurationBuilder;
+
+/**
+ * Java アプリケーション起動時に実行されるクラス
+ * 
+ * @author みぞ@CrazyBeatCoder
+ */
+public class Application {
+
+  public static void main(String[] args) {
+    TwitterTimelineTalker twitterTimelineTalker;
+    Talker talker;
+
+    try {
+      talker = new Talker();
+      twitterTimelineTalker =
+          new TwitterTimelineTalker(new Arguments(args).twitterConfiguration, talker);
+    } catch (IllegalArgumentException | IllegalStateException e) {
+      System.err.println(e.getMessage());
+      return;
+    }
+
+    twitterTimelineTalker.start();
+    talker.talkAsync("アプリケーションを起動しました", Talker.YukkuriVoice.REIMU);
+  }
+
+  /**
+   * Java アプリケーション起動時に指定する引数のデータクラス
+   * 
+   * @author みぞ@CrazyBeatCoder
+   */
+  private static class Arguments {
+
+    private final Configuration twitterConfiguration;
+
+    private Arguments(String[] args) throws IllegalArgumentException {
+      if (args.length < Argument.values().length) {
+        StringBuilder exceptionMessage = new StringBuilder();
+        exceptionMessage.append(Argument.values().length + " つの引数を指定してください。\n");
+        for (Argument arg : Argument.values()) {
+          exceptionMessage.append((arg.ordinal() + 1) + " つ目: " + arg.detail + "\n");
+        }
+        throw new IllegalArgumentException(exceptionMessage.toString());
+      }
+
+      String consumer_key = args[Argument.CONSUMER_KEY.ordinal()];
+      String consumer_secret = args[Argument.CONSUMER_SECRET.ordinal()];
+      String access_token = args[Argument.ACCESS_TOKEN.ordinal()];
+      String access_token_secret = args[Argument.ACCESS_TOKEN_SECRET.ordinal()];
+
+      twitterConfiguration = new ConfigurationBuilder().setOAuthConsumerKey(consumer_key)
+          .setOAuthConsumerSecret(consumer_secret).setOAuthAccessToken(access_token)
+          .setOAuthAccessTokenSecret(access_token_secret).build();
+    }
+
+  }
+
+  /**
+   * Java アプリケーション起動時に指定する引数の定義
+   * 
+   * @author みぞ@CrazyBeatCoder
+   */
+  private enum Argument {
+    CONSUMER_KEY("Twitter Application's Consumer Key (API Key)"), //
+    CONSUMER_SECRET("Twitter Application's Consumer Secret (API Secret)"), //
+    ACCESS_TOKEN("Twitter Account's Access Token"), //
+    ACCESS_TOKEN_SECRET("Twitter Account's Access Token Secret"), //
+    ;
+
+    private final String detail;
+
+    private Argument(String detail) {
+      this.detail = detail;
+    }
+  }
+}
diff --git a/src/com/mizo0203/twitter/timeline/talker/RuntimeUtil.java b/src/com/mizo0203/twitter/timeline/talker/RuntimeUtil.java
new file mode 100644
index 0000000..c0c225b
--- /dev/null
+++ b/src/com/mizo0203/twitter/timeline/talker/RuntimeUtil.java
@@ -0,0 +1,17 @@
+package com.mizo0203.twitter.timeline.talker;
+
+import java.io.IOException;
+
+public class RuntimeUtil {
+
+  public static void execute(String[] cmdarray) {
+    try {
+      Process process = Runtime.getRuntime().exec(cmdarray);
+      process.waitFor();
+      process.destroy();
+    } catch (IOException | InterruptedException e) {
+      e.printStackTrace();
+    }
+  }
+
+}
diff --git a/src/com/mizo0203/twitter/timeline/talker/Talker.java b/src/com/mizo0203/twitter/timeline/talker/Talker.java
new file mode 100644
index 0000000..8382b0e
--- /dev/null
+++ b/src/com/mizo0203/twitter/timeline/talker/Talker.java
@@ -0,0 +1,63 @@
+package com.mizo0203.twitter.timeline.talker;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+public class Talker {
+
+  private static final String AQUESTALK_PI_PATH = "./aquestalkpi/AquesTalkPi";
+
+  private final ExecutorService mSingleThreadExecutor = Executors.newSingleThreadExecutor();
+
+  public Talker() throws IllegalStateException, SecurityException {
+    File file = new File(AQUESTALK_PI_PATH);
+    if (!file.isFile()) {
+      throw new IllegalStateException(file.getPath() + " に AquesTalk Pi がありません。\n"
+          + "https://www.a-quest.com/products/aquestalkpi.html\n" + "からダウンロードしてください。");
+    }
+    if (!file.canExecute()) {
+      throw new IllegalStateException(file.getPath() + " に実行権限がありません。");
+    }
+  }
+
+  public void talkAsync(final String text, final YukkuriVoice voice) {
+    mSingleThreadExecutor.submit(new Runnable() {
+
+      @Override
+      public void run() {
+        try {
+          File file = new File("text.txt");
+          PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter(file)));
+          pw.println(text);
+          pw.flush();
+          pw.close();
+          RuntimeUtil.execute(new String[] {AQUESTALK_PI_PATH, "-v", voice.value, "-f", "text.txt",
+              "-o", "out.wav"});
+          RuntimeUtil.execute(new String[] {"sh", "-c", "aplay < out.wav"}); // 起動コマンドを指定する
+          Thread.sleep(2000);
+        } catch (IOException | InterruptedException e) {
+          e.printStackTrace();
+        }
+      }
+
+    });
+  }
+
+  public static enum YukkuriVoice {
+    REIMU("f1"), //
+    MARISA("f2"), //
+    ;
+
+    private final String value;
+
+    private YukkuriVoice(String value) {
+      this.value = value;
+    }
+  }
+
+}
diff --git a/src/com/mizo0203/twitter/timeline/talker/TwitterTimelineTalker.java b/src/com/mizo0203/twitter/timeline/talker/TwitterTimelineTalker.java
new file mode 100644
index 0000000..6329acc
--- /dev/null
+++ b/src/com/mizo0203/twitter/timeline/talker/TwitterTimelineTalker.java
@@ -0,0 +1,72 @@
+package com.mizo0203.twitter.timeline.talker;
+
+import twitter4j.StallWarning;
+import twitter4j.Status;
+import twitter4j.StatusDeletionNotice;
+import twitter4j.StatusListener;
+import twitter4j.TwitterStream;
+import twitter4j.TwitterStreamFactory;
+import twitter4j.conf.Configuration;
+
+public class TwitterTimelineTalker {
+
+  private boolean mVoice_f1 = true;
+  private final TwitterStream mTwitterStream;
+  private final Talker mTalker;
+
+  public TwitterTimelineTalker(Configuration configuration, Talker talker) {
+    mTwitterStream = new TwitterStreamFactory(configuration).getInstance();
+    mTwitterStream.addListener(new MyStatusListener());
+    mTalker = talker;
+  }
+
+  public void start() {
+    mTwitterStream.user();
+  }
+
+  private class MyStatusListener implements StatusListener {
+
+    public void onStatus(final Status status) {
+      if (!"ja".equalsIgnoreCase(status.getLang())) {
+        return;
+      }
+      final StringBuffer buffer = new StringBuffer();
+      buffer.append(status.getUser().getName());
+      buffer.append("さんから、");
+      buffer.append(status.getText());
+      System.out.println(buffer);
+
+      // System.out.println("@" + status.getUser().getScreenName() + " | "
+      // + status.getText() + " 【 https://twitter.com/" +
+      // status.getUser().getScreenName() + "/status/" + status.getId() +
+      // " 】");
+      // こんな感じでstatusについている名前とかを色々表示させるとさらに欲しい情報にたどり着けると思います
+
+
+      mTalker.talkAsync(UrlUtil.convURLEmpty(buffer).replaceAll("\n", "。"),
+          (mVoice_f1 ? Talker.YukkuriVoice.REIMU : Talker.YukkuriVoice.MARISA));
+      mVoice_f1 = !mVoice_f1;
+
+    }
+
+    public void onDeletionNotice(StatusDeletionNotice sdn) {
+      System.err.println("onDeletionNotice.");
+    }
+
+    public void onTrackLimitationNotice(int i) {
+      System.err.println("onTrackLimitationNotice.(" + i + ")");
+    }
+
+    public void onScrubGeo(long lat, long lng) {
+      System.err.println("onScrubGeo.(" + lat + ", " + lng + ")");
+    }
+
+    public void onException(Exception excptn) {
+      System.err.println("onException.");
+    }
+
+    @Override
+    public void onStallWarning(StallWarning arg0) {}
+  }
+
+}
diff --git a/src/com/mizo0203/twitter/timeline/talker/UrlUtil.java b/src/com/mizo0203/twitter/timeline/talker/UrlUtil.java
new file mode 100644
index 0000000..264cd49
--- /dev/null
+++ b/src/com/mizo0203/twitter/timeline/talker/UrlUtil.java
@@ -0,0 +1,24 @@
+package com.mizo0203.twitter.timeline.talker;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * http://chat-messenger.net/blog-entry-40.html
+ */
+public class UrlUtil {
+  /** URLを抽出するための正規表現パターン */
+  private static final Pattern convURLLinkPtn = Pattern.compile(
+      "(http://|https://){1}[\\w\\.\\-/:\\#\\?\\=\\&\\;\\%\\~\\+]+", Pattern.CASE_INSENSITIVE);
+
+  /**
+   * 指定された文字列内のURLを、正規表現を使用し、 空文字列に変換する。
+   * 
+   * @param str 指定の文字列。
+   * @return リンクに変換された文字列。
+   */
+  public static String convURLEmpty(CharSequence str) {
+    Matcher matcher = convURLLinkPtn.matcher(str);
+    return matcher.replaceAll("");
+  }
+}
