Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
vsct-jburet committed Mar 20, 2017
1 parent ecfadaf commit 91c9d27
Show file tree
Hide file tree
Showing 391 changed files with 18,080 additions and 2 deletions.
27 changes: 27 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.

# compiled output
/dist
/tmp
target

# IDEs and editors
/.idea
.project
.classpath
*.iml
.c9/
*.launch
.settings/
*.sublime-workspace

# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json

#System Files
.DS_Store
Thumbs.db
5 changes: 3 additions & 2 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
Expand Down Expand Up @@ -178,15 +179,15 @@
APPENDIX: How to apply the Apache License to your work.

To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.

Copyright {yyyy} {name of copyright owner}
Copyright [yyyy] [name of copyright owner]

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down
Empty file added README.md
Empty file.
49 changes: 49 additions & 0 deletions bot/connector-messenger/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright (C) 2017 VSCT
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->

<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>

<parent>
<groupId>fr.vsct.tock</groupId>
<artifactId>tock-bot</artifactId>
<version>0.1-SNAPSHOT</version>
</parent>

<artifactId>tock-bot-connector-messenger</artifactId>

<properties>
<commons-codec>1.9</commons-codec>
</properties>


<dependencies>
<dependency>
<groupId>fr.vsct.tock</groupId>
<artifactId>tock-bot-engine</artifactId>
</dependency>

<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>${commons-codec}</version>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
/*
* Copyright (C) 2017 VSCT
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package fr.vsct.tock.bot.connector.messenger

import fr.vsct.tock.bot.connector.messenger.MessengerConnectorProvider.connectorType
import fr.vsct.tock.bot.connector.messenger.model.Recipient
import fr.vsct.tock.bot.connector.messenger.model.send.MessageRequest
import fr.vsct.tock.bot.connector.messenger.model.send.OutputMessage
import fr.vsct.tock.bot.connector.messenger.model.send.Payload
import fr.vsct.tock.bot.connector.messenger.model.webhook.InputAttachment
import fr.vsct.tock.bot.connector.messenger.model.webhook.InputMessage
import fr.vsct.tock.bot.engine.action.Action
import fr.vsct.tock.bot.engine.action.SendAttachment
import fr.vsct.tock.bot.engine.action.SendAttachment.AttachmentType.audio
import fr.vsct.tock.bot.engine.action.SendAttachment.AttachmentType.image
import fr.vsct.tock.bot.engine.action.SendAttachment.AttachmentType.video
import fr.vsct.tock.bot.engine.action.SendChoice
import fr.vsct.tock.bot.engine.action.SendLocation
import fr.vsct.tock.bot.engine.action.SendSentence
import fr.vsct.tock.bot.engine.user.PlayerType.bot
import fr.vsct.tock.bot.engine.user.PlayerType.user
import mu.KotlinLogging
import fr.vsct.tock.bot.connector.messenger.model.send.Attachment as FacebookAttachment

/**
*
*/
object MessengerActionConverter {

private val logger = KotlinLogging.logger {}

fun messengerToAction(message: InputMessage, applicationId: String): Action? {
if (message.message != null) {
with(message.message) {
//skip for now
if (isEcho) {
return null
}
if (attachments != null && attachments.isNotEmpty() && attachments.first().type != null) {
val type = attachments.first().type
if (type == "location") {
return readLocation(message, attachments.first(), applicationId)
} else if (type == "image" && text.isNullOrEmpty()) {
return readImage(message, attachments.first(), applicationId)
}
}
return readSentence(message, applicationId)
}
} else if (message.postback != null) {
return SendChoice(
message.playerId(user),
applicationId,
message.recipientId(bot),
message.postback.payload)
} else if (message.optin != null) {
//not yet supported
TODO()
}

logger.error { "unknown message $message" }
return readSentence(message, applicationId)
}

private fun readSentence(message: InputMessage, applicationId: String): SendSentence {
return SendSentence(
message.playerId(user),
applicationId,
message.recipientId(bot),
message.message?.text ?: "",
mutableListOf(message)
)
}

private fun readLocation(message: InputMessage, attachment: InputAttachment, applicationId: String): SendLocation {
logger.debug { "read location attachment : $attachment" }
return SendLocation(
message.playerId(user),
applicationId,
message.recipientId(bot),
attachment.payload?.coordinates
)
}

private fun readImage(message: InputMessage, attachment: InputAttachment, applicationId: String): SendAttachment {
logger.debug { "read image attachment : $attachment" }
return SendAttachment(
message.playerId(user),
applicationId,
message.recipientId(bot),
attachment.payload?.url ?: "",
image
)
}


fun actionToMessenger(action: Action): MessageRequest? {
return when (action) {
is SendSentence ->
if (action.hasMessage(connectorType)) {
action.message(connectorType) as OutputMessage
} else {
OutputMessage(action.text ?: "")
}

is SendAttachment -> OutputMessage(
FacebookAttachment(
when (action.type) {
image -> "image"
audio -> "audio"
video -> "video"
},
Payload(url = action.url))
)
else -> {
logger.warn { "action not supported : $action " }
null
}
}?.let {
MessageRequest(Recipient(action.recipientId.id), it)
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*
* Copyright (C) 2017 VSCT
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package fr.vsct.tock.bot.connector.messenger

import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import fr.vsct.tock.bot.connector.ConnectorException
import fr.vsct.tock.bot.connector.messenger.model.Recipient
import fr.vsct.tock.bot.connector.messenger.model.UserProfile
import fr.vsct.tock.bot.connector.messenger.model.send.ActionRequest
import fr.vsct.tock.bot.connector.messenger.model.send.MessageRequest
import fr.vsct.tock.bot.connector.messenger.model.webhook.FacebookResponse
import fr.vsct.tock.shared.mapper
import mu.KotlinLogging
import okhttp3.OkHttpClient
import retrofit2.Call
import retrofit2.Response
import retrofit2.Retrofit
import retrofit2.converter.jackson.JacksonConverterFactory
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.POST
import retrofit2.http.Path
import retrofit2.http.Query
import java.lang.Exception
import java.util.concurrent.TimeUnit

/**
*
*/
class MessengerClient(val secretKey: String) {

interface GraphApi {

@POST("/v2.6/me/messages")
fun sendMessage(@Query("access_token") accessToken: String, @Body messageRequest: MessageRequest): Call<FacebookResponse>

@POST("v2.6/me/messages")
fun activateTyping(@Query("access_token") accessToken: String, @Body actionRequest: ActionRequest): Call<FacebookResponse>

@GET("v2.6/{userId}/")
fun getUserProfile(@Path("userId") userId: String, @Query("access_token") accessToken: String, @Query("fields") fields: String): Call<UserProfile>
}

private val logger = KotlinLogging.logger {}
private val graphApi: MessengerClient.GraphApi

init {
val okHttpClient = OkHttpClient.Builder().readTimeout(30, TimeUnit.SECONDS).connectTimeout(30, TimeUnit.SECONDS).build()
val retrofit = Retrofit.Builder()
.baseUrl("https://graph.facebook.com")
.addConverterFactory(JacksonConverterFactory.create(mapper))
.client(okHttpClient)
.build()
graphApi = retrofit.create<MessengerClient.GraphApi>(MessengerClient.GraphApi::class.java)
}

fun sendMessage(token: String, messageRequest: MessageRequest): FacebookResponse {
return send(messageRequest, { graphApi.sendMessage(token, messageRequest).execute() })
}

fun sendAction(token: String, actionRequest: ActionRequest): FacebookResponse {
return send(actionRequest, { graphApi.activateTyping(token, actionRequest).execute() })
}

fun getUserProfile(token: String, recipient: Recipient): UserProfile {
try {
return graphApi.getUserProfile(recipient.id, token, "first_name,last_name,profile_pic,locale,timezone,gender")
.execute().body()
} catch(e: Exception) {
logger.error(e.message, e)
return UserProfile("", "", null, null, 0, null)
}
}

private fun <T> send(request: T, call: (T) -> Response<FacebookResponse>): FacebookResponse {
try {
logger.debug { "Graph Request Input : ${jacksonObjectMapper().writeValueAsString(request)}" }
val response = call(request)

if (!response.isSuccessful) {
val error = response.message()
val errorCode = response.code()
logger.error { "Graph Request Error : $errorCode $error" }
logger.error { "Graph Request Error headers : ${response.headers()}" }
logger.error { "Graph Request Error body : ${response.errorBody().string()}" }
throw ConnectorException(error)
} else {
logger.debug { "Graph Request Output : ${jacksonObjectMapper().writeValueAsString(response.body())}" }
return response.body()
}
} catch(e: Exception) {
logger.error(e.message, e)
throw ConnectorException(e.message ?: "")
}
}
}
Loading

0 comments on commit 91c9d27

Please sign in to comment.