LoginSignup
15
15

More than 5 years have passed since last update.

java8 spring-boot を練習しながら facebook messenger bot を書いてみた

Posted at

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 全面的にやめようと思う。
15
15
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
15