リビジョン 6a3fcb6d
みぞ @mizo0203 さんが7年以上前に追加
| .idea/artifacts/TimelineTalker_jar.xml | ||
|---|---|---|
| 2 | 2 |
<artifact type="jar" name="TimelineTalker:jar"> |
| 3 | 3 |
<output-path>$PROJECT_DIR$/out/artifacts/TimelineTalker_jar</output-path> |
| 4 | 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="/" /> |
|
| 5 |
<element id="module-output" name="TimelineTalker" /> |
|
| 6 |
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/com/google/code/gson/gson/2.8.0/gson-2.8.0.jar" path-in-jar="/" /> |
|
| 7 |
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/io/reactivex/rxjava2/rxjava/2.0.8/rxjava-2.0.8.jar" path-in-jar="/" /> |
|
| 8 |
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.2.0/kotlin-stdlib-1.2.0.jar" path-in-jar="/" /> |
|
| 9 |
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/com/github/sys1yagi/mastodon4j/mastodon4j/1.6.0/mastodon4j-1.6.0.jar" path-in-jar="/" /> |
|
| 10 |
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jre7/1.2.0/kotlin-stdlib-jre7-1.2.0.jar" path-in-jar="/" /> |
|
| 11 |
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar" path-in-jar="/" /> |
|
| 12 |
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/com/github/sys1yagi/mastodon4j/mastodon4j-rx/1.6.0/mastodon4j-rx-1.6.0.jar" path-in-jar="/" /> |
|
| 13 |
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/com/squareup/okio/okio/1.11.0/okio-1.11.0.jar" path-in-jar="/" /> |
|
| 14 |
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/reactivestreams/reactive-streams/1.0.0/reactive-streams-1.0.0.jar" path-in-jar="/" /> |
|
| 15 |
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/twitter4j/twitter4j-core/4.0.7/twitter4j-core-4.0.7.jar" path-in-jar="/" /> |
|
| 16 |
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/commons-io/commons-io/2.6/commons-io-2.6.jar" path-in-jar="/" /> |
|
| 17 |
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/com/squareup/okhttp3/okhttp/3.6.0/okhttp-3.6.0.jar" path-in-jar="/" /> |
|
| 8 | 18 |
</root> |
| 9 | 19 |
</artifact> |
| 10 | 20 |
</component> |
| TimelineTalker.iml | ||
|---|---|---|
| 9 | 9 |
</content> |
| 10 | 10 |
<orderEntry type="inheritedJdk" /> |
| 11 | 11 |
<orderEntry type="sourceFolder" forTests="false" /> |
| 12 |
<orderEntry type="library" name="Maven: org.twitter4j:twitter4j-core:4.0.6" level="project" /> |
|
| 13 | 12 |
<orderEntry type="library" name="Maven: org.twitter4j:twitter4j-core:4.0.7" level="project" /> |
| 13 |
<orderEntry type="library" name="Maven: com.github.sys1yagi.mastodon4j:mastodon4j:1.6.0" level="project" /> |
|
| 14 |
<orderEntry type="library" name="Maven: org.jetbrains.kotlin:kotlin-stdlib-jre7:1.2.0" level="project" /> |
|
| 15 |
<orderEntry type="library" name="Maven: org.jetbrains.kotlin:kotlin-stdlib:1.2.0" level="project" /> |
|
| 16 |
<orderEntry type="library" name="Maven: org.jetbrains:annotations:13.0" level="project" /> |
|
| 17 |
<orderEntry type="library" name="Maven: com.squareup.okhttp3:okhttp:3.6.0" level="project" /> |
|
| 18 |
<orderEntry type="library" name="Maven: com.squareup.okio:okio:1.11.0" level="project" /> |
|
| 19 |
<orderEntry type="library" name="Maven: com.google.code.gson:gson:2.8.0" level="project" /> |
|
| 20 |
<orderEntry type="library" name="Maven: com.github.sys1yagi.mastodon4j:mastodon4j-rx:1.6.0" level="project" /> |
|
| 21 |
<orderEntry type="library" name="Maven: io.reactivex.rxjava2:rxjava:2.0.8" level="project" /> |
|
| 22 |
<orderEntry type="library" name="Maven: org.reactivestreams:reactive-streams:1.0.0" level="project" /> |
|
| 23 |
<orderEntry type="library" name="Maven: commons-io:commons-io:2.6" level="project" /> |
|
| 14 | 24 |
</component> |
| 15 | 25 |
</module> |
| pom.xml | ||
|---|---|---|
| 1 |
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
| 2 |
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
|
| 3 |
<modelVersion>4.0.0</modelVersion>
|
|
| 1 |
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
|
|
| 2 |
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
|
| 3 |
<modelVersion>4.0.0</modelVersion>
|
|
| 4 | 4 |
|
| 5 |
<groupId>com.mizo0203</groupId>
|
|
| 6 |
<artifactId>TimelineTalker</artifactId>
|
|
| 7 |
<version>0.0.1-SNAPSHOT</version>
|
|
| 8 |
<packaging>jar</packaging>
|
|
| 5 |
<groupId>com.mizo0203</groupId>
|
|
| 6 |
<artifactId>TimelineTalker</artifactId>
|
|
| 7 |
<version>0.0.1-SNAPSHOT</version>
|
|
| 8 |
<packaging>jar</packaging>
|
|
| 9 | 9 |
|
| 10 |
<properties>
|
|
| 11 |
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
|
| 12 |
</properties>
|
|
| 10 |
<properties>
|
|
| 11 |
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
|
| 12 |
</properties>
|
|
| 13 | 13 |
|
| 14 |
<build> |
|
| 15 |
<sourceDirectory>src</sourceDirectory> |
|
| 16 |
<outputDirectory>target/classes</outputDirectory> |
|
| 17 |
<resources> |
|
| 18 |
<resource> |
|
| 19 |
<directory>src</directory> |
|
| 20 |
<excludes> |
|
| 21 |
<exclude>**/*.java</exclude> |
|
| 22 |
</excludes> |
|
| 23 |
</resource> |
|
| 24 |
</resources> |
|
| 25 |
<plugins> |
|
| 26 |
<plugin> |
|
| 27 |
<artifactId>maven-compiler-plugin</artifactId> |
|
| 28 |
<version>3.1</version> |
|
| 29 |
<configuration> |
|
| 30 |
<source>1.7</source> |
|
| 31 |
<target>1.7</target> |
|
| 32 |
</configuration> |
|
| 33 |
</plugin> |
|
| 34 |
</plugins> |
|
| 35 |
</build> |
|
| 14 |
<repositories> |
|
| 15 |
<repository> |
|
| 16 |
<id>jitpack.io</id> |
|
| 17 |
<url>https://jitpack.io</url> |
|
| 18 |
</repository> |
|
| 19 |
</repositories> |
|
| 36 | 20 |
|
| 37 |
<dependencies> |
|
| 38 |
<dependency> |
|
| 39 |
<groupId>org.twitter4j</groupId> |
|
| 40 |
<artifactId>twitter4j-core</artifactId> |
|
| 41 |
<version>[4.0,)</version> |
|
| 42 |
</dependency> |
|
| 43 |
</dependencies> |
|
| 21 |
<build> |
|
| 22 |
<sourceDirectory>src</sourceDirectory> |
|
| 23 |
<outputDirectory>target/classes</outputDirectory> |
|
| 24 |
<resources> |
|
| 25 |
<resource> |
|
| 26 |
<directory>src</directory> |
|
| 27 |
<excludes> |
|
| 28 |
<exclude>**/*.java</exclude> |
|
| 29 |
</excludes> |
|
| 30 |
</resource> |
|
| 31 |
</resources> |
|
| 32 |
<plugins> |
|
| 33 |
<plugin> |
|
| 34 |
<artifactId>maven-compiler-plugin</artifactId> |
|
| 35 |
<version>3.1</version> |
|
| 36 |
<configuration> |
|
| 37 |
<source>1.7</source> |
|
| 38 |
<target>1.7</target> |
|
| 39 |
</configuration> |
|
| 40 |
</plugin> |
|
| 41 |
</plugins> |
|
| 42 |
</build> |
|
| 43 |
|
|
| 44 |
<dependencies> |
|
| 45 |
<dependency> |
|
| 46 |
<groupId>org.twitter4j</groupId> |
|
| 47 |
<artifactId>twitter4j-core</artifactId> |
|
| 48 |
<version>[4.0,)</version> |
|
| 49 |
</dependency> |
|
| 50 |
<dependency> |
|
| 51 |
<groupId>com.github.sys1yagi.mastodon4j</groupId> |
|
| 52 |
<artifactId>mastodon4j</artifactId> |
|
| 53 |
<version>[1.6,)</version> |
|
| 54 |
</dependency> |
|
| 55 |
<dependency> |
|
| 56 |
<groupId>com.github.sys1yagi.mastodon4j</groupId> |
|
| 57 |
<artifactId>mastodon4j-rx</artifactId> |
|
| 58 |
<version>[1.6,)</version> |
|
| 59 |
</dependency> |
|
| 60 |
|
|
| 61 |
<!-- https://mvnrepository.com/artifact/commons-io/commons-io --> |
|
| 62 |
<dependency> |
|
| 63 |
<groupId>commons-io</groupId> |
|
| 64 |
<artifactId>commons-io</artifactId> |
|
| 65 |
<version>2.6</version> |
|
| 66 |
</dependency> |
|
| 67 |
|
|
| 68 |
</dependencies> |
|
| 44 | 69 |
</project> |
| src/META-INF/MANIFEST.MF | ||
|---|---|---|
| 1 | 1 |
Manifest-Version: 1.0 |
| 2 | 2 |
Implementation-Title: TimelineTalker |
| 3 | 3 |
Implementation-Version: 1.1 |
| 4 |
Main-Class: com.mizo0203.timeline.talker.Application
|
|
| 4 |
Main-Class: com.mizo0203.timeline.talker.Main
|
|
| 5 | 5 |
|
| 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/Arguments.java | ||
|---|---|---|
| 1 |
package com.mizo0203.timeline.talker; |
|
| 2 |
|
|
| 3 |
import com.google.gson.Gson; |
|
| 4 |
import com.sys1yagi.mastodon4j.MastodonClient; |
|
| 5 |
import okhttp3.OkHttpClient; |
|
| 6 |
import org.jetbrains.annotations.NotNull; |
|
| 7 |
import org.jetbrains.annotations.Nullable; |
|
| 8 |
import twitter4j.conf.Configuration; |
|
| 9 |
import twitter4j.conf.ConfigurationBuilder; |
|
| 10 |
|
|
| 11 |
/** |
|
| 12 |
* Java アプリケーション起動時に指定する引数のデータクラス |
|
| 13 |
* |
|
| 14 |
* @author みぞ@CrazyBeatCoder |
|
| 15 |
*/ |
|
| 16 |
/* package */ class Arguments {
|
|
| 17 |
|
|
| 18 |
@Nullable /* package */ final Configuration twitterConfiguration; |
|
| 19 |
@Nullable /* package */ final MastodonClient mastodonClient; |
|
| 20 |
|
|
| 21 |
/* package */ Arguments(String[] args) throws IllegalArgumentException {
|
|
| 22 |
if (args.length == Argument.values().length) {
|
|
| 23 |
twitterConfiguration = |
|
| 24 |
createTwitterConfiguration( |
|
| 25 |
args[Argument.TWITTER_CONSUMER_KEY.ordinal()], |
|
| 26 |
args[Argument.TWITTER_CONSUMER_SECRET.ordinal()], |
|
| 27 |
args[Argument.TWITTER_ACCESS_TOKEN.ordinal()], |
|
| 28 |
args[Argument.TWITTER_ACCESS_TOKEN_SECRET.ordinal()]); |
|
| 29 |
mastodonClient = |
|
| 30 |
createMastodonClient( |
|
| 31 |
args[Argument.MASTODON_INSTANCE_NAME.ordinal()], |
|
| 32 |
args[Argument.MASTODON_ACCESS_TOKEN.ordinal()]); |
|
| 33 |
} else if (args.length == Argument.Twitter.values().length) {
|
|
| 34 |
twitterConfiguration = |
|
| 35 |
createTwitterConfiguration( |
|
| 36 |
args[Argument.Twitter.CONSUMER_KEY.ordinal()], |
|
| 37 |
args[Argument.Twitter.CONSUMER_SECRET.ordinal()], |
|
| 38 |
args[Argument.Twitter.ACCESS_TOKEN.ordinal()], |
|
| 39 |
args[Argument.Twitter.ACCESS_TOKEN_SECRET.ordinal()]); |
|
| 40 |
mastodonClient = null; |
|
| 41 |
} else if (args.length == Argument.Mastodon.values().length) {
|
|
| 42 |
twitterConfiguration = null; |
|
| 43 |
mastodonClient = |
|
| 44 |
createMastodonClient( |
|
| 45 |
args[Argument.Mastodon.INSTANCE_NAME.ordinal()], |
|
| 46 |
args[Argument.Mastodon.ACCESS_TOKEN.ordinal()]); |
|
| 47 |
} else {
|
|
| 48 |
throw createIllegalArgumentException(); |
|
| 49 |
} |
|
| 50 |
} |
|
| 51 |
|
|
| 52 |
private static Configuration createTwitterConfiguration( |
|
| 53 |
String twitterConsumerKey, |
|
| 54 |
String twitterConsumerSecret, |
|
| 55 |
String twitterAccessToken, |
|
| 56 |
String twitterAccessTokenSecret) {
|
|
| 57 |
return new ConfigurationBuilder() |
|
| 58 |
.setOAuthConsumerKey(twitterConsumerKey) |
|
| 59 |
.setOAuthConsumerSecret(twitterConsumerSecret) |
|
| 60 |
.setOAuthAccessToken(twitterAccessToken) |
|
| 61 |
.setOAuthAccessTokenSecret(twitterAccessTokenSecret) |
|
| 62 |
.build(); |
|
| 63 |
} |
|
| 64 |
|
|
| 65 |
private static MastodonClient createMastodonClient( |
|
| 66 |
String mastodonInstanceName, String mastodonAccessToken) {
|
|
| 67 |
return new MastodonClient.Builder(mastodonInstanceName, new OkHttpClient.Builder(), new Gson()) |
|
| 68 |
.accessToken(mastodonAccessToken) |
|
| 69 |
.useStreamingApi() |
|
| 70 |
.build(); |
|
| 71 |
} |
|
| 72 |
|
|
| 73 |
private static IllegalArgumentException createIllegalArgumentException() {
|
|
| 74 |
return new IllegalArgumentException( |
|
| 75 |
Argument.createExceptionMessage() |
|
| 76 |
+ "\n" |
|
| 77 |
+ Argument.Twitter.createExceptionMessage() |
|
| 78 |
+ "\n" |
|
| 79 |
+ Argument.Mastodon.createExceptionMessage()); |
|
| 80 |
} |
|
| 81 |
|
|
| 82 |
/** |
|
| 83 |
* Java アプリケーション起動時に指定する引数の定義 |
|
| 84 |
* |
|
| 85 |
* @author みぞ@CrazyBeatCoder |
|
| 86 |
*/ |
|
| 87 |
private enum Argument {
|
|
| 88 |
TWITTER_CONSUMER_KEY, // |
|
| 89 |
TWITTER_CONSUMER_SECRET, // |
|
| 90 |
TWITTER_ACCESS_TOKEN, // |
|
| 91 |
TWITTER_ACCESS_TOKEN_SECRET, // |
|
| 92 |
MASTODON_INSTANCE_NAME, // |
|
| 93 |
MASTODON_ACCESS_TOKEN, // |
|
| 94 |
; |
|
| 95 |
|
|
| 96 |
@NotNull |
|
| 97 |
private static String createExceptionMessage() {
|
|
| 98 |
StringBuilder exceptionMessage = |
|
| 99 |
new StringBuilder("Twitter と Mastodon の両方を読み上げる場合、 ")
|
|
| 100 |
.append(values().length) |
|
| 101 |
.append(" つの引数を指定してください。\n");
|
|
| 102 |
for (Argument arg : values()) {
|
|
| 103 |
exceptionMessage |
|
| 104 |
.append(arg.ordinal() + 1) |
|
| 105 |
.append(" つ目: ")
|
|
| 106 |
.append(arg.getDetail()) |
|
| 107 |
.append("\n");
|
|
| 108 |
} |
|
| 109 |
|
|
| 110 |
return exceptionMessage.toString(); |
|
| 111 |
} |
|
| 112 |
|
|
| 113 |
@NotNull |
|
| 114 |
private String getDetail() throws IllegalStateException {
|
|
| 115 |
switch (this) {
|
|
| 116 |
case TWITTER_CONSUMER_KEY: |
|
| 117 |
return "Twitter Application's Consumer Key (API Key)"; |
|
| 118 |
case TWITTER_CONSUMER_SECRET: |
|
| 119 |
return "Twitter Application's Consumer Secret (API Secret)"; |
|
| 120 |
case TWITTER_ACCESS_TOKEN: |
|
| 121 |
return "Twitter Account's Access Token"; |
|
| 122 |
case TWITTER_ACCESS_TOKEN_SECRET: |
|
| 123 |
return "Twitter Account's Access Token Secret"; |
|
| 124 |
case MASTODON_INSTANCE_NAME: |
|
| 125 |
return "Mastodon Instance Name"; |
|
| 126 |
case MASTODON_ACCESS_TOKEN: |
|
| 127 |
return "Mastodon Account's Access Token"; |
|
| 128 |
default: |
|
| 129 |
throw new IllegalStateException("getDetail this: " + this);
|
|
| 130 |
} |
|
| 131 |
} |
|
| 132 |
|
|
| 133 |
private enum Twitter {
|
|
| 134 |
CONSUMER_KEY, // |
|
| 135 |
CONSUMER_SECRET, // |
|
| 136 |
ACCESS_TOKEN, // |
|
| 137 |
ACCESS_TOKEN_SECRET, // |
|
| 138 |
; |
|
| 139 |
|
|
| 140 |
@NotNull |
|
| 141 |
private static String createExceptionMessage() {
|
|
| 142 |
StringBuilder exceptionMessage = |
|
| 143 |
new StringBuilder("Twitter のみを読み上げる場合、 ")
|
|
| 144 |
.append(values().length) |
|
| 145 |
.append(" つの引数を指定してください。\n");
|
|
| 146 |
for (Argument.Twitter arg : values()) {
|
|
| 147 |
exceptionMessage |
|
| 148 |
.append(arg.ordinal() + 1) |
|
| 149 |
.append(" つ目: ")
|
|
| 150 |
.append(arg.getDetail()) |
|
| 151 |
.append("\n");
|
|
| 152 |
} |
|
| 153 |
return exceptionMessage.toString(); |
|
| 154 |
} |
|
| 155 |
|
|
| 156 |
@NotNull |
|
| 157 |
private String getDetail() throws IllegalStateException {
|
|
| 158 |
switch (this) {
|
|
| 159 |
case CONSUMER_KEY: |
|
| 160 |
return TWITTER_CONSUMER_KEY.getDetail(); |
|
| 161 |
case CONSUMER_SECRET: |
|
| 162 |
return TWITTER_CONSUMER_SECRET.getDetail(); |
|
| 163 |
case ACCESS_TOKEN: |
|
| 164 |
return TWITTER_ACCESS_TOKEN.getDetail(); |
|
| 165 |
case ACCESS_TOKEN_SECRET: |
|
| 166 |
return TWITTER_ACCESS_TOKEN_SECRET.getDetail(); |
|
| 167 |
default: |
|
| 168 |
throw new IllegalStateException("getDetail this: " + this);
|
|
| 169 |
} |
|
| 170 |
} |
|
| 171 |
} |
|
| 172 |
|
|
| 173 |
private enum Mastodon {
|
|
| 174 |
INSTANCE_NAME, // |
|
| 175 |
ACCESS_TOKEN, // |
|
| 176 |
; |
|
| 177 |
|
|
| 178 |
@NotNull |
|
| 179 |
private static String createExceptionMessage() {
|
|
| 180 |
StringBuilder exceptionMessage = |
|
| 181 |
new StringBuilder("Mastodon のみを読み上げる場合、 ")
|
|
| 182 |
.append(values().length) |
|
| 183 |
.append(" つの引数を指定してください。\n");
|
|
| 184 |
for (Argument.Mastodon arg : values()) {
|
|
| 185 |
exceptionMessage |
|
| 186 |
.append(arg.ordinal() + 1) |
|
| 187 |
.append(" つ目: ")
|
|
| 188 |
.append(arg.getDetail()) |
|
| 189 |
.append("\n");
|
|
| 190 |
} |
|
| 191 |
return exceptionMessage.toString(); |
|
| 192 |
} |
|
| 193 |
|
|
| 194 |
@NotNull |
|
| 195 |
private String getDetail() throws IllegalStateException {
|
|
| 196 |
switch (this) {
|
|
| 197 |
case INSTANCE_NAME: |
|
| 198 |
return MASTODON_INSTANCE_NAME.getDetail(); |
|
| 199 |
case ACCESS_TOKEN: |
|
| 200 |
return MASTODON_ACCESS_TOKEN.getDetail(); |
|
| 201 |
default: |
|
| 202 |
throw new IllegalStateException("getDetail this: " + this);
|
|
| 203 |
} |
|
| 204 |
} |
|
| 205 |
} |
|
| 206 |
} |
|
| 207 |
} |
|
| src/com/mizo0203/timeline/talker/Main.java | ||
|---|---|---|
| 1 |
package com.mizo0203.timeline.talker; |
|
| 2 |
|
|
| 3 |
import org.jetbrains.annotations.NotNull; |
|
| 4 |
|
|
| 5 |
/** |
|
| 6 |
* Java アプリケーション起動時に実行されるクラス |
|
| 7 |
* |
|
| 8 |
* @author みぞ@CrazyBeatCoder |
|
| 9 |
*/ |
|
| 10 |
public class Main {
|
|
| 11 |
|
|
| 12 |
public static void main(@NotNull String[] args) {
|
|
| 13 |
TimelineTalker twitterTimelineTalker; |
|
| 14 |
TimelineTalker mastodonTimelineTalker; |
|
| 15 |
Talker talker; |
|
| 16 |
|
|
| 17 |
try {
|
|
| 18 |
Arguments arguments = new Arguments(args); |
|
| 19 |
talker = new Talker(); |
|
| 20 |
|
|
| 21 |
if (arguments.twitterConfiguration != null) {
|
|
| 22 |
twitterTimelineTalker = new TwitterTimelineTalker(arguments.twitterConfiguration, talker); |
|
| 23 |
twitterTimelineTalker.start(); |
|
| 24 |
} |
|
| 25 |
|
|
| 26 |
if (arguments.mastodonClient != null) {
|
|
| 27 |
mastodonTimelineTalker = new MastodonTimelineTalker(arguments.mastodonClient, talker); |
|
| 28 |
mastodonTimelineTalker.start(); |
|
| 29 |
} |
|
| 30 |
|
|
| 31 |
} catch (@NotNull IllegalArgumentException | IllegalStateException e) {
|
|
| 32 |
System.err.println(e.getMessage()); |
|
| 33 |
return; |
|
| 34 |
} |
|
| 35 |
|
|
| 36 |
talker.talkAlternatelyAsync("アプリケーションを起動しました");
|
|
| 37 |
} |
|
| 38 |
} |
|
| src/com/mizo0203/timeline/talker/MastodonTimelineTalker.java | ||
|---|---|---|
| 1 |
package com.mizo0203.timeline.talker; |
|
| 2 |
|
|
| 3 |
import com.mizo0203.timeline.talker.util.DisplayNameUtil; |
|
| 4 |
import com.mizo0203.timeline.talker.util.HTMLParser; |
|
| 5 |
import com.mizo0203.timeline.talker.util.UrlUtil; |
|
| 6 |
import com.sys1yagi.mastodon4j.MastodonClient; |
|
| 7 |
import com.sys1yagi.mastodon4j.api.Handler; |
|
| 8 |
import com.sys1yagi.mastodon4j.api.entity.Account; |
|
| 9 |
import com.sys1yagi.mastodon4j.api.entity.Notification; |
|
| 10 |
import com.sys1yagi.mastodon4j.api.entity.Status; |
|
| 11 |
import com.sys1yagi.mastodon4j.api.method.Streaming; |
|
| 12 |
import org.jetbrains.annotations.NotNull; |
|
| 13 |
|
|
| 14 |
import java.io.IOException; |
|
| 15 |
import java.nio.charset.StandardCharsets; |
|
| 16 |
|
|
| 17 |
public class MastodonTimelineTalker implements TimelineTalker {
|
|
| 18 |
|
|
| 19 |
@NotNull private final Streaming mStreaming; |
|
| 20 |
@NotNull private final OnStatusEvent mOnStatusEvent; |
|
| 21 |
|
|
| 22 |
/* package */ MastodonTimelineTalker(MastodonClient client, Talker talker) {
|
|
| 23 |
mStreaming = new Streaming(client); |
|
| 24 |
mOnStatusEvent = new OnStatusEvent(talker); |
|
| 25 |
} |
|
| 26 |
|
|
| 27 |
@Override |
|
| 28 |
public void start() {
|
|
| 29 |
|
|
| 30 |
try {
|
|
| 31 |
mStreaming.user(mOnStatusEvent); |
|
| 32 |
} catch (Exception e) {
|
|
| 33 |
e.printStackTrace(); |
|
| 34 |
} |
|
| 35 |
} |
|
| 36 |
|
|
| 37 |
private static class OnStatusEvent implements Handler {
|
|
| 38 |
|
|
| 39 |
private final Talker mTalker; |
|
| 40 |
|
|
| 41 |
private OnStatusEvent(Talker talker) {
|
|
| 42 |
mTalker = talker; |
|
| 43 |
} |
|
| 44 |
|
|
| 45 |
@Override |
|
| 46 |
public void onStatus(@NotNull Status status) {
|
|
| 47 |
|
|
| 48 |
try {
|
|
| 49 |
final StringBuffer buffer = new StringBuffer(); |
|
| 50 |
final Status reblogStatus = status.getReblog(); |
|
| 51 |
|
|
| 52 |
String displayName = "誰か"; |
|
| 53 |
if (status.getAccount() != null) {
|
|
| 54 |
displayName = status.getAccount().getDisplayName(); |
|
| 55 |
} |
|
| 56 |
if (reblogStatus != null) {
|
|
| 57 |
String reblogDisplayName = "誰か"; |
|
| 58 |
if (status.getAccount() != null) {
|
|
| 59 |
displayName = status.getAccount().getDisplayName(); |
|
| 60 |
} |
|
| 61 |
buffer.append(DisplayNameUtil.removeContext(displayName)).append("さんがブースト。");
|
|
| 62 |
buffer.append(DisplayNameUtil.removeContext(reblogDisplayName)).append("さんから、");
|
|
| 63 |
buffer.append( |
|
| 64 |
new HTMLParser().parse(reblogStatus.getContent(), StandardCharsets.UTF_8, true)); |
|
| 65 |
} else {
|
|
| 66 |
buffer.append(DisplayNameUtil.removeContext(displayName)).append("さんから、");
|
|
| 67 |
buffer.append(new HTMLParser().parse(status.getContent(), StandardCharsets.UTF_8, true)); |
|
| 68 |
} |
|
| 69 |
|
|
| 70 |
final String talkText = UrlUtil.convURLEmpty(buffer).replaceAll("\n", "。");
|
|
| 71 |
mTalker.talkAlternatelyAsync(talkText); |
|
| 72 |
} catch (IOException e) {
|
|
| 73 |
e.printStackTrace(); |
|
| 74 |
} |
|
| 75 |
} |
|
| 76 |
|
|
| 77 |
@Override |
|
| 78 |
public void onNotification(@NotNull Notification notification) {
|
|
| 79 |
final StringBuilder buffer = new StringBuilder(); |
|
| 80 |
final Account account = notification.getAccount(); |
|
| 81 |
|
|
| 82 |
if (account == null) {
|
|
| 83 |
return; |
|
| 84 |
} |
|
| 85 |
|
|
| 86 |
switch (notification.getType()) {
|
|
| 87 |
case "mention": |
|
| 88 |
buffer |
|
| 89 |
.append(DisplayNameUtil.removeContext(account.getDisplayName())) |
|
| 90 |
.append("さんがあなたをメンションしました。");
|
|
| 91 |
break; |
|
| 92 |
case "reblog": |
|
| 93 |
buffer |
|
| 94 |
.append(DisplayNameUtil.removeContext(account.getDisplayName())) |
|
| 95 |
.append("さんがあなたのトゥートをブーストしました。");
|
|
| 96 |
break; |
|
| 97 |
case "favourite": |
|
| 98 |
buffer |
|
| 99 |
.append(DisplayNameUtil.removeContext(account.getDisplayName())) |
|
| 100 |
.append("さんがあなたのトゥートをお気に入りに登録しました。");
|
|
| 101 |
break; |
|
| 102 |
case "follow": |
|
| 103 |
buffer |
|
| 104 |
.append(DisplayNameUtil.removeContext(account.getDisplayName())) |
|
| 105 |
.append("さんにフォローされました。");
|
|
| 106 |
break; |
|
| 107 |
default: |
|
| 108 |
return; |
|
| 109 |
} |
|
| 110 |
|
|
| 111 |
final String talkText = buffer.toString(); |
|
| 112 |
mTalker.talkAlternatelyAsync(talkText); |
|
| 113 |
} |
|
| 114 |
|
|
| 115 |
@Override |
|
| 116 |
public void onDelete(long id) {
|
|
| 117 |
/* no op */ |
|
| 118 |
} |
|
| 119 |
} |
|
| 120 |
} |
|
| 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 | 1 |
package com.mizo0203.timeline.talker; |
| 2 | 2 |
|
| 3 |
import com.mizo0203.timeline.talker.util.RuntimeUtil; |
|
| 4 |
import org.jetbrains.annotations.NotNull; |
|
| 5 |
|
|
| 3 | 6 |
import java.io.*; |
| 4 | 7 |
import java.util.concurrent.ExecutorService; |
| 5 | 8 |
import java.util.concurrent.Executors; |
| ... | ... | |
| 10 | 13 |
|
| 11 | 14 |
private final ExecutorService mSingleThreadExecutor = Executors.newSingleThreadExecutor(); |
| 12 | 15 |
|
| 16 |
@NotNull private YukkuriVoice mNextVoice = Talker.YukkuriVoice.REIMU; |
|
| 17 |
|
|
| 13 | 18 |
public Talker() throws IllegalStateException, SecurityException {
|
| 14 | 19 |
File file = new File(AQUESTALK_PI_PATH); |
| 15 | 20 |
if (!file.isFile()) {
|
| 16 |
throw new IllegalStateException(file.getPath() + " に AquesTalk Pi がありません。\n" |
|
| 17 |
+ "https://www.a-quest.com/products/aquestalkpi.html\n" + "からダウンロードしてください。"); |
|
| 21 |
throw new IllegalStateException( |
|
| 22 |
file.getPath() |
|
| 23 |
+ " に AquesTalk Pi がありません。\n" |
|
| 24 |
+ "https://www.a-quest.com/products/aquestalkpi.html\n" |
|
| 25 |
+ "からダウンロードしてください。"); |
|
| 18 | 26 |
} |
| 19 | 27 |
if (!file.canExecute()) {
|
| 20 | 28 |
throw new IllegalStateException(file.getPath() + " に実行権限がありません。"); |
| 21 | 29 |
} |
| 22 | 30 |
} |
| 23 | 31 |
|
| 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 |
}); |
|
| 32 |
public void talkAlternatelyAsync(final String text) {
|
|
| 33 |
mSingleThreadExecutor.submit( |
|
| 34 |
new Runnable() {
|
|
| 35 |
|
|
| 36 |
@Override |
|
| 37 |
public void run() {
|
|
| 38 |
try {
|
|
| 39 |
File file = new File("text.txt");
|
|
| 40 |
PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter(file))); |
|
| 41 |
pw.println(text); |
|
| 42 |
pw.flush(); |
|
| 43 |
pw.close(); |
|
| 44 |
RuntimeUtil.execute( |
|
| 45 |
new String[] {
|
|
| 46 |
AQUESTALK_PI_PATH, "-v", mNextVoice.value, "-f", "text.txt", "-o", "out.wav" |
|
| 47 |
}); |
|
| 48 |
RuntimeUtil.execute(new String[] {"sh", "-c", "aplay < out.wav"}); // 起動コマンドを指定する
|
|
| 49 |
|
|
| 50 |
// 読み上げは、霊夢と魔理沙が交互に行なう |
|
| 51 |
if (mNextVoice == Talker.YukkuriVoice.REIMU) {
|
|
| 52 |
mNextVoice = Talker.YukkuriVoice.MARISA; |
|
| 53 |
} else {
|
|
| 54 |
mNextVoice = Talker.YukkuriVoice.REIMU; |
|
| 55 |
} |
|
| 56 |
|
|
| 57 |
Thread.sleep(2000); |
|
| 58 |
} catch (@NotNull IOException | InterruptedException e) {
|
|
| 59 |
e.printStackTrace(); |
|
| 60 |
} |
|
| 61 |
} |
|
| 62 |
}); |
|
| 45 | 63 |
} |
| 46 | 64 |
|
| 47 |
public static enum YukkuriVoice {
|
|
| 65 |
public enum YukkuriVoice {
|
|
| 48 | 66 |
|
| 49 |
/** |
|
| 50 |
* ゆっくりボイス - 霊夢 |
|
| 51 |
*/ |
|
| 67 |
/** ゆっくりボイス - 霊夢 */ |
|
| 52 | 68 |
REIMU("f1"), //
|
| 53 | 69 |
|
| 54 |
/** |
|
| 55 |
* ゆっくりボイス - 魔理沙 |
|
| 56 |
*/ |
|
| 70 |
/** ゆっくりボイス - 魔理沙 */ |
|
| 57 | 71 |
MARISA("f2"), //
|
| 58 | 72 |
; |
| 59 | 73 |
|
| 60 | 74 |
private final String value; |
| 61 | 75 |
|
| 62 |
private YukkuriVoice(String value) {
|
|
| 76 |
YukkuriVoice(String value) {
|
|
| 63 | 77 |
this.value = value; |
| 64 | 78 |
} |
| 65 | 79 |
} |
| 66 |
|
|
| 67 | 80 |
} |
| src/com/mizo0203/timeline/talker/TimelineTalker.java | ||
|---|---|---|
| 1 | 1 |
package com.mizo0203.timeline.talker; |
| 2 | 2 |
|
| 3 |
import twitter4j.*; |
|
| 4 |
import twitter4j.conf.Configuration; |
|
| 3 |
/* package */ interface TimelineTalker {
|
|
| 5 | 4 |
|
| 6 |
import java.util.Collections; |
|
| 7 |
import java.util.Locale; |
|
| 8 |
import java.util.Timer; |
|
| 9 |
import java.util.TimerTask; |
|
| 10 |
import java.util.concurrent.TimeUnit; |
|
| 11 |
import java.util.regex.Matcher; |
|
| 12 |
import java.util.regex.Pattern; |
|
| 13 |
|
|
| 14 |
public class TimelineTalker {
|
|
| 15 |
|
|
| 16 |
/** |
|
| 17 |
* ISO 639 言語コード - 日本語 (ja) |
|
| 18 |
*/ |
|
| 19 |
public static final String LANG_JA = Locale.JAPAN.getLanguage(); |
|
| 20 |
|
|
| 21 |
private final RequestHomeTimelineTimerTask mRequestHomeTimelineTimerTask; |
|
| 22 |
|
|
| 23 |
public TimelineTalker(Configuration configuration, Talker talker) {
|
|
| 24 |
Twitter twitter = new TwitterFactory(configuration).getInstance(); |
|
| 25 |
mRequestHomeTimelineTimerTask = new RequestHomeTimelineTimerTask(twitter, talker); |
|
| 26 |
} |
|
| 27 |
|
|
| 28 |
private static String getUserNameWithoutContext(String name) {
|
|
| 29 |
Pattern p = Pattern.compile("([^@@]+).+");
|
|
| 30 |
Matcher m = p.matcher(name); |
|
| 31 |
return m.replaceFirst("$1");
|
|
| 32 |
} |
|
| 33 |
|
|
| 34 |
public void start() {
|
|
| 35 |
new Timer().schedule(mRequestHomeTimelineTimerTask, 0L, TimeUnit.MINUTES.toMillis(1)); |
|
| 36 |
} |
|
| 37 |
|
|
| 38 |
private static class RequestHomeTimelineTimerTask extends TimerTask {
|
|
| 39 |
|
|
| 40 |
private static final int HOME_TIMELINE_COUNT_MAX = 200; |
|
| 41 |
private static final int HOME_TIMELINE_COUNT_MIN = 1; |
|
| 42 |
|
|
| 43 |
private final Twitter mTwitter; |
|
| 44 |
private final Talker mTalker; |
|
| 45 |
|
|
| 46 |
private Talker.YukkuriVoice mYukkuriVoice = Talker.YukkuriVoice.REIMU; |
|
| 47 |
|
|
| 48 |
/** |
|
| 49 |
* mStatusSinceId より大きい(つまり、より新しい) ID を持つ HomeTimeline をリクエストする |
|
| 50 |
*/ |
|
| 51 |
private long mStatusSinceId = 1L; |
|
| 52 |
|
|
| 53 |
private boolean mIsUpdatedStatusSinceId = false; |
|
| 54 |
|
|
| 55 |
private RequestHomeTimelineTimerTask(Twitter twitter, Talker talker) {
|
|
| 56 |
mTwitter = twitter; |
|
| 57 |
mTalker = talker; |
|
| 58 |
} |
|
| 59 |
|
|
| 60 |
/** |
|
| 61 |
* The action to be performed by this timer task. |
|
| 62 |
*/ |
|
| 63 |
@Override |
|
| 64 |
public void run() {
|
|
| 65 |
try {
|
|
| 66 |
// mStatusSinceId が未更新ならば、 Status を 1 つだけ取得する |
|
| 67 |
int count = mIsUpdatedStatusSinceId ? HOME_TIMELINE_COUNT_MAX : HOME_TIMELINE_COUNT_MIN; |
|
| 68 |
Paging paging = new Paging(1, count, mStatusSinceId); |
|
| 69 |
ResponseList<Status> statusResponseList = mTwitter.getHomeTimeline(paging); |
|
| 70 |
|
|
| 71 |
if (statusResponseList.isEmpty()) {
|
|
| 72 |
return; |
|
| 73 |
} |
|
| 74 |
|
|
| 75 |
// mStatusSinceId を、取得した最新の ID に更新する |
|
| 76 |
mStatusSinceId = statusResponseList.get(0).getId(); |
|
| 77 |
mIsUpdatedStatusSinceId = true; |
|
| 78 |
|
|
| 79 |
// Status が古い順になるよう、 statusResponseList を逆順に並び替える |
|
| 80 |
Collections.reverse(statusResponseList); |
|
| 81 |
|
|
| 82 |
for (Status status : statusResponseList) {
|
|
| 83 |
onStatus(status); |
|
| 84 |
} |
|
| 85 |
|
|
| 86 |
} catch (TwitterException e) {
|
|
| 87 |
e.printStackTrace(); |
|
| 88 |
} |
|
| 89 |
} |
|
| 90 |
|
|
| 91 |
private void onStatus(final Status status) {
|
|
| 92 |
if (!LANG_JA.equalsIgnoreCase(status.getLang())) {
|
|
| 93 |
return; |
|
| 94 |
} |
|
| 95 |
|
|
| 96 |
final StringBuffer buffer = new StringBuffer(); |
|
| 97 |
|
|
| 98 |
if (status.isRetweet()) {
|
|
| 99 |
Status retweetedStatus = status.getRetweetedStatus(); |
|
| 100 |
buffer.append(getUserNameWithoutContext(status.getUser().getName()) + "さんがリツイート。"); |
|
| 101 |
buffer.append(getUserNameWithoutContext(retweetedStatus.getUser().getName()) + "さんから、"); |
|
| 102 |
buffer.append(retweetedStatus.getText()); |
|
| 103 |
} else {
|
|
| 104 |
buffer.append(getUserNameWithoutContext(status.getUser().getName()) + "さんから、"); |
|
| 105 |
buffer.append(status.getText()); |
|
| 106 |
} |
|
| 107 |
|
|
| 108 |
mTalker.talkAsync(UrlUtil.convURLEmpty(buffer).replaceAll("\n", "。"), mYukkuriVoice);
|
|
| 109 |
|
|
| 110 |
// 読み上げは、霊夢と魔理沙が交互に行なう |
|
| 111 |
if (mYukkuriVoice == Talker.YukkuriVoice.REIMU) {
|
|
| 112 |
mYukkuriVoice = Talker.YukkuriVoice.MARISA; |
|
| 113 |
} else {
|
|
| 114 |
mYukkuriVoice = Talker.YukkuriVoice.REIMU; |
|
| 115 |
} |
|
| 116 |
} |
|
| 117 |
} |
|
| 5 |
/* package */ void start(); |
|
| 118 | 6 |
} |
| src/com/mizo0203/timeline/talker/TwitterTimelineTalker.java | ||
|---|---|---|
| 1 |
package com.mizo0203.timeline.talker; |
|
| 2 |
|
|
| 3 |
import com.mizo0203.timeline.talker.util.DisplayNameUtil; |
|
| 4 |
import com.mizo0203.timeline.talker.util.UrlUtil; |
|
| 5 |
import org.jetbrains.annotations.NotNull; |
|
| 6 |
import twitter4j.*; |
|
| 7 |
import twitter4j.conf.Configuration; |
|
| 8 |
|
|
| 9 |
import java.util.Collections; |
|
| 10 |
import java.util.Locale; |
|
| 11 |
import java.util.Timer; |
|
| 12 |
import java.util.TimerTask; |
|
| 13 |
import java.util.concurrent.TimeUnit; |
|
| 14 |
|
|
| 15 |
public class TwitterTimelineTalker implements TimelineTalker {
|
|
| 16 |
|
|
| 17 |
/** ISO 639 言語コード - 日本語 (ja) */ |
|
| 18 |
private static final String LANG_JA = Locale.JAPAN.getLanguage(); |
|
| 19 |
|
|
| 20 |
@NotNull private final RequestHomeTimelineTimerTask mRequestHomeTimelineTimerTask; |
|
| 21 |
|
|
| 22 |
public TwitterTimelineTalker(@NotNull Configuration configuration, Talker talker) {
|
|
| 23 |
Twitter twitter = new TwitterFactory(configuration).getInstance(); |
|
| 24 |
mRequestHomeTimelineTimerTask = new RequestHomeTimelineTimerTask(twitter, talker); |
|
| 25 |
} |
|
| 26 |
|
|
| 27 |
@Override |
|
| 28 |
public void start() {
|
|
| 29 |
new Timer().schedule(mRequestHomeTimelineTimerTask, 0L, TimeUnit.MINUTES.toMillis(1)); |
|
| 30 |
} |
|
| 31 |
|
|
| 32 |
private static class RequestHomeTimelineTimerTask extends TimerTask {
|
|
| 33 |
|
|
| 34 |
private static final int HOME_TIMELINE_COUNT_MAX = 200; |
|
| 35 |
private static final int HOME_TIMELINE_COUNT_MIN = 1; |
|
| 36 |
|
|
| 37 |
private final Twitter mTwitter; |
|
| 38 |
private final Talker mTalker; |
|
| 39 |
|
|
| 40 |
/** mStatusSinceId より大きい(つまり、より新しい) ID を持つ HomeTimeline をリクエストする */ |
|
| 41 |
private long mStatusSinceId = 1L; |
|
| 42 |
|
|
| 43 |
private boolean mIsUpdatedStatusSinceId = false; |
|
| 44 |
|
|
| 45 |
private RequestHomeTimelineTimerTask(Twitter twitter, Talker talker) {
|
|
| 46 |
mTwitter = twitter; |
|
| 47 |
mTalker = talker; |
|
| 48 |
} |
|
| 49 |
|
|
| 50 |
/** The action to be performed by this timer task. */ |
|
| 51 |
@Override |
|
| 52 |
public void run() {
|
|
| 53 |
try {
|
|
| 54 |
// mStatusSinceId が未更新ならば、 Status を 1 つだけ取得する |
|
| 55 |
int count = mIsUpdatedStatusSinceId ? HOME_TIMELINE_COUNT_MAX : HOME_TIMELINE_COUNT_MIN; |
|
| 56 |
Paging paging = new Paging(1, count, mStatusSinceId); |
|
| 57 |
ResponseList<Status> statusResponseList = mTwitter.getHomeTimeline(paging); |
|
| 58 |
|
|
| 59 |
if (statusResponseList.isEmpty()) {
|
|
| 60 |
return; |
|
| 61 |
} |
|
| 62 |
|
|
| 63 |
// mStatusSinceId を、取得した最新の ID に更新する |
|
| 64 |
mStatusSinceId = statusResponseList.get(0).getId(); |
|
| 65 |
mIsUpdatedStatusSinceId = true; |
|
| 66 |
|
|
| 67 |
// Status が古い順になるよう、 statusResponseList を逆順に並び替える |
|
| 68 |
Collections.reverse(statusResponseList); |
|
| 69 |
|
|
| 70 |
for (Status status : statusResponseList) {
|
|
| 71 |
onStatus(status); |
|
| 72 |
} |
|
| 73 |
|
|
| 74 |
} catch (TwitterException e) {
|
|
| 75 |
e.printStackTrace(); |
|
| 76 |
} |
|
| 77 |
} |
|
| 78 |
|
|
| 79 |
private void onStatus(final Status status) {
|
|
| 80 |
if (!LANG_JA.equalsIgnoreCase(status.getLang())) {
|
|
| 81 |
return; |
|
| 82 |
} |
|
| 83 |
|
|
| 84 |
final StringBuffer buffer = new StringBuffer(); |
|
| 85 |
|
|
| 86 |
if (status.isRetweet()) {
|
|
| 87 |
Status retweetedStatus = status.getRetweetedStatus(); |
|
| 88 |
buffer |
|
| 89 |
.append(DisplayNameUtil.removeContext(status.getUser().getName())) |
|
| 90 |
.append("さんがリツイート。");
|
|
| 91 |
buffer |
|
| 92 |
.append(DisplayNameUtil.removeContext(retweetedStatus.getUser().getName())) |
|
| 93 |
.append("さんから、");
|
|
| 94 |
buffer.append(retweetedStatus.getText()); |
|
| 95 |
} else {
|
|
| 96 |
buffer.append(DisplayNameUtil.removeContext(status.getUser().getName())).append("さんから、");
|
|
| 97 |
buffer.append(status.getText()); |
|
| 98 |
} |
|
| 99 |
|
|
| 100 |
mTalker.talkAlternatelyAsync(UrlUtil.convURLEmpty(buffer).replaceAll("\n", "。"));
|
|
| 101 |
} |
|
| 102 |
} |
|
| 103 |
} |
|
| 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/timeline/talker/util/DisplayNameUtil.java | ||
|---|---|---|
| 1 |
package com.mizo0203.timeline.talker.util; |
|
| 2 |
|
|
| 3 |
import org.jetbrains.annotations.NotNull; |
|
| 4 |
|
|
| 5 |
import java.util.regex.Matcher; |
|
| 6 |
import java.util.regex.Pattern; |
|
| 7 |
|
|
| 8 |
public class DisplayNameUtil {
|
|
| 9 |
|
|
| 10 |
public static String removeContext(@NotNull String name) {
|
|
| 11 |
Pattern p = Pattern.compile("([^@@]+).+");
|
|
| 12 |
Matcher m = p.matcher(name); |
|
| 13 |
return m.replaceFirst("$1");
|
|
| 14 |
} |
|
| 15 |
} |
|
| src/com/mizo0203/timeline/talker/util/HTMLParser.java | ||
|---|---|---|
| 1 |
package com.mizo0203.timeline.talker.util; |
|
| 2 |
|
|
| 3 |
import org.apache.commons.io.IOUtils; |
|
| 4 |
import org.jetbrains.annotations.NotNull; |
|
| 5 |
|
|
| 6 |
import javax.swing.text.html.HTMLEditorKit; |
|
| 7 |
import javax.swing.text.html.parser.ParserDelegator; |
|
| 8 |
import java.io.IOException; |
|
| 9 |
import java.io.InputStreamReader; |
|
| 10 |
import java.nio.charset.Charset; |
|
| 11 |
|
|
| 12 |
public class HTMLParser {
|
|
| 13 |
|
|
| 14 |
@NotNull |
|
| 15 |
public String parse(@NotNull String html, @NotNull Charset encoding, boolean ignoreCharSet) |
|
| 16 |
throws IOException {
|
|
| 17 |
try (InputStreamReader r = |
|
| 18 |
new InputStreamReader(IOUtils.toInputStream(html, encoding), encoding)) {
|
|
| 19 |
HTMLParserCallback hp = new HTMLParserCallback(); |
|
| 20 |
ParserDelegator parser = new ParserDelegator(); |
|
| 21 |
parser.parse(r, hp, ignoreCharSet); |
|
| 22 |
return hp.getText(); |
|
| 23 |
} |
|
| 24 |
} |
|
| 25 |
|
|
| 26 |
/** |
|
| 27 |
* http://www.my-notebook.net/736a69e0-820c-423b-9047-a02b8a9eefb1.html |
|
| 28 |
* |
|
| 29 |
* <p>HTMLParser.java |
|
| 30 |
*/ |
|
| 31 |
private static class HTMLParserCallback extends HTMLEditorKit.ParserCallback {
|
|
| 32 |
private final StringBuffer sb = new StringBuffer(); |
|
| 33 |
|
|
| 34 |
private String getText() {
|
|
| 35 |
return sb.toString(); |
|
| 36 |
} |
|
| 37 |
|
|
| 38 |
@Override |
|
| 39 |
public void handleText(@NotNull char[] data, int pos) {
|
|
| 40 |
sb.append(new String(data)); |
|
| 41 |
sb.append(System.getProperty("line.separator"));
|
|
| 42 |
} |
|
| 43 |
} |
|
| 44 |
} |
|
| src/com/mizo0203/timeline/talker/util/RuntimeUtil.java | ||
|---|---|---|
| 1 |
package com.mizo0203.timeline.talker.util; |
|
| 2 |
|
|
| 3 |
import org.jetbrains.annotations.NotNull; |
|
| 4 |
|
|
| 5 |
import java.io.IOException; |
|
| 6 |
|
|
| 7 |
public class RuntimeUtil {
|
|
| 8 |
|
|
| 9 |
public static void execute(String[] cmdarray) {
|
|
| 10 |
try {
|
|
| 11 |
Process process = Runtime.getRuntime().exec(cmdarray); |
|
| 12 |
process.waitFor(); |
|
| 13 |
process.destroy(); |
|
| 14 |
} catch (@NotNull IOException | InterruptedException e) {
|
|
| 15 |
e.printStackTrace(); |
|
| 16 |
} |
|
| 17 |
} |
|
| 18 |
} |
|
| src/com/mizo0203/timeline/talker/util/UrlUtil.java | ||
|---|---|---|
| 1 |
package com.mizo0203.timeline.talker.util; |
|
| 2 |
|
|
| 3 |
import org.jetbrains.annotations.NotNull; |
|
| 4 |
|
|
| 5 |
import java.util.regex.Matcher; |
|
| 6 |
import java.util.regex.Pattern; |
|
| 7 |
|
|
| 8 |
/** http://chat-messenger.net/blog-entry-40.html */ |
|
| 9 |
public class UrlUtil {
|
|
| 10 |
/** URLを抽出するための正規表現パターン */ |
|
| 11 |
@SuppressWarnings("Annotator")
|
|
| 12 |
private static final Pattern convURLLinkPtn = |
|
| 13 |
Pattern.compile( |
|
| 14 |
"(http://|https://){1}[\\w\\.\\-/:\\#\\?\\=\\&\\;\\%\\~\\+]+", Pattern.CASE_INSENSITIVE);
|
|
| 15 |
|
|
| 16 |
/** |
|
| 17 |
* 指定された文字列内のURLを、正規表現を使用し、 空文字列に変換する。 |
|
| 18 |
* |
|
| 19 |
* @param str 指定の文字列。 |
|
| 20 |
* @return リンクに変換された文字列。 |
|
| 21 |
*/ |
|
| 22 |
public static String convURLEmpty(@NotNull CharSequence str) {
|
|
| 23 |
Matcher matcher = convURLLinkPtn.matcher(str); |
|
| 24 |
return matcher.replaceAll("");
|
|
| 25 |
} |
|
| 26 |
} |
|
他の形式にエクスポート: Unified diff
Add feature Mastodon's timeline talking. fix #422 @16.0h