リビジョン 493ff79f
みぞ @mizo0203 さんが7年以上前に追加
| .idea/artifacts/TimelineTalker_jar.xml | ||
|---|---|---|
| 1 |
<component name="ArtifactManager"> |
|
| 2 |
<artifact type="jar" name="TimelineTalker:jar"> |
|
| 3 |
<output-path>$PROJECT_DIR$/out/artifacts/TimelineTalker_jar</output-path> |
|
| 4 |
<root id="archive" name="TimelineTalker.jar"> |
|
| 5 |
<element id="module-output" name="TimelineTalker"/> |
|
| 6 |
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/twitter4j/twitter4j-core/4.0.6/twitter4j-core-4.0.6.jar" path-in-jar="/" /> |
|
| 7 |
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/twitter4j/twitter4j-stream/4.0.6/twitter4j-stream-4.0.6.jar" path-in-jar="/" /> |
|
| 8 |
</root> |
|
| 9 |
</artifact> |
|
| 10 |
</component> |
|
| .idea/artifacts/TwitterTimelineTalker_jar.xml | ||
|---|---|---|
| 1 |
<component name="ArtifactManager"> |
|
| 2 |
<artifact type="jar" name="TwitterTimelineTalker:jar"> |
|
| 3 |
<output-path>$PROJECT_DIR$/out/artifacts/TwitterTimelineTalker_jar</output-path> |
|
| 4 |
<root id="archive" name="TwitterTimelineTalker.jar"> |
|
| 5 |
<element id="module-output" name="TwitterTimelineTalker" /> |
|
| 6 |
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/twitter4j/twitter4j-core/4.0.6/twitter4j-core-4.0.6.jar" path-in-jar="/" /> |
|
| 7 |
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/twitter4j/twitter4j-stream/4.0.6/twitter4j-stream-4.0.6.jar" path-in-jar="/" /> |
|
| 8 |
</root> |
|
| 9 |
</artifact> |
|
| 10 |
</component> |
|
| .idea/compiler.xml | ||
|---|---|---|
| 6 | 6 |
<sourceOutputDir name="target/generated-sources/annotations" /> |
| 7 | 7 |
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" /> |
| 8 | 8 |
<outputRelativeToContentRoot value="true" /> |
| 9 |
<module name="TwitterTimelineTalker" />
|
|
| 9 |
<module name="TimelineTalker"/>
|
|
| 10 | 10 |
</profile> |
| 11 | 11 |
</annotationProcessing> |
| 12 | 12 |
<bytecodeTargetLevel> |
| 13 |
<module name="TwitterTimelineTalker" target="1.7" />
|
|
| 13 |
<module name="TimelineTalker" target="1.7"/>
|
|
| 14 | 14 |
</bytecodeTargetLevel> |
| 15 | 15 |
</component> |
| 16 | 16 |
</project> |
| .idea/modules.xml | ||
|---|---|---|
| 2 | 2 |
<project version="4"> |
| 3 | 3 |
<component name="ProjectModuleManager"> |
| 4 | 4 |
<modules> |
| 5 |
<module fileurl="file://$PROJECT_DIR$/TwitterTimelineTalker.iml" filepath="$PROJECT_DIR$/TwitterTimelineTalker.iml" />
|
|
| 5 |
<module fileurl="file://$PROJECT_DIR$/TimelineTalker.iml" filepath="$PROJECT_DIR$/TimelineTalker.iml"/>
|
|
| 6 | 6 |
</modules> |
| 7 | 7 |
</component> |
| 8 | 8 |
</project> |
| TimelineTalker.iml | ||
|---|---|---|
| 1 |
<?xml version="1.0" encoding="UTF-8"?> |
|
| 2 |
<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4"> |
|
| 3 |
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7"> |
|
| 4 |
<output url="file://$MODULE_DIR$/target/classes" /> |
|
| 5 |
<output-test url="file://$MODULE_DIR$/target/test-classes" /> |
|
| 6 |
<content url="file://$MODULE_DIR$"> |
|
| 7 |
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" /> |
|
| 8 |
<excludeFolder url="file://$MODULE_DIR$/target" /> |
|
| 9 |
</content> |
|
| 10 |
<orderEntry type="inheritedJdk" /> |
|
| 11 |
<orderEntry type="sourceFolder" forTests="false" /> |
|
| 12 |
<orderEntry type="library" name="Maven: org.twitter4j:twitter4j-core:4.0.6" level="project" /> |
|
| 13 |
<orderEntry type="library" name="Maven: org.twitter4j:twitter4j-stream:4.0.6" level="project" /> |
|
| 14 |
</component> |
|
| 15 |
</module> |
|
| TwitterTimelineTalker.iml | ||
|---|---|---|
| 1 |
<?xml version="1.0" encoding="UTF-8"?> |
|
| 2 |
<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4"> |
|
| 3 |
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7"> |
|
| 4 |
<output url="file://$MODULE_DIR$/target/classes" /> |
|
| 5 |
<output-test url="file://$MODULE_DIR$/target/test-classes" /> |
|
| 6 |
<content url="file://$MODULE_DIR$"> |
|
| 7 |
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" /> |
|
| 8 |
<excludeFolder url="file://$MODULE_DIR$/target" /> |
|
| 9 |
</content> |
|
| 10 |
<orderEntry type="inheritedJdk" /> |
|
| 11 |
<orderEntry type="sourceFolder" forTests="false" /> |
|
| 12 |
<orderEntry type="library" name="Maven: org.twitter4j:twitter4j-core:4.0.6" level="project" /> |
|
| 13 |
<orderEntry type="library" name="Maven: org.twitter4j:twitter4j-stream:4.0.6" level="project" /> |
|
| 14 |
</component> |
|
| 15 |
</module> |
|
| pom.xml | ||
|---|---|---|
| 3 | 3 |
<modelVersion>4.0.0</modelVersion> |
| 4 | 4 |
|
| 5 | 5 |
<groupId>com.mizo0203</groupId> |
| 6 |
<artifactId>TwitterTimelineTalker</artifactId>
|
|
| 6 |
<artifactId>TimelineTalker</artifactId> |
|
| 7 | 7 |
<version>0.0.1-SNAPSHOT</version> |
| 8 | 8 |
<packaging>jar</packaging> |
| 9 | 9 |
|
| src/META-INF/MANIFEST.MF | ||
|---|---|---|
| 1 | 1 |
Manifest-Version: 1.0 |
| 2 |
Main-Class: com.mizo0203.twitter.timeline.talker.Application
|
|
| 2 |
Main-Class: com.mizo0203.timeline.talker.Application |
|
| 3 | 3 |
|
| src/com/mizo0203/timeline/talker/Application.java | ||
|---|---|---|
| 1 |
package com.mizo0203.timeline.talker; |
|
| 2 |
|
|
| 3 |
import twitter4j.conf.Configuration; |
|
| 4 |
import twitter4j.conf.ConfigurationBuilder; |
|
| 5 |
|
|
| 6 |
/** |
|
| 7 |
* Java アプリケーション起動時に実行されるクラス |
|
| 8 |
* |
|
| 9 |
* @author みぞ@CrazyBeatCoder |
|
| 10 |
*/ |
|
| 11 |
public class Application {
|
|
| 12 |
|
|
| 13 |
public static void main(String[] args) {
|
|
| 14 |
TimelineTalker timelineTalker; |
|
| 15 |
Talker talker; |
|
| 16 |
|
|
| 17 |
try {
|
|
| 18 |
talker = new Talker(); |
|
| 19 |
timelineTalker = |
|
| 20 |
new TimelineTalker(new Arguments(args).twitterConfiguration, talker); |
|
| 21 |
} catch (IllegalArgumentException | IllegalStateException e) {
|
|
| 22 |
System.err.println(e.getMessage()); |
|
| 23 |
return; |
|
| 24 |
} |
|
| 25 |
|
|
| 26 |
timelineTalker.start(); |
|
| 27 |
talker.talkAsync("アプリケーションを起動しました", Talker.YukkuriVoice.REIMU);
|
|
| 28 |
} |
|
| 29 |
|
|
| 30 |
/** |
|
| 31 |
* Java アプリケーション起動時に指定する引数のデータクラス |
|
| 32 |
* |
|
| 33 |
* @author みぞ@CrazyBeatCoder |
|
| 34 |
*/ |
|
| 35 |
private static class Arguments {
|
|
| 36 |
|
|
| 37 |
private final Configuration twitterConfiguration; |
|
| 38 |
|
|
| 39 |
private Arguments(String[] args) throws IllegalArgumentException {
|
|
| 40 |
if (args.length < Argument.values().length) {
|
|
| 41 |
StringBuilder exceptionMessage = new StringBuilder(); |
|
| 42 |
exceptionMessage.append(Argument.values().length + " つの引数を指定してください。\n"); |
|
| 43 |
for (Argument arg : Argument.values()) {
|
|
| 44 |
exceptionMessage.append((arg.ordinal() + 1) + " つ目: " + arg.detail + "\n"); |
|
| 45 |
} |
|
| 46 |
throw new IllegalArgumentException(exceptionMessage.toString()); |
|
| 47 |
} |
|
| 48 |
|
|
| 49 |
String consumer_key = args[Argument.CONSUMER_KEY.ordinal()]; |
|
| 50 |
String consumer_secret = args[Argument.CONSUMER_SECRET.ordinal()]; |
|
| 51 |
String access_token = args[Argument.ACCESS_TOKEN.ordinal()]; |
|
| 52 |
String access_token_secret = args[Argument.ACCESS_TOKEN_SECRET.ordinal()]; |
|
| 53 |
|
|
| 54 |
twitterConfiguration = new ConfigurationBuilder().setOAuthConsumerKey(consumer_key) |
|
| 55 |
.setOAuthConsumerSecret(consumer_secret).setOAuthAccessToken(access_token) |
|
| 56 |
.setOAuthAccessTokenSecret(access_token_secret).build(); |
|
| 57 |
} |
|
| 58 |
|
|
| 59 |
} |
|
| 60 |
|
|
| 61 |
/** |
|
| 62 |
* Java アプリケーション起動時に指定する引数の定義 |
|
| 63 |
* |
|
| 64 |
* @author みぞ@CrazyBeatCoder |
|
| 65 |
*/ |
|
| 66 |
private enum Argument {
|
|
| 67 |
CONSUMER_KEY("Twitter Application's Consumer Key (API Key)"), //
|
|
| 68 |
CONSUMER_SECRET("Twitter Application's Consumer Secret (API Secret)"), //
|
|
| 69 |
ACCESS_TOKEN("Twitter Account's Access Token"), //
|
|
| 70 |
ACCESS_TOKEN_SECRET("Twitter Account's Access Token Secret"), //
|
|
| 71 |
; |
|
| 72 |
|
|
| 73 |
private final String detail; |
|
| 74 |
|
|
| 75 |
private Argument(String detail) {
|
|
| 76 |
this.detail = detail; |
|
| 77 |
} |
|
| 78 |
} |
|
| 79 |
} |
|
| src/com/mizo0203/timeline/talker/RuntimeUtil.java | ||
|---|---|---|
| 1 |
package com.mizo0203.timeline.talker; |
|
| 2 |
|
|
| 3 |
import java.io.IOException; |
|
| 4 |
|
|
| 5 |
public class RuntimeUtil {
|
|
| 6 |
|
|
| 7 |
public static void execute(String[] cmdarray) {
|
|
| 8 |
try {
|
|
| 9 |
Process process = Runtime.getRuntime().exec(cmdarray); |
|
| 10 |
process.waitFor(); |
|
| 11 |
process.destroy(); |
|
| 12 |
} catch (IOException | InterruptedException e) {
|
|
| 13 |
e.printStackTrace(); |
|
| 14 |
} |
|
| 15 |
} |
|
| 16 |
|
|
| 17 |
} |
|
| src/com/mizo0203/timeline/talker/Talker.java | ||
|---|---|---|
| 1 |
package com.mizo0203.timeline.talker; |
|
| 2 |
|
|
| 3 |
import java.io.*; |
|
| 4 |
import java.util.concurrent.ExecutorService; |
|
| 5 |
import java.util.concurrent.Executors; |
|
| 6 |
|
|
| 7 |
public class Talker {
|
|
| 8 |
|
|
| 9 |
private static final String AQUESTALK_PI_PATH = "./aquestalkpi/AquesTalkPi"; |
|
| 10 |
|
|
| 11 |
private final ExecutorService mSingleThreadExecutor = Executors.newSingleThreadExecutor(); |
|
| 12 |
|
|
| 13 |
public Talker() throws IllegalStateException, SecurityException {
|
|
| 14 |
File file = new File(AQUESTALK_PI_PATH); |
|
| 15 |
if (!file.isFile()) {
|
|
| 16 |
throw new IllegalStateException(file.getPath() + " に AquesTalk Pi がありません。\n" |
|
| 17 |
+ "https://www.a-quest.com/products/aquestalkpi.html\n" + "からダウンロードしてください。"); |
|
| 18 |
} |
|
| 19 |
if (!file.canExecute()) {
|
|
| 20 |
throw new IllegalStateException(file.getPath() + " に実行権限がありません。"); |
|
| 21 |
} |
|
| 22 |
} |
|
| 23 |
|
|
| 24 |
public void talkAsync(final String text, final YukkuriVoice voice) {
|
|
| 25 |
mSingleThreadExecutor.submit(new Runnable() {
|
|
| 26 |
|
|
| 27 |
@Override |
|
| 28 |
public void run() {
|
|
| 29 |
try {
|
|
| 30 |
File file = new File("text.txt");
|
|
| 31 |
PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter(file))); |
|
| 32 |
pw.println(text); |
|
| 33 |
pw.flush(); |
|
| 34 |
pw.close(); |
|
| 35 |
RuntimeUtil.execute(new String[] {AQUESTALK_PI_PATH, "-v", voice.value, "-f", "text.txt",
|
|
| 36 |
"-o", "out.wav"}); |
|
| 37 |
RuntimeUtil.execute(new String[] {"sh", "-c", "aplay < out.wav"}); // 起動コマンドを指定する
|
|
| 38 |
Thread.sleep(2000); |
|
| 39 |
} catch (IOException | InterruptedException e) {
|
|
| 40 |
e.printStackTrace(); |
|
| 41 |
} |
|
| 42 |
} |
|
| 43 |
|
|
| 44 |
}); |
|
| 45 |
} |
|
| 46 |
|
|
| 47 |
public static enum YukkuriVoice {
|
|
| 48 |
|
|
| 49 |
/** |
|
| 50 |
* ゆっくりボイス - 霊夢 |
|
| 51 |
*/ |
|
| 52 |
REIMU("f1"), //
|
|
| 53 |
|
|
| 54 |
/** |
|
| 55 |
* ゆっくりボイス - 魔理沙 |
|
| 56 |
*/ |
|
| 57 |
MARISA("f2"), //
|
|
| 58 |
; |
|
| 59 |
|
|
| 60 |
private final String value; |
|
| 61 |
|
|
| 62 |
private YukkuriVoice(String value) {
|
|
| 63 |
this.value = value; |
|
| 64 |
} |
|
| 65 |
} |
|
| 66 |
|
|
| 67 |
} |
|
| src/com/mizo0203/timeline/talker/TimelineTalker.java | ||
|---|---|---|
| 1 |
package com.mizo0203.timeline.talker; |
|
| 2 |
|
|
| 3 |
import twitter4j.*; |
|
| 4 |
import twitter4j.conf.Configuration; |
|
| 5 |
|
|
| 6 |
import java.util.Locale; |
|
| 7 |
import java.util.regex.Matcher; |
|
| 8 |
import java.util.regex.Pattern; |
|
| 9 |
|
|
| 10 |
public class TimelineTalker {
|
|
| 11 |
|
|
| 12 |
/** |
|
| 13 |
* ISO 639 言語コード - 日本語 (ja) |
|
| 14 |
*/ |
|
| 15 |
public static final String LANG_JA = Locale.JAPAN.getLanguage(); |
|
| 16 |
|
|
| 17 |
private Talker.YukkuriVoice mYukkuriVoice = Talker.YukkuriVoice.REIMU; |
|
| 18 |
private final TwitterStream mTwitterStream; |
|
| 19 |
private final Talker mTalker; |
|
| 20 |
|
|
| 21 |
public TimelineTalker(Configuration configuration, Talker talker) {
|
|
| 22 |
mTwitterStream = new TwitterStreamFactory(configuration).getInstance(); |
|
| 23 |
mTwitterStream.addListener(new OnStatusEvent()); |
|
| 24 |
mTalker = talker; |
|
| 25 |
} |
|
| 26 |
|
|
| 27 |
public void start() {
|
|
| 28 |
// OnStatusEvent に Twitter タイムラインが通知される |
|
| 29 |
mTwitterStream.user(); |
|
| 30 |
} |
|
| 31 |
|
|
| 32 |
private static String getUserNameWithoutContext(String name) {
|
|
| 33 |
Pattern p = Pattern.compile("([^@@]+).+");
|
|
| 34 |
Matcher m = p.matcher(name); |
|
| 35 |
return m.replaceFirst("$1");
|
|
| 36 |
} |
|
| 37 |
|
|
| 38 |
private class OnStatusEvent implements StatusListener {
|
|
| 39 |
|
|
| 40 |
public void onStatus(final Status status) {
|
|
| 41 |
if (!LANG_JA.equalsIgnoreCase(status.getLang())) {
|
|
| 42 |
return; |
|
| 43 |
} |
|
| 44 |
|
|
| 45 |
final StringBuffer buffer = new StringBuffer(); |
|
| 46 |
|
|
| 47 |
if (status.isRetweet()) {
|
|
| 48 |
Status retweetedStatus = status.getRetweetedStatus(); |
|
| 49 |
buffer.append(getUserNameWithoutContext(status.getUser().getName()) + "さんがリツイート。"); |
|
| 50 |
buffer.append(getUserNameWithoutContext(retweetedStatus.getUser().getName()) + "さんから、"); |
|
| 51 |
buffer.append(retweetedStatus.getText()); |
|
| 52 |
} else {
|
|
| 53 |
buffer.append(getUserNameWithoutContext(status.getUser().getName()) + "さんから、"); |
|
| 54 |
buffer.append(status.getText()); |
|
| 55 |
} |
|
| 56 |
|
|
| 57 |
mTalker.talkAsync(UrlUtil.convURLEmpty(buffer).replaceAll("\n", "。"), mYukkuriVoice);
|
|
| 58 |
|
|
| 59 |
// 読み上げは、霊夢と魔理沙が交互に行なう |
|
| 60 |
if (mYukkuriVoice == Talker.YukkuriVoice.REIMU) {
|
|
| 61 |
mYukkuriVoice = Talker.YukkuriVoice.MARISA; |
|
| 62 |
} else {
|
|
| 63 |
mYukkuriVoice = Talker.YukkuriVoice.REIMU; |
|
| 64 |
} |
|
| 65 |
|
|
| 66 |
} |
|
| 67 |
|
|
| 68 |
public void onDeletionNotice(StatusDeletionNotice sdn) {
|
|
| 69 |
System.err.println("onDeletionNotice.");
|
|
| 70 |
} |
|
| 71 |
|
|
| 72 |
public void onTrackLimitationNotice(int i) {
|
|
| 73 |
System.err.println("onTrackLimitationNotice.(" + i + ")");
|
|
| 74 |
} |
|
| 75 |
|
|
| 76 |
public void onScrubGeo(long lat, long lng) {
|
|
| 77 |
System.err.println("onScrubGeo.(" + lat + ", " + lng + ")");
|
|
| 78 |
} |
|
| 79 |
|
|
| 80 |
public void onException(Exception excptn) {
|
|
| 81 |
System.err.println("onException.");
|
|
| 82 |
} |
|
| 83 |
|
|
| 84 |
@Override |
|
| 85 |
public void onStallWarning(StallWarning arg0) {}
|
|
| 86 |
} |
|
| 87 |
|
|
| 88 |
} |
|
| src/com/mizo0203/timeline/talker/UrlUtil.java | ||
|---|---|---|
| 1 |
package com.mizo0203.timeline.talker; |
|
| 2 |
|
|
| 3 |
import java.util.regex.Matcher; |
|
| 4 |
import java.util.regex.Pattern; |
|
| 5 |
|
|
| 6 |
/** |
|
| 7 |
* http://chat-messenger.net/blog-entry-40.html |
|
| 8 |
*/ |
|
| 9 |
public class UrlUtil {
|
|
| 10 |
/** URLを抽出するための正規表現パターン */ |
|
| 11 |
private static final Pattern convURLLinkPtn = Pattern.compile( |
|
| 12 |
"(http://|https://){1}[\\w\\.\\-/:\\#\\?\\=\\&\\;\\%\\~\\+]+", Pattern.CASE_INSENSITIVE);
|
|
| 13 |
|
|
| 14 |
/** |
|
| 15 |
* 指定された文字列内のURLを、正規表現を使用し、 空文字列に変換する。 |
|
| 16 |
* |
|
| 17 |
* @param str 指定の文字列。 |
|
| 18 |
* @return リンクに変換された文字列。 |
|
| 19 |
*/ |
|
| 20 |
public static String convURLEmpty(CharSequence str) {
|
|
| 21 |
Matcher matcher = convURLLinkPtn.matcher(str); |
|
| 22 |
return matcher.replaceAll("");
|
|
| 23 |
} |
|
| 24 |
} |
|
| src/com/mizo0203/twitter/timeline/talker/Application.java | ||
|---|---|---|
| 1 |
package com.mizo0203.twitter.timeline.talker; |
|
| 2 |
|
|
| 3 |
import twitter4j.conf.Configuration; |
|
| 4 |
import twitter4j.conf.ConfigurationBuilder; |
|
| 5 |
|
|
| 6 |
/** |
|
| 7 |
* Java アプリケーション起動時に実行されるクラス |
|
| 8 |
* |
|
| 9 |
* @author みぞ@CrazyBeatCoder |
|
| 10 |
*/ |
|
| 11 |
public class Application {
|
|
| 12 |
|
|
| 13 |
public static void main(String[] args) {
|
|
| 14 |
TwitterTimelineTalker twitterTimelineTalker; |
|
| 15 |
Talker talker; |
|
| 16 |
|
|
| 17 |
try {
|
|
| 18 |
talker = new Talker(); |
|
| 19 |
twitterTimelineTalker = |
|
| 20 |
new TwitterTimelineTalker(new Arguments(args).twitterConfiguration, talker); |
|
| 21 |
} catch (IllegalArgumentException | IllegalStateException e) {
|
|
| 22 |
System.err.println(e.getMessage()); |
|
| 23 |
return; |
|
| 24 |
} |
|
| 25 |
|
|
| 26 |
twitterTimelineTalker.start(); |
|
| 27 |
talker.talkAsync("アプリケーションを起動しました", Talker.YukkuriVoice.REIMU);
|
|
| 28 |
} |
|
| 29 |
|
|
| 30 |
/** |
|
| 31 |
* Java アプリケーション起動時に指定する引数のデータクラス |
|
| 32 |
* |
|
| 33 |
* @author みぞ@CrazyBeatCoder |
|
| 34 |
*/ |
|
| 35 |
private static class Arguments {
|
|
| 36 |
|
|
| 37 |
private final Configuration twitterConfiguration; |
|
| 38 |
|
|
| 39 |
private Arguments(String[] args) throws IllegalArgumentException {
|
|
| 40 |
if (args.length < Argument.values().length) {
|
|
| 41 |
StringBuilder exceptionMessage = new StringBuilder(); |
|
| 42 |
exceptionMessage.append(Argument.values().length + " つの引数を指定してください。\n"); |
|
| 43 |
for (Argument arg : Argument.values()) {
|
|
| 44 |
exceptionMessage.append((arg.ordinal() + 1) + " つ目: " + arg.detail + "\n"); |
|
| 45 |
} |
|
| 46 |
throw new IllegalArgumentException(exceptionMessage.toString()); |
|
| 47 |
} |
|
| 48 |
|
|
| 49 |
String consumer_key = args[Argument.CONSUMER_KEY.ordinal()]; |
|
| 50 |
String consumer_secret = args[Argument.CONSUMER_SECRET.ordinal()]; |
|
| 51 |
String access_token = args[Argument.ACCESS_TOKEN.ordinal()]; |
|
| 52 |
String access_token_secret = args[Argument.ACCESS_TOKEN_SECRET.ordinal()]; |
|
| 53 |
|
|
| 54 |
twitterConfiguration = new ConfigurationBuilder().setOAuthConsumerKey(consumer_key) |
|
| 55 |
.setOAuthConsumerSecret(consumer_secret).setOAuthAccessToken(access_token) |
|
| 56 |
.setOAuthAccessTokenSecret(access_token_secret).build(); |
|
| 57 |
} |
|
| 58 |
|
|
| 59 |
} |
|
| 60 |
|
|
| 61 |
/** |
|
| 62 |
* Java アプリケーション起動時に指定する引数の定義 |
|
| 63 |
* |
|
| 64 |
* @author みぞ@CrazyBeatCoder |
|
| 65 |
*/ |
|
| 66 |
private enum Argument {
|
|
| 67 |
CONSUMER_KEY("Twitter Application's Consumer Key (API Key)"), //
|
|
| 68 |
CONSUMER_SECRET("Twitter Application's Consumer Secret (API Secret)"), //
|
|
| 69 |
ACCESS_TOKEN("Twitter Account's Access Token"), //
|
|
| 70 |
ACCESS_TOKEN_SECRET("Twitter Account's Access Token Secret"), //
|
|
| 71 |
; |
|
| 72 |
|
|
| 73 |
private final String detail; |
|
| 74 |
|
|
| 75 |
private Argument(String detail) {
|
|
| 76 |
this.detail = detail; |
|
| 77 |
} |
|
| 78 |
} |
|
| 79 |
} |
|
| src/com/mizo0203/twitter/timeline/talker/RuntimeUtil.java | ||
|---|---|---|
| 1 |
package com.mizo0203.twitter.timeline.talker; |
|
| 2 |
|
|
| 3 |
import java.io.IOException; |
|
| 4 |
|
|
| 5 |
public class RuntimeUtil {
|
|
| 6 |
|
|
| 7 |
public static void execute(String[] cmdarray) {
|
|
| 8 |
try {
|
|
| 9 |
Process process = Runtime.getRuntime().exec(cmdarray); |
|
| 10 |
process.waitFor(); |
|
| 11 |
process.destroy(); |
|
| 12 |
} catch (IOException | InterruptedException e) {
|
|
| 13 |
e.printStackTrace(); |
|
| 14 |
} |
|
| 15 |
} |
|
| 16 |
|
|
| 17 |
} |
|
| src/com/mizo0203/twitter/timeline/talker/Talker.java | ||
|---|---|---|
| 1 |
package com.mizo0203.twitter.timeline.talker; |
|
| 2 |
|
|
| 3 |
import java.io.BufferedWriter; |
|
| 4 |
import java.io.File; |
|
| 5 |
import java.io.FileWriter; |
|
| 6 |
import java.io.IOException; |
|
| 7 |
import java.io.PrintWriter; |
|
| 8 |
import java.util.concurrent.ExecutorService; |
|
| 9 |
import java.util.concurrent.Executors; |
|
| 10 |
|
|
| 11 |
public class Talker {
|
|
| 12 |
|
|
| 13 |
private static final String AQUESTALK_PI_PATH = "./aquestalkpi/AquesTalkPi"; |
|
| 14 |
|
|
| 15 |
private final ExecutorService mSingleThreadExecutor = Executors.newSingleThreadExecutor(); |
|
| 16 |
|
|
| 17 |
public Talker() throws IllegalStateException, SecurityException {
|
|
| 18 |
File file = new File(AQUESTALK_PI_PATH); |
|
| 19 |
if (!file.isFile()) {
|
|
| 20 |
throw new IllegalStateException(file.getPath() + " に AquesTalk Pi がありません。\n" |
|
| 21 |
+ "https://www.a-quest.com/products/aquestalkpi.html\n" + "からダウンロードしてください。"); |
|
| 22 |
} |
|
| 23 |
if (!file.canExecute()) {
|
|
| 24 |
throw new IllegalStateException(file.getPath() + " に実行権限がありません。"); |
|
| 25 |
} |
|
| 26 |
} |
|
| 27 |
|
|
| 28 |
public void talkAsync(final String text, final YukkuriVoice voice) {
|
|
| 29 |
mSingleThreadExecutor.submit(new Runnable() {
|
|
| 30 |
|
|
| 31 |
@Override |
|
| 32 |
public void run() {
|
|
| 33 |
try {
|
|
| 34 |
File file = new File("text.txt");
|
|
| 35 |
PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter(file))); |
|
| 36 |
pw.println(text); |
|
| 37 |
pw.flush(); |
|
| 38 |
pw.close(); |
|
| 39 |
RuntimeUtil.execute(new String[] {AQUESTALK_PI_PATH, "-v", voice.value, "-f", "text.txt",
|
|
| 40 |
"-o", "out.wav"}); |
|
| 41 |
RuntimeUtil.execute(new String[] {"sh", "-c", "aplay < out.wav"}); // 起動コマンドを指定する
|
|
| 42 |
Thread.sleep(2000); |
|
| 43 |
} catch (IOException | InterruptedException e) {
|
|
| 44 |
e.printStackTrace(); |
|
| 45 |
} |
|
| 46 |
} |
|
| 47 |
|
|
| 48 |
}); |
|
| 49 |
} |
|
| 50 |
|
|
| 51 |
public static enum YukkuriVoice {
|
|
| 52 |
|
|
| 53 |
/** |
|
| 54 |
* ゆっくりボイス - 霊夢 |
|
| 55 |
*/ |
|
| 56 |
REIMU("f1"), //
|
|
| 57 |
|
|
| 58 |
/** |
|
| 59 |
* ゆっくりボイス - 魔理沙 |
|
| 60 |
*/ |
|
| 61 |
MARISA("f2"), //
|
|
| 62 |
; |
|
| 63 |
|
|
| 64 |
private final String value; |
|
| 65 |
|
|
| 66 |
private YukkuriVoice(String value) {
|
|
| 67 |
this.value = value; |
|
| 68 |
} |
|
| 69 |
} |
|
| 70 |
|
|
| 71 |
} |
|
| src/com/mizo0203/twitter/timeline/talker/TwitterTimelineTalker.java | ||
|---|---|---|
| 1 |
package com.mizo0203.twitter.timeline.talker; |
|
| 2 |
|
|
| 3 |
import java.util.Locale; |
|
| 4 |
import java.util.regex.Matcher; |
|
| 5 |
import java.util.regex.Pattern; |
|
| 6 |
import twitter4j.StallWarning; |
|
| 7 |
import twitter4j.Status; |
|
| 8 |
import twitter4j.StatusDeletionNotice; |
|
| 9 |
import twitter4j.StatusListener; |
|
| 10 |
import twitter4j.TwitterStream; |
|
| 11 |
import twitter4j.TwitterStreamFactory; |
|
| 12 |
import twitter4j.conf.Configuration; |
|
| 13 |
|
|
| 14 |
public class TwitterTimelineTalker {
|
|
| 15 |
|
|
| 16 |
/** |
|
| 17 |
* ISO 639 言語コード - 日本語 (ja) |
|
| 18 |
*/ |
|
| 19 |
public static final String LANG_JA = Locale.JAPAN.getLanguage(); |
|
| 20 |
|
|
| 21 |
private Talker.YukkuriVoice mYukkuriVoice = Talker.YukkuriVoice.REIMU; |
|
| 22 |
private final TwitterStream mTwitterStream; |
|
| 23 |
private final Talker mTalker; |
|
| 24 |
|
|
| 25 |
public TwitterTimelineTalker(Configuration configuration, Talker talker) {
|
|
| 26 |
mTwitterStream = new TwitterStreamFactory(configuration).getInstance(); |
|
| 27 |
mTwitterStream.addListener(new OnStatusEvent()); |
|
| 28 |
mTalker = talker; |
|
| 29 |
} |
|
| 30 |
|
|
| 31 |
public void start() {
|
|
| 32 |
// OnStatusEvent に Twitter タイムラインが通知される |
|
| 33 |
mTwitterStream.user(); |
|
| 34 |
} |
|
| 35 |
|
|
| 36 |
private static String getUserNameWithoutContext(String name) {
|
|
| 37 |
Pattern p = Pattern.compile("([^@@]+).+");
|
|
| 38 |
Matcher m = p.matcher(name); |
|
| 39 |
return m.replaceFirst("$1");
|
|
| 40 |
} |
|
| 41 |
|
|
| 42 |
private class OnStatusEvent implements StatusListener {
|
|
| 43 |
|
|
| 44 |
public void onStatus(final Status status) {
|
|
| 45 |
if (!LANG_JA.equalsIgnoreCase(status.getLang())) {
|
|
| 46 |
return; |
|
| 47 |
} |
|
| 48 |
|
|
| 49 |
final StringBuffer buffer = new StringBuffer(); |
|
| 50 |
|
|
| 51 |
if (status.isRetweet()) {
|
|
| 52 |
Status retweetedStatus = status.getRetweetedStatus(); |
|
| 53 |
buffer.append(getUserNameWithoutContext(status.getUser().getName()) + "さんがリツイート。"); |
|
| 54 |
buffer.append(getUserNameWithoutContext(retweetedStatus.getUser().getName()) + "さんから、"); |
|
| 55 |
buffer.append(retweetedStatus.getText()); |
|
| 56 |
} else {
|
|
| 57 |
buffer.append(getUserNameWithoutContext(status.getUser().getName()) + "さんから、"); |
|
| 58 |
buffer.append(status.getText()); |
|
| 59 |
} |
|
| 60 |
|
|
| 61 |
mTalker.talkAsync(UrlUtil.convURLEmpty(buffer).replaceAll("\n", "。"), mYukkuriVoice);
|
|
| 62 |
|
|
| 63 |
// 読み上げは、霊夢と魔理沙が交互に行なう |
|
| 64 |
if (mYukkuriVoice == Talker.YukkuriVoice.REIMU) {
|
|
| 65 |
mYukkuriVoice = Talker.YukkuriVoice.MARISA; |
|
| 66 |
} else {
|
|
| 67 |
mYukkuriVoice = Talker.YukkuriVoice.REIMU; |
|
| 68 |
} |
|
| 69 |
|
|
| 70 |
} |
|
| 71 |
|
|
| 72 |
public void onDeletionNotice(StatusDeletionNotice sdn) {
|
|
| 73 |
System.err.println("onDeletionNotice.");
|
|
| 74 |
} |
|
| 75 |
|
|
| 76 |
public void onTrackLimitationNotice(int i) {
|
|
| 77 |
System.err.println("onTrackLimitationNotice.(" + i + ")");
|
|
| 78 |
} |
|
| 79 |
|
|
| 80 |
public void onScrubGeo(long lat, long lng) {
|
|
| 81 |
System.err.println("onScrubGeo.(" + lat + ", " + lng + ")");
|
|
| 82 |
} |
|
| 83 |
|
|
| 84 |
public void onException(Exception excptn) {
|
|
| 85 |
System.err.println("onException.");
|
|
| 86 |
} |
|
| 87 |
|
|
| 88 |
@Override |
|
| 89 |
public void onStallWarning(StallWarning arg0) {}
|
|
| 90 |
} |
|
| 91 |
|
|
| 92 |
} |
|
| src/com/mizo0203/twitter/timeline/talker/UrlUtil.java | ||
|---|---|---|
| 1 |
package com.mizo0203.twitter.timeline.talker; |
|
| 2 |
|
|
| 3 |
import java.util.regex.Matcher; |
|
| 4 |
import java.util.regex.Pattern; |
|
| 5 |
|
|
| 6 |
/** |
|
| 7 |
* http://chat-messenger.net/blog-entry-40.html |
|
| 8 |
*/ |
|
| 9 |
public class UrlUtil {
|
|
| 10 |
/** URLを抽出するための正規表現パターン */ |
|
| 11 |
private static final Pattern convURLLinkPtn = Pattern.compile( |
|
| 12 |
"(http://|https://){1}[\\w\\.\\-/:\\#\\?\\=\\&\\;\\%\\~\\+]+", Pattern.CASE_INSENSITIVE);
|
|
| 13 |
|
|
| 14 |
/** |
|
| 15 |
* 指定された文字列内のURLを、正規表現を使用し、 空文字列に変換する。 |
|
| 16 |
* |
|
| 17 |
* @param str 指定の文字列。 |
|
| 18 |
* @return リンクに変換された文字列。 |
|
| 19 |
*/ |
|
| 20 |
public static String convURLEmpty(CharSequence str) {
|
|
| 21 |
Matcher matcher = convURLLinkPtn.matcher(str); |
|
| 22 |
return matcher.replaceAll("");
|
|
| 23 |
} |
|
| 24 |
} |
|
他の形式にエクスポート: Unified diff
Change application name from TwitterTimelineTalker to TimelineTalker.