Giter Site home page Giter Site logo

juvet / juvet Goto Github PK

View Code? Open in Web Editor NEW
71.0 2.0 2.0 597 KB

The MVC framework for chat apps built on a platform designed for communication systems.

Home Page: http://juvet.io

License: MIT License

Elixir 100.00%
chatbot chatbot-framework elixir elixir-library sponsorware slack bot

juvet's Introduction

juvet logo

The MVC framework for chat apps built on a platform designed for communication systems.

THIS IS A WORK IN PROGRESS AND NOT READY FOR ANYTHING REAL YET

DESCRIPTION

Build chat bot applications for the major chat bot application platforms using familiar model-view-controller architecture patterns that developers have been using for decades.

Juvet is an application framework that includes everything you need to build a chat application for all the major messaging platforms including:

Juvet offers all the features you need to build a scalable and maintainable chat application, including

  • API Wrappers
  • Message Queuing
  • Middleware and Plugins
  • Conversation Support
  • NLP Support
  • more to come...

The ROADMAP describes major upcoming features within each release.

THIS FRAMEWORK IS AVAILABLE FOR SPONSORSHIP ๐Ÿ‘

This repository is available for sponsorship via GitHub Sponsors at https://github.com/sponsors/jwright.

If you or your company will benefit from a well-maintained and easy to use chat application framework, please consider a sponsorship. Your sponsorship will help with this development.

Thank you for the support! ๐Ÿ’“

INSTALLATION

  • Add the Juvet dependencies to your mix.exs file
# mix.exs

def deps do
  [{:juvet, "~> 0.0.1"}]
end
  • Install the depedencies
mix deps.get
  • Ensure Juvet is started before your application
# mix.exs

def application do
  [extra_applications: [:juvet]]
end

USAGE

Initial Processes

When Juvet starts, the following is what that process tree should look like.

                                            +------------------+     +-------------------+
                                            |                  |-----| ViewStateRegistry |
                                         +--| ViewStateManager |     +-------------------+
                                         |  |                  |     +---------------------+
                                         |  +------------------+-----| ViewStateSupervisor |
  +---------------+    +--------------+--+  +----------------+       +---------------------+
  |               |    |              |     |                |
  |     Juvet     |----|  BotFactory  |-----| Superintendent |
  | (application) |    |              |     |                |
  |               |    +--------------+--+  +----------------+
  +---------------+                      |  +-------------------+
                                         |  |                   |
                                         +--| FactorySupervisor |
                                            |                   |
                                            +-------------------+
                                             |                 |
                                             |                 |
                                      +---------------+ +---------------+
                                      |               | |               |
                                      | BotSupervisor | | BotSupervisor |
                                      |               | |               |
                                      +---------------+ +---------------+
                                               |                |
                                               |                |
                                            +-----+          +-----+
                                            | Bot |          | Bot |
                                            +-----+          +-----+
  • Juvet - Application that starts the Juvet.BotFactory supervisor
  • BotFactory - Supervisor that starts the Juvet.Superintendent and Juvet.ViewStateManager processes.
  • ViewStateManager - Supervisor that can manage the storage of any arbitray piece of data for a given set of keys. Starts the Juvet.ViewStateRegistry and a dynamic supervisor for Juvet.ViewState processes.
  • ViewStateRegistry - Server to act as a registry service to convert keys (as Tuples) into pids in order to identify Juvet.ViewState processes.
  • Superintdendent - The brains of the operation. Process checks the validity of the configuration and if it is configured correctly, it starts the Juvet.Endpoint process and the Juvet.FactorySupervisor
  • FactorySupervisor - Supervisor over all of the Juvet.BotSupervisor processes.
  • BotSupervisor - Supervisor over one Juvet.Bot process as well as any additional supporting processes (like Juvet.Receivers.SlackRTMReceiver)
  • Bot - Receives messages from the chat providers. It is responsible for processing messages and generating responses

Configuration

You need to tell Juvet what bot module should be created when a new connection is made. You can do that with the following configuration.

# config/config.exs

config :juvet,
  bot: MyBot,
  slack: [
    actions_endpoint: "/slack/actions",
    commands_endpoint: "/slack/commands",
    events_endpoint: "/slack/events",
    options_load_endpoint: "/slack/options"
  ]

Mount Router

The client application that is using Juvet can use the router from your client application. You just need to mount the Juvet.Plug.

Mounting in Phoenix

The router can be mounted inside the Phoenix endpoint by just adding:

# lib/my_phoenix_app_web/endpoint.ex

defmodule MyPhoenixAppWeb.Endpoint do
  use Phoenix.Endpoint, otp_app: :my_phoenix_app

  # ...

  plug Juvet.Plug
end

Slack

Authorizing with your Slack app

Currently Juvet does not perform any oauth functionality. That will be coming soon so it is up to your application to connect your app to Slack via OAuth. If you are using ueberauth, then ueberauth_slack is a good choice to get your users authorized with Slack.

Once your get the bot access token for your team, you are ready to go.

Connecting to your Slack app

Once you have a bot access token for your team, you can connect to Slack via:

{:ok, bot} = Juvet.create_bot("MyBot")

Handling events from Slack

You can handle messages from Slack by overriding the handle_event/3 function on your bot. This function can use pattern matching in order to handle various events from Slack.

defmodule MyBot do
  use Juvet.Bot

  def handle_event(platform, %{type: "message"} = message, state) do
    # Add your logic here on how to handle a message event

    {:ok, state}
  end

  def handle_event(platform, %{type: "file_created"} = message, state) do
    # Add your logic here on how to handle a file_created event

    {:ok, state}
  end
end

Sending messages to Slack

You can send messages back to Slack from your bot by overridding the send_message/3 function on your bot. The second argument (state) should contain an (id) key which will be used to send the message to the correct team.

defmodule MyBot do
  use Juvet.Bot

  def handle_event(platform, %{type: "message", text: "Hello"} = message, %{id: id, channel: channel} = state) do
    send_message(platform, state, %{type: "message", channel: channel, message: "Right back at cha!"})

    {:ok, state}
  end
end

DOCUMENTATION

Connecting to a platform

Receiving messages

Responding to messages

TESTING

You can run the tasks with the standard mix command:

mix test

Re-recording responses

You can re-record the responses from Slack with the following mix command:

MIX_ENV=test mix record token:<slack token here> channel:<slack channel id here> text:"<Welcome from Juvet!>" ts:<valid message timestamp here> user:<slack user id here> users:<slack user id here>,<another slack user id here>

You can record the casettes for just one method by adding a method parameter above (i.e. method:chat.update) and it will just re-record that one method.

You can create a Slack token for any of your teams by visiting the OAuth & Permissions area in your Slack API Apps.

COMMUNITY

Contributing

  1. Clone the repository git clone https://github.com/juvet/juvet
  2. Create a feature branch git checkout -b my-awesome-feature
  3. Codez!
  4. Commit your changes (small commits please)
  5. Push your new branch git push origin my-awesome-feature
  6. Create a pull request hub pull-request -b juvet:main -h juvet:my-awesome-feature

Copyright and License

Copyright (c) 2018, Jamie Wright.

Juvet source code is licensed under the MIT License.

juvet's People

Contributors

jwright avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

juvet's Issues

Slack Bot Behavior

Create a behavior or module that can be injected into custom bots to help outside implementors create a bot.

defmodule MyBot do
  use Juvet.Bot

  def handle_message(:slack, %{type: "message"} = message) do
    #...
    send_message(:slack, %{})...
  end
end

Connection Supervisor Tree

Add a supervisor tree to the connections so a new SlackRTM connection is created for each new connection (e.g. Slack API token) that exists.

The following should be created:

Juvet.ConnnectionFactory -> Worker that gets added to a Juvet.ConnectionFactorySupervisor
Juvet.ConnectionSupervisor -> Supervises workers that get created

Setup CI

Setup CI using GitHub actions so the library is built on every merge into main as well as all new pull requests.

Extract View Submission Values

Create a module that allows a client to easily extract the submission values from a submission payload:

Juvet.Slack.View.values(payload)

This would extract a view payload like the following:

{
  payload: {
    type: "view_submission",
    view: {
      state: {
        values: {
          "block_id" => {
             "my_action" => {
               type: "plain_text_input",
               value: "my value"
             }
           }
        }
      }
    }
  }
}

And would return the following:

%{
  "my_action" => "my value"
}

`user_install` bot method

Called when a user authenticates with Juvet.

def user_install(bot, platform, payload)
end

# USAGE: Juvet.BotFactory.find_or_create(auth.team.team_name) |> MyBot.user_install(:slack, auth)
# Juvet.find_or_create_bot could be a shortcut

This method runs through a series of behavior callbacks which will perform the following:

  1. Call mounted callback if started -> maybe called when the bot is created?
  2. Call authenticated callback
  3. Call installed callback
  • Determines if user is new or existing
  • Looks at local state unless overridden
  1. Call failed_authentication callback if the auth failed
  2. Call signup if user is new
  3. Call signin if user is existing
  • State for the bot should now have the ability to retrieve the user list (via user_ids)
  • Need a way to map teams from different platforms
    • { team: { name: "Tatsu", identifiers: [{ slack: "T12345" }, { facebook: "" }] } }
  • Need a way to map users from different platforms
    • { user: { name: "Jimmy Page", identifiers: [{ slack: "T12345/U12345" }, { facebook: "" }] } }
  • This may remove the need for Juvet.create_bot!/1, Juvet.connect_bot!/3 and Juvet.start_bot!/3 because Juvet.user_install/3 would be the next logical step and it does what connect_bot! does.

Slack Signature Timestamp Range Configuration

Allow a client to configure the verifying of requests for Slack.

A user should be able to:

  1. Turn on/off the verifying of Slack Requests โœ…
  2. Turn on/off timestamp verification
  3. Extend/contract the default 5 minute time-limit for stale requests

Slack OAuth

Juvet should handle the intricacies of oauthing with Slack (and future providers like MS Teams and Amazon). This will allow the setup of a bot within a particular framework easier and built in.

Possible Solution

  • We may want to expose the request and callback routes through the SlackEndpointRouter via a SlackAuthenticationRouter and allow them to be overriden
  • Within the "request phase", we just need a way to get an authorize_url with configured scopes, app identifiers and secrets
  • The way to retrieve the authorize_url should be available publically within the OAuth library
  • Within the "callback phase", we should expose an Access callback which gets the token, an Authenticating callback which creates the bot and installs the user and bot, and an Authenticated callback which is called after the bot is installed (can be a callback on the bot).
  • The way to retrieve the token from the code should be available publically within the OAuth library

Design "Musts"

  • The OAuth functionality should be namespaced so that it can be pulled out into it's own package in the future if necessary.
  • It should not modify the payloads that come from the token endpoints. This will make it easier to debug and maintain if the payload matches the documentation.
  • The SlackAuthenticationRouter handler should be a one line call so it can easily be called in overridden callbacks

Supervisor Process Architecture

I think the supervisor architecture may need to change. Currently, the archiecture is something like this:

On Startup:

[Juvet.ConnectionFactorySupervisor]
-> [Juvet.ConnectionFactory]
-> [Juvet.ConnectionSupervisor]

[Juvet.BotFactorySupervisor]
-> [Juvet.BotFactory]
-> [Juvet.BotSupervisor]

On Connecting:

[Juvet.ConnectionFactorySupervisor]
-> [Juvet.ConnectionFactory]
-> [Juvet.ConnectionSupervisor]
-> [Juvet.Connection.SlackRTM]

[Juvet.BotFactorySupervisor]
-> [Juvet.BotFactory]
-> [Juvet.BotSupervisor]
-> [tatsuio (Juvet.Bot.Server)]

It would be nice to connect each connection to the bot so a bot may have supervise multiple connections so if one goes down, they can be restarted by the bot that has the state.

Also, we should research the architecture a bit to see if the Factory workers are needed to be under the FactorySupervisor or if those can just be stand alone processes. If they can be standalone processes, we should rename the FactorySupervisor processes.

Send Message from Bot

The bot behavior should have a send_message function that can send text or a Map to Slack via the SlackRTM connection.

Expose Router instead of Endpoint

Adding Juvet to an application should not on it's own start it's own Plug.Router as that is another HTTP endpoint that has to be managed in the deployed application's environment.

Since most bot applications are going to have their own web presence, Juvet should expose the Plug.Router that can be mounted inside another application like so:

defmodule Web.Router do
  use Plug.Router
  forward "/slack", to: Juvet.EndpointRouter
end

This should not start the Juvet.Endpoint supervisor as Juvet itself does not need to start a Cowboy plug. Future options may allow for a standalone bot instance that does not have an endpoint

Specify Middleware in Router

The user should be able to specify the middleware that gets run when a route is identified and then run.

Should work similar to GraphQL "base middleware".

Update the Juvet.Middleware module so the MiddlewareProcessor can retrieve the correct middleware.

SlackAPI Request Updates

The SlackAPI is lacking some basic Slack API communication. These upgrades need to be made:

  • Use POST for the requests instead of using GET
  • Specify a default content-type of application/x-www-form-urlencoded
  • Remove the need to have a SlackAPI.request_params/1 and just use a Map (which should be handled by HTTPoison)

Supervisor Process Architecture

I think the supervisor architecture may need to change. Currently, the archiecture is something like this:

On Startup:

[Juvet.ConnectionFactorySupervisor]
-> [Juvet.ConnectionFactory]
-> [Juvet.ConnectionSupervisor]

[Juvet.BotFactorySupervisor]
-> [Juvet.BotFactory]
-> [Juvet.BotSupervisor]

On Connecting:

[Juvet.ConnectionFactorySupervisor]
-> [Juvet.ConnectionFactory]
-> [Juvet.ConnectionSupervisor]
-> [Juvet.Connection.SlackRTM]

[Juvet.BotFactorySupervisor]
-> [Juvet.BotFactory]
-> [Juvet.BotSupervisor]
-> [tatsuio (Juvet.Bot.Server)]

It would be nice to connect each connection to the bot so a bot may have supervise multiple connections so if one goes down, they can be restarted by the bot that has the state.

Also, we should research the architecture a bit to see if the Factory workers are needed to be under the FactorySupervisor or if those can just be stand alone processes. If they can be standalone processes, we should rename the FactorySupervisor processes.

Slack Router for actions

Create a router that can accept parameters for a block_actions in order to route the request to a controller and action.

A single route file will be available for every Juvet application which can define routes for a Slack endpoint. Here is an example of what some actions may look like within a route file:

# lib/tatsu_bot/router.ex

defmodule TatsuBot.Router do
  use Juvet.Router

  platform :slack do
    action "action_id", "controller#action"
  end
end

The above will find a route with a request that has a type of block_actions and it matches the action_id specified. Here is the documentation around the block_actions payload: https://api.slack.com/reference/interaction-payloads/block-actions

Bot State

The connections, bot processes, and bots should have an organized Juvet.State.Slack state struct that holds onto the state when the connection initially connects.

  • Allows for the modules to retrieve the platform
  • Allows for the modules to retrieve the connection_id (e.g. in Slack, this would be the team id)
  • Easily retrieve information about the team
  • Easily retrieve information about the champion (user)

rtm.start

Allow for the rtm.start method to be called instead of the rtm.connect method.

The start method is heavier weight because it returns much more state but some may opt to use it. This should be passed in as config but it would also be nice to config it on a per Slack bot basis.

Connect to a Slack RTM Bot

Connecting to a Slack RTM should be done within a Connection module.

defmodule MyConnection do
  use Juvet.Connection.Slack

  def handle_connect(context) do
    IO.puts "Connected to #{context.connection.type}..."
    {:ok, context}
  end
end

A connection should handle the following:

  • handle_connect
  • handle_disconnect
  • handle_close

Slack State

The connections, bot processes, and bots should have an organized Juvet.State.Slack state struct that holds onto the state when the connection initially connects.

  • Allows for the modules to retrieve the platform
  • Allows for the modules to retrieve the connection_id (e.g. in Slack, this would be the team id)
  • Easily retrieve information about the team
  • Easily retrieve information about the champion (user)

Message Broadcaster

A Process that receives new messages from multiple endpoints. These are the raw messages with an origin_platform that describes where the message came from.

This is a simple pubsub process that simply forwards messages to anything that is interested from the SlackRTM process right now.

Bot Factory

Process that is used to create bots based on various messages that are received. This bot factory should have child processes for all of the bot children it creates.

It listens for the following messages:

  • new_slack_connection -> Creates a new bot process for the specified team

Slack Router for Commands

Create a router that can accept parameters for a command in order to route the request to a controller and action.

A single route file will be available for every Juvet application which can define routes for a Slack endpoint. Here is an example of what some commands may look like within a route file:

# lib/tatsu_bot/router.ex

defmodule TatsuBot.Router do
  use Juvet.Router

  platform :slack do
    command :start, MeetingController, :start
  end
end

Setup CI

CI should be setup so it is run and the GitHub Checks API checks and secures the master branch. CircleCI is preferred.

Update README

  • Add installation details around adding it to an application
  • Add details around how to use bot behaviours

Allow for `rtm.start` to be used when connecting via Slack RTM

Allow for the rtm.start method to be called instead of the rtm.connect method.

The start method is heavier weight because it returns much more state but some may opt to use it. This should be passed in as config but it would also be nice to config it on a per Slack bot basis.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.