Created
December 6, 2016 12:42
-
-
Save paavohuhtala/fcf7c73a474ad39277e6b3de492d60ae to your computer and use it in GitHub Desktop.
F# telegram bot with Hopac
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace FsBots | |
open System | |
open Hopac | |
type ChatMessage<'id, 'msg, 'user> = { | |
chatId: 'id | |
content: string | |
conversation: Conversation<'id, 'msg, 'user> | |
internalMessage: 'msg | |
} | |
and OutboundMessage<'id> = | SendMessage of recipient: 'id * text: string | |
and Conversation<'id, 'msg, 'user> = { | |
chatId: 'id | |
from: 'user | |
started: DateTime | |
inbound: Mailbox<ChatMessage<'id, 'msg, 'user>> | |
outbound: Mailbox<OutboundMessage<'id>> | |
// Methods | |
send: string -> Job<unit> | |
receive: unit -> Job<ChatMessage<'id, 'msg, 'user>> | |
} | |
type ConversationHandler<'id, 'msg, 'user> = | |
| Handler of (ChatMessage<'id, 'msg, 'user> -> Job<ConversationHandler<'id, 'msg, 'user> option>) | |
type InternalMessage<'id, 'msg, 'user> = | MessageReceived of 'msg | |
type BotImpl<'id, 'msg, 'user when 'id : comparison> = { | |
sendMessage: ('id * string) -> Job<unit> | |
toChatMessage: Conversation<'id, 'msg, 'user> -> 'msg -> ChatMessage<'id, 'msg, 'user> | |
chatIdFromMessage: 'msg -> 'id | |
authorFromMessage: 'msg -> 'user | |
getInitialHandler: unit -> ConversationHandler<'id, 'msg, 'user> | |
} | |
and ServerState<'id, 'msg, 'user when 'id : comparison> = | |
{ | |
bot: BotImpl<'id, 'msg, 'user> | |
serverMb: Mailbox<InternalMessage<'id, 'msg, 'user>> | |
outMb: Mailbox<OutboundMessage<'id>> | |
conversations: Map<'id, Conversation<'id, 'msg, 'user>> | |
initialHandler: ConversationHandler<'id, 'msg, 'user> | |
} | |
module Conversation = | |
let private send_ outbound chatId str = Mailbox.send outbound (SendMessage (chatId, str)) | |
let private receive_ inbound = Mailbox.take inbound | |
let send (convo: Conversation<_, _, _>) str = send_ convo.outbound str | |
let receive (convo: Conversation<_, _, _>) = receive_ convo.inbound | |
let fromMessage impl outbound msg = | |
let inbound = Mailbox<ChatMessage<'id, 'msg, 'user>> () | |
let chatId = impl.chatIdFromMessage msg | |
{ | |
chatId = chatId | |
from = impl.authorFromMessage msg | |
started = DateTime.Now | |
inbound = Mailbox () | |
outbound = outbound | |
send = send_ outbound chatId | |
receive = fun () -> receive_ inbound :> Job<_> | |
} | |
module Bot = | |
let rec convoLoop inbound (Handler handler) = job { | |
let! convoMsg = Mailbox.take inbound | |
let! nextHandler = handler convoMsg | |
do! convoLoop inbound (defaultArg nextHandler (Handler handler)) | |
} | |
let rec serverLoop state = job { | |
let! msg = Mailbox.take state.serverMb | |
match msg with | |
| MessageReceived msg -> | |
let msgChatId = state.bot.chatIdFromMessage msg | |
let msgConvo = Map.tryFind msgChatId state.conversations | |
match msgConvo with | |
| Some convo -> | |
do! Mailbox.send convo.inbound (state.bot.toChatMessage convo msg) | |
return state | |
| None -> | |
let convo = Conversation.fromMessage state.bot state.outMb msg | |
let chatMsg = state.bot.toChatMessage convo msg | |
do! Job.start (convoLoop convo.inbound state.initialHandler) | |
do! Mailbox.send convo.inbound chatMsg | |
return { state with conversations = (Map.add convo.chatId convo state.conversations) } | |
} | |
let rec outboundServer state = job { | |
let! msg = Mailbox.take state.outMb | |
match msg with | |
| SendMessage (recipient, text) -> | |
do! state.bot.sendMessage (recipient, text) | |
return state | |
} | |
let public startNew impl = | |
let outMb = Mailbox () | |
let state = { | |
bot = impl | |
serverMb = Mailbox () | |
outMb = outMb | |
conversations = Map.empty | |
initialHandler = impl.getInitialHandler () | |
} | |
job { | |
do! Job.iterateServer state outboundServer | |
do! Job.iterateServer state serverLoop | |
return state | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace FsBots.Telegram | |
open Hopac | |
open Telegram.Bot | |
open Telegram.Bot.Types | |
open Telegram.Bot.Types.Enums | |
open FsBots | |
module TelegramBot = | |
let startNew token initialHandler = | |
let bot = TelegramBotClient(token) | |
let impl = { | |
sendMessage = fun (chatId: int64, msg: string) -> job { | |
do! Job.awaitUnitTask (bot.SendChatActionAsync(chatId, ChatAction.Typing)) | |
do! timeOutMillis (String.length msg * 10 + 100) | |
do! Job.awaitUnitTask (bot.SendTextMessageAsync(chatId, msg)) | |
} | |
toChatMessage = fun convo (msg: Message) -> | |
{ | |
chatId = msg.Chat.Id | |
content = msg.Text | |
conversation = convo | |
internalMessage = msg | |
} | |
chatIdFromMessage = fun msg -> msg.Chat.Id | |
authorFromMessage = fun msg -> msg.From | |
getInitialHandler = fun () -> initialHandler | |
} | |
job { | |
let! botState = Bot.startNew impl | |
bot.OnMessage.Add (fun x -> Mailbox.send botState.serverMb (MessageReceived x.Message) |> start) | |
return botState | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment