java8 + spring-boot で facebook messenger bot を書きました
http://qiita.com/amanoiverse/items/742a963be069e1902269 の fb messenger 版
- callback は https://...:443/fbmessengerbot に
- 必要情報は環境変数で
- jackson, httpclient, lombok 使用
- try catch はノーポリシー + 過剰な例外回避
controller
@RestController
@val
public class FBMessengerBotController {
@Autowired
FBMessengerBotService botService;
@RequestMapping(value = "/fbmessengerbot", method = RequestMethod.GET)
String verify(HttpServletRequest request) throws RuntimeException {
return botService.verify(request);
}
@RequestMapping(value = "/fbmessengerbot", method = RequestMethod.POST)
String message(HttpServletRequest request) throws RuntimeException {
return botService.sentToMessenger(request);
}
}
model
@Data
public class FBMessengerBotWebhook {
String object;
List<FBMessengerBotWebhookEntry> entry;
}
@Data
public class FBMessengerBotWebhookEntry {
BigDecimal id;
BigDecimal time;
List<FBMessengerBotWebhookEntryMessaging> messaging;
}
@Data
public class FBMessengerBotWebhookEntryMessaging {
Map<String, BigDecimal> sender;
Map<String, BigDecimal> recipient;
BigDecimal timestamp;
FBMessengerBotWebhookEntryMessagingMessage message;
}
@Data
public class FBMessengerBotWebhookEntryMessagingMessage {
String mid;
BigDecimal seq;
String text;
}
@Data
public class FBMessengerBotWebhookRecipient {
Map<String, BigDecimal> recipient;
Map<String, String> message;
}
service
@val
@Slf4j
@Service
public class FBMessengerBotService {
final String FBMESSENGERBOT_VERIFY_TOKEN = System.getenv("FBMESSENGERBOT_VERIFY_TOKEN");
final String FBMESSENGERBOT_ACCESS_TOKEN = System.getenv("FBMESSENGERBOT_ACCESS_TOKEN");
final String FBMESSENGERBOT_ENDPOINT = "https://graph.facebook.com/v2.6/me/messages";
public String verify(HttpServletRequest request) {
try {
val paramMap = request.getParameterMap();
// debug
val mapStream = paramMap.entrySet().stream();
mapStream.forEach(e -> log.info(String.format("%s:%s", e.getKey(), String.join("", e.getValue()))));
if (paramMap.get("hub.mode")[0].equals("subscribe")
&& paramMap.get("hub.verify_token")[0].equals(FBMESSENGERBOT_VERIFY_TOKEN)) {
return String.join("", paramMap.get("hub.challenge"));
}
} catch (Exception ex) {
ex.printStackTrace();
}
return "failed";
}
public String sentToMessenger(HttpServletRequest request) {
try {
val jb = new StringBuffer();
request.getReader().lines().forEach(jb::append);
log.info(jb.toString());
val mapper = new ObjectMapper();
val botResponse = mapper.readValue(jb.toString(), FBMessengerBotWebhook.class);
log.error("!!!" + ToStringBuilder.reflectionToString(botResponse));
botResponse.getEntry().forEach(e -> e.getMessaging().stream().forEach(this::sendMessage));
} catch (UnrecognizedPropertyException ex) {
;
} catch (Exception e) {
e.printStackTrace();
}
return "ok";
}
void sendMessage(FBMessengerBotWebhookEntryMessaging messaging) {
try {
val message = messaging.getMessage();
val text = message.getText();
log.info(text);
Stream<String> stream = Arrays.stream(text.split(""));
val builder = new URIBuilder(FBMESSENGERBOT_ENDPOINT);
builder.setParameter("access_token", FBMESSENGERBOT_ACCESS_TOKEN);
val recipient = new FBMessengerBotWebhookRecipient();
recipient.setRecipient(messaging.getSender());
val post = new HttpPost(builder.build());
post.setHeader("Content-Type", "application/json; charset=UTF-8");
try (val httpclient = HttpClients.createDefault()) {
stream.forEach(e -> sendOneRequest(httpclient, post, recipient, e));
}
} catch (IOException e) {
e.printStackTrace();
} catch (URISyntaxException e) {
e.printStackTrace();
}
}
void sendOneRequest(CloseableHttpClient client, HttpPost post, FBMessengerBotWebhookRecipient recipient,
String oneString) {
try {
Map<String, String> message = new HashMap<>();
message.put("text", oneString);
recipient.setMessage(message);
val mapper = new ObjectMapper();
val json = mapper.writeValueAsString(recipient);
log.info(json);
post.setEntity(new StringEntity(json, StandardCharsets.UTF_8));
val res = client.execute(post);
Arrays.stream(res.getAllHeaders()).forEach(e -> log.info(e.toString()));
try (val br = new BufferedReader(
new InputStreamReader(res.getEntity().getContent(), StandardCharsets.UTF_8))) {
br.lines().forEach(e -> log.info(e.toString()));
} catch (IOException e) {
e.printStackTrace();
}
log.info("JSON:" + json);
log.info("STATUSLINE:" + res.getStatusLine().toString());
} catch (IOException e) {
e.printStackTrace();
}
}
}
build.gradle
dependencies {
compileOnly('org.projectlombok:lombok')
compile('org.springframework.boot:spring-boot-starter-web')
compile('com.fasterxml.jackson.core:jackson-databind:2.7.0')
compile('com.fasterxml.jackson.core:jackson-annotations:2.7.0')
compile('com.fasterxml.jackson.core:jackson-core:2.7.0')
compile('org.apache.httpcomponents:httpasyncclient:4.1.1')
compile('org.apache.httpcomponents:httpcore-nio:4.4.4')
}
task wrapper(type: Wrapper) {
gradleVersion = '2.12'
}
- なんか、bot自身の発言でも callback が来るので、 json パースエラーを catch して握りつぶす。
- Organize Import すると、
Unexpected error in organize imports. See log for details AST must not be null
っていわれる(ここが一番はまった)。**回避するには val を全部あきらめる。**Organize Import ないと生きていけない。- lombok version は 1.6.8 かな。。。。過去の changelog で AST... についての修正的なのが書いてあるが、最新でもだめだった。
- ということで、今後は val 全面的にやめようと思う。