├── .gitignore ├── .preferred_otp_version ├── .travis.yml ├── Changelog.md ├── Contributors ├── LICENSE ├── Makefile ├── Procfile ├── README.md ├── bin ├── console.sh └── start.sh ├── include └── ybot.hrl ├── plugins ├── ackbar.rb ├── check-site.py ├── chuck.rb ├── date.sh ├── decide.rb ├── echo.py ├── erl.rb ├── github_status.rb ├── hacker_help.py ├── hacker_news.py ├── help.py ├── ip.py ├── math.rb ├── memory │ ├── Makefile │ └── src │ │ ├── ybot_plugin_memory.app.src │ │ ├── ybot_plugin_memory.erl │ │ ├── ybot_plugin_memory_app.erl │ │ └── ybot_plugin_memory_sup.erl ├── notifications │ └── ackbar.rb ├── ping.py ├── pugme.rb ├── ruby.rb ├── shorten_url.py ├── today?.sh ├── translate.rb ├── url.scala └── wat.rb ├── priv ├── skype.py └── webadmin │ ├── css │ ├── bootstrap.min.css │ └── ybot.css │ ├── index.html │ ├── js │ ├── angular-ui.tpls.min │ ├── angular.js │ └── ybot.js │ └── views │ ├── activate_transport.html │ ├── home.html │ ├── observer.html │ ├── sidebar.html │ ├── storage.html │ ├── transport.html │ └── upload_plugin.html ├── rebar ├── rebar.config ├── src ├── channel │ ├── email │ │ ├── ybot_mail_client.erl │ │ └── ybot_mail_client_sup.erl │ └── twitter │ │ ├── ybot_twitter_client.erl │ │ └── ybot_twitter_sup.erl ├── libs │ ├── oauth.erl │ └── xmerl_validate.erl ├── transport │ ├── campfire │ │ ├── README.md │ │ ├── campfire_client.erl │ │ ├── campfire_handler.erl │ │ └── campfire_sup.erl │ ├── flowdock │ │ ├── README.md │ │ ├── flowdock_client.erl │ │ ├── flowdock_handler.erl │ │ └── flowdock_sup.erl │ ├── http │ │ ├── README.md │ │ ├── http_handler.erl │ │ ├── http_server.erl │ │ └── http_sup.erl │ ├── irc │ │ ├── README.md │ │ ├── irc_handler.erl │ │ ├── irc_lib_client.erl │ │ └── irc_lib_sup.erl │ ├── skype │ │ ├── README.md │ │ └── skype.erl │ ├── talkerapp │ │ ├── README.md │ │ ├── talker_app_client.erl │ │ ├── talker_app_sup.erl │ │ └── talkerapp_handler.erl │ └── xmpp │ │ ├── README.md │ │ ├── xmpp.hrl │ │ ├── xmpp_client.erl │ │ ├── xmpp_handler.erl │ │ ├── xmpp_sup.erl │ │ └── xmpp_xml.erl ├── web │ ├── web_admin.erl │ ├── web_admin_req_handler.erl │ └── ybot_web_admin_sup.erl ├── ybot.app.src ├── ybot.erl ├── ybot_actor.erl ├── ybot_app.erl ├── ybot_brain.erl ├── ybot_brain_api.erl ├── ybot_brain_api_server.erl ├── ybot_brain_mnesia.erl ├── ybot_brain_sup.erl ├── ybot_history.erl ├── ybot_manager.erl ├── ybot_notification.erl ├── ybot_notification_handler.erl ├── ybot_notification_sup.erl ├── ybot_parser.erl ├── ybot_plugins_observer.erl ├── ybot_shell.erl ├── ybot_sup.erl ├── ybot_utils.erl └── ybot_validators.erl └── ybot.config.template /.gitignore: -------------------------------------------------------------------------------- 1 | .eunit 2 | deps 3 | *.o 4 | ebin/ 5 | log/* 6 | *.plt 7 | *.swp 8 | *.pyc 9 | ebin/ 10 | erl_crash.dump 11 | *~ 12 | Mnesia.* 13 | -------------------------------------------------------------------------------- /.preferred_otp_version: -------------------------------------------------------------------------------- 1 | OTP_R15B01 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: erlang 2 | otp_release: 3 | - R16B 4 | - R15B02 5 | - R15B01 6 | -------------------------------------------------------------------------------- /Changelog.md: -------------------------------------------------------------------------------- 1 | # Ybot news & changelog 2 | 3 | ## Ybot-0.5.0 --> In progress 4 | 5 | ## Ybot-0.4.0 --> released 6 | 7 | * compatibility with R16B1 8 | * compilation fails on R16B02 #80 9 | * Latest cowboy 10 | * multiply irc channel support (thank you @Mendor) 11 | * nickname in xmpp muc 12 | * many fixes for plugins (thank you @tgrk) 13 | * Basic auth in web admin. 14 | 15 | ## Ybot-0.3.5 --> released. 16 | 17 | * twitter channel compatible with twitter api v.1.1. 18 | * XMPP support imrpoved. 19 | * XMPP error during connection fixed. #75. 20 | * Removed protype.js and other js besides AngularJs. 21 | * Web admin remade, now it's only based on Angular-js. 22 | 23 | ## Ybot-0.3.4 -- released. 24 | 25 | * today plugin compatible with Mac OS X date. 26 | * fixed http application/json data receiving. 27 | * #62 fixed. Security issue. Command args escaping. 28 | * new configuration option - `notification`. 29 | * Notification support. 30 | * Added channels concept. Write only transports. 31 | * Twitter channel added. 32 | * Notification support for channels. 33 | * Ybot memory json validating fix. 34 | * added new config option - `use_web_admin :: boolean()` 35 | * `gen_smtpc` added to dependencies. 36 | * Mail channel added. 37 | 38 | ## Ybot-0.3.3 --> released 39 | 40 | * #47. HipChat config moved to separte config from XMPP transport. 41 | * XMPP 'is_hipchat' option was removed. 42 | * XMPP 'hipchat_nick' option was removed. 43 | * #48. Mnesia backend storage added. 44 | * Erlang/otp application as plugin support added. 45 | * New core plugin - memory. 46 | * New config option - brain_storage. 47 | * New config option - brain_api_host. 48 | * New config option - brain_api_port. 49 | * checking_new_plugins, checking_new_plugins_timeout, commands_history and history_command_limit_count aren't required parametes now. 50 | * New Ybot erlang api, ybot:get_plugins_directory/0, get_runned_transports/0, is_new_plugins_observing/0 and etc... (see ybot.erl) 51 | * New dependence - jiffy. 52 | * New configuration option - webadmin_host :: integer() 53 | * New configuration option - webadmin_port :: binary() 54 | * Web interface added. 55 | * mochijson2 removed from dependecies. 56 | * New internal command 'name?'. 57 | * 'help' plugin updated. 58 | * all api from ybot.erl beside start/stop were removed. 59 | * ybot_shell added for testing Ybot from erlang shell. 60 | * reconnect_timeout option added to flowdock transport. 61 | * reconnect_timeout option added to talkerapp transport. 62 | * Improved skype support. 63 | * priv/skype.py now sends ping to Ybot every minute in separate thread, script work will end when ping fail.. 64 | * Fixed long message sending for campfire transport. 65 | 66 | ## Ybot-0.3.2 --> released 67 | 68 | * Fixed #42 issue. Unable to connect to IRC bug. 69 | * Fixed #44 issue. Fixed internal Ybot commands. 70 | * #42. If bot nickname already in use, generate new name and try to reconnect. 71 | * #45 fixed. Timeout error from IRC transport. 72 | * #43 fixed. Unable to compile using rebar. 73 | * Help plugin improved. 74 | * #44 Internal commands tested and fixed. 75 | * To all plugins added checks arguments. 76 | * New internal command 'announce' added. 77 | * New api ybot:act/1 added 78 | * New api ybot:plugins/0 added 79 | * All plugins argumets checking added. 80 | * Fixed campfire image/video posting. 81 | * Scala plugins support added. 82 | * Url decode/encode new plugin added. 83 | * math.rb plugin result output fixed. 84 | * New core plugin translate.rb added. tranlate text with google translate plugin added. 85 | 86 | ## Ybot-0.3.1 --> released 87 | 88 | * Added reconnect timeout option and reconnect ability to irc client --> #33. 89 | * hacker_help plugin fixed --> #35 90 | * Added reconnect timeout option to xmpp and campfire. 91 | * Added supporting of HipChat. 92 | * HTTP transport imrpoved. JSON request support added. 93 | * HTTP transport bot-nick parameter added to config. 94 | * ybot_manager get_all_transports_pid api added. 95 | * Use cowboy web server instead inets httpd for http serving. 96 | * Added http `PUT` request support. Resend request body from http to all runned transports. 97 | * echo plugin added. 98 | * Flowdock support added. 99 | * Skype support added. 100 | * Removed http PUT request supporting. Now only POST. 101 | * New transport - Talkerapp (http://talkerapp.com/rooms). 102 | * Message parsers moved to ybot_parser module. 103 | 104 | ## Ybot-0.3 --> released 105 | 106 | * New wat plugin 107 | * Irc ssl supporting --> #16 108 | * New irc configuration options: {use_ssl, true | false} 109 | * New irc configuration options: {port, PortNumber :: integer()} 110 | * New check-site plugin added 111 | * hacker_news plugin improved. Added two modes. 112 | * Join to channels with key to irc transport added. 113 | * ruby.rb plugin added. Eval simple ruby expression. 114 | * ip.py plugin added. Return Ybot external ip address. 115 | * stackoverflow search plugin added. 116 | * Dynamic loading plugins. 117 | * Added command history. 118 | * commands_history configuration parameter added. 119 | * history_command_limit_count configuration parameter added. 120 | * Added http transport. 121 | * Added PASS paraemetr to IRC. 122 | * Irc private messages support added --> # 24 123 | * XMPP-muc private message supporting added. 124 | * XMPP single user chat supporting added. 125 | * XMPP ssl support added. 126 | * GTALK support added. 127 | * New option xmpp port added. 128 | * New option xmpp use_ssl added. 129 | * Transport options validating added. 130 | * Procfile added for Heroku deploying support. 131 | * Added experemental message parser to irc handler --> #23 132 | 133 | ## Ybot-0.2 --> released 134 | 135 | * Xmpp transport supporting added 136 | * Campfire transport supporting added 137 | * New plugins added (chuck, github_status, erl, decide) 138 | * Ibrowse added to dependencies 139 | * Reloader added to dependencies 140 | * Lager added to dependencies 141 | * Mochijson2 added to dependencies 142 | * Code cleaned & documentation improved 143 | * Help plugin fixed --> #9 144 | * Command args must be string, not list of strings --> #12 145 | * Added supporting for perl plugins. 146 | * Added supporting for elixir plugins. 147 | * Added hacker_news plugin 148 | 149 | ## Ybot-0.1 --> released 150 | 151 | * Irc transport support 152 | * 5 core plugins: help, date, today?, math and ping 153 | * Xmpp experemental support in xmpp-transport-experemental branch 154 | * Simple command: Ybot hi, Ybot bue and etc... 155 | * Python plugins supporting 156 | * Ruby plugins supporting 157 | * Shell plugin supporting 158 | * Ybot stales after some time of inactivity -> #2 fixed 159 | * Documentation improved 160 | * Code layout fixed 161 | -------------------------------------------------------------------------------- /Contributors: -------------------------------------------------------------------------------- 1 | Thanks to all Ybot contributors: 2 | 3 | * Alin Popa (https://github.com/alinpopa) 4 | * Martin Wiso (https://github.com/tgrk) 5 | * James Turnbull (https://github.com/jamtur01) 6 | * Jonn Mostovoy (https://github.com/manpages) 7 | * Carlo Bertoldi (https://github.com/cbertoldi) 8 | * Andreas Stenius (https://github.com/kaos) 9 | * Tim McGilchrist (https://github.com/tmcgilchrist) 10 | * Eduardo Gurgel (https://github.com/edgurgel) 11 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all clean test eunit deps doc dialyzer 2 | 3 | PROJECT=$(basename $(CURDIR)) 4 | REBAR=rebar 5 | DIALYZER=dialyzer 6 | 7 | DIALYZER_OPTS = 8 | #DIALYZER_OPTS = -Werror_handling -Wrace_conditions -Wunmatched_returns # -Wunderspecs 9 | 10 | # Dependencies that should be included in a cached dialyzer PLT file. 11 | #DIALYZER_DEPS = deps/*/ebin 12 | DIALYZER_DEPS = deps/lager/ebin \ 13 | deps/ibrowse/ebin \ 14 | deps/mochijson2/ebin \ 15 | deps/jiffy/ebin \ 16 | deps/cowboy/ebin 17 | 18 | DEPS_PLT = $(PROJECT).plt 19 | 20 | ERLANG_DIALYZER_APPS = asn1 \ 21 | compiler \ 22 | crypto \ 23 | edoc \ 24 | erts \ 25 | eunit \ 26 | gs \ 27 | hipe \ 28 | inets \ 29 | kernel \ 30 | mnesia \ 31 | observer \ 32 | public_key \ 33 | runtime_tools \ 34 | ssl \ 35 | stdlib \ 36 | syntax_tools \ 37 | tools \ 38 | webtool \ 39 | xmerl 40 | 41 | EUNIT_FLAGS=-sasl errlog_type error 42 | 43 | all: deps compile 44 | 45 | deps: 46 | $(REBAR) get-deps 47 | 48 | compile: 49 | 50 | ifeq ($(NODEPS),true) 51 | $(REBAR) compile skip_deps=true 52 | else 53 | $(REBAR) compile 54 | endif 55 | 56 | clean: 57 | $(REBAR) clean 58 | 59 | test: all 60 | rm -rf .eunit 61 | $(REBAR) eunit skip_deps=true 62 | 63 | eunit: 64 | 65 | ifeq ($(SUITES),) # comma separated module names 66 | ERL_FLAGS="$(EUNIT_FLAGS)" $(REBAR) eunit skip_deps=true 67 | else 68 | ERL_FLAGS="$(EUNIT_FLAGS)" $(REBAR) eunit skip_deps=true suites=$(SUITES) 69 | endif 70 | 71 | # Only include local PLT if we have deps that we are going to analyze 72 | ifeq ($(strip $(DIALYZER_DEPS)),) 73 | dialyzer: ~/.dialyzer_plt 74 | $(DIALYZER) $(DIALYZER_OPTS) -r ebin 75 | else 76 | dialyzer: ~/.dialyzer_plt $(DEPS_PLT) 77 | $(DIALYZER) $(DIALYZER_OPTS) --plts ~/.dialyzer_plt $(DEPS_PLT) -r ebin 78 | 79 | $(DEPS_PLT): 80 | $(DIALYZER) --build_plt $(DIALYZER_DEPS) --output_plt $(DEPS_PLT) 81 | endif 82 | 83 | ~/.dialyzer_plt: 84 | @echo "ERROR: Missing ~/.dialyzer_plt. Please wait while a new PLT is compiled." 85 | $(DIALYZER) --build_plt --apps $(ERLANG_DIALYZER_APPS) 86 | @echo "now try your build again" 87 | 88 | doc: 89 | $(REBAR) doc skip_deps=true 90 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | worker: erl -pa ebin deps/*/ebin -noshell -config ybot -s ybot -------------------------------------------------------------------------------- /bin/console.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | erl -remsh ybot@$(hostname) -sname ybot_$RANDOM@$(hostname) -------------------------------------------------------------------------------- /bin/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | SKYPE_PID=$(ps x | grep 'skype.py' | awk '/python/' | awk '{print $1}') 4 | if [ -n "$SKYPE_PID" ]; then 5 | kill $SKYPE_PID 6 | fi 7 | 8 | if [ -f "ybot.config" ] 9 | then 10 | cp ybot.config ebin/ 11 | else 12 | echo "Unable to find ybot.config file!" 13 | exit 1 14 | fi 15 | 16 | erl -pa deps/*/ebin plugins/*/ebin ebin \ 17 | -boot start_sasl +P 2000000 \ 18 | -sname ybot@$(hostname) \ 19 | -s ybot \ 20 | -config ybot \ 21 | -noshell \ 22 | -detached 23 | -------------------------------------------------------------------------------- /include/ybot.hrl: -------------------------------------------------------------------------------- 1 | % Types 2 | -type date() :: {pos_integer(), pos_integer(), pos_integer()}. 3 | -type time() :: {pos_integer(), pos_integer(), pos_integer()}. 4 | -type datetime() :: {date(), time()}. 5 | 6 | % Schema 7 | -record(memory, {uuid :: binary(), 8 | plugin :: atom(), 9 | key :: binary(), 10 | value :: any(), 11 | created :: datetime() 12 | }). 13 | -------------------------------------------------------------------------------- /plugins/ackbar.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # 4 | # Ybot show an Admiral Ackbar 5 | # Usage: Ybot ackbar 6 | # 7 | 8 | url_list = ["http://dayofthejedi.com/wp-content/uploads/2011/03/171.jpg", 9 | "http://dayofthejedi.com/wp-content/uploads/2011/03/152.jpg", 10 | "http://farm4.static.flickr.com/3572/3637082894_e23313f6fb_o.jpg", 11 | "http://www.youtube.com/watch?v=dddAi8FF3F4", 12 | "http://6.asset.soup.io/asset/0610/8774_242b_500.jpeg", 13 | "http://files.g4tv.com/ImageDb3/279875_S/steampunk-ackbar.jpg", 14 | "http://farm6.staticflickr.com/5126/5725607070_b80e61b4b3_z.jpg", 15 | "http://www.x929.ca/shows/newsboy/wp-content/uploads/admiral-ackbar-mouse-trap.jpg", 16 | "http://farm6.static.flickr.com/5291/5542027315_ba79daabfb.jpg", 17 | "http://farm5.staticflickr.com/4074/4751546688_5c76b0e308_z.jpg", 18 | "http://farm6.staticflickr.com/5250/5216539895_09f963f448_z.jpg"] 19 | 20 | puts url_list[rand(url_list.length)] -------------------------------------------------------------------------------- /plugins/check-site.py: -------------------------------------------------------------------------------- 1 | """ 2 | Ybot check site up/down 3 | 4 | Usage: 5 | 6 | Ybot check-site http://example.com 7 | 8 | """ 9 | 10 | import sys 11 | import urllib 12 | import urllib2 13 | 14 | if len(sys.argv) != 2: 15 | print 'Wrong arguments\nUsage: Ybot checkk-site http://example.com' 16 | exit(0) 17 | 18 | # Get url for checking 19 | url = sys.argv[1] 20 | # Send request 21 | response = urllib2.urlopen('http://www.isup.me/' + url).read() 22 | # check site up or down 23 | check_site = 'It\'s just you' in response 24 | 25 | # Return 26 | if check_site == True: 27 | print "Site is up" 28 | else: 29 | print "Site is down" 30 | 31 | -------------------------------------------------------------------------------- /plugins/chuck.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # 4 | # Ybot Chuck Norris plugin 5 | # Usage: Ybot chuck 6 | # 7 | 8 | require 'net/http' 9 | require 'json' 10 | 11 | uri = URI('http://api.icndb.com/jokes/random') 12 | res = Net::HTTP.get_response(uri) 13 | 14 | # get url 15 | result = JSON.parse(res.body)['value'] 16 | 17 | # return url 18 | puts result["joke"] 19 | -------------------------------------------------------------------------------- /plugins/date.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/sh 2 | 3 | echo "Current date/time: "`date` -------------------------------------------------------------------------------- /plugins/decide.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # 4 | # Ybot decision helper plugin 5 | # Usage: Ybot decide foo bar 6 | # 7 | 8 | if ARGV.length != 1 9 | puts 'Wrong argument\nUsage: Ybot decide foo bar' 10 | exit 11 | end 12 | 13 | options = ARGV[0].split(' ') 14 | 15 | puts 'Definitely: ' + options[rand(options.length)] 16 | -------------------------------------------------------------------------------- /plugins/echo.py: -------------------------------------------------------------------------------- 1 | # 2 | # Simple Ybot echo plugin 3 | # 4 | # Usage: 5 | # 6 | # Ybot echo some text 7 | # 8 | 9 | import sys 10 | 11 | if len(sys.argv) != 2: 12 | print 'Wrong arguments\nUsage: Ybot echo some text' 13 | exit(0) 14 | 15 | # return 16 | print sys.argv[1] -------------------------------------------------------------------------------- /plugins/erl.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # 4 | ## Eval simple erlang expressions with tryerlang.org 5 | ## Usage: 6 | ## Ybot erl 1 + 1. 7 | # 8 | 9 | require 'net/http' 10 | require 'json' 11 | 12 | # new erlang expression 13 | expr = '' 14 | 15 | if ARGV.length != 1 16 | puts 'Wrong argument\nUsage: Ybot erl 1 + 1.' 17 | exit 18 | end 19 | 20 | # collect arguments in expression 21 | ARGV.each do |arg| 22 | expr += arg 23 | end 24 | 25 | # send request 26 | uri = URI('http://www.tryerlang.org/api/eval/default/intro') 27 | # get response 28 | response = Net::HTTP.post_form(uri, {'expression' => expr}) 29 | 30 | puts 'Result: ' + JSON.parse(response.body)['result'] -------------------------------------------------------------------------------- /plugins/github_status.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # 4 | # Ybot GitHub status plugin 5 | # Usage: Ybot github_status 6 | # 7 | 8 | require 'net/https' 9 | require 'json' 10 | 11 | uri = URI('https://status.github.com/api/status.json') 12 | http = Net::HTTP.new(uri.host, 443) 13 | http.use_ssl = true 14 | http.verify_mode = OpenSSL::SSL::VERIFY_NONE 15 | res = http.start do |http| 16 | request = Net::HTTP::Get.new(uri.request_uri) 17 | http.request(request) 18 | end 19 | result = JSON.parse(res.body) 20 | puts "GitHub status: " + result['status'] 21 | -------------------------------------------------------------------------------- /plugins/hacker_help.py: -------------------------------------------------------------------------------- 1 | # 2 | # Ybot search N question in stackoverflow.com 3 | # 4 | # Usage: 5 | # 6 | # Ybot hacker_help Convert integer to string in c++ 7 | # 8 | 9 | import sys 10 | import urllib 11 | import urllib2 12 | import simplejson as json 13 | import zlib 14 | 15 | if len(sys.argv) != 2: 16 | print 'Wrong arguments\nUsage: ybot hacker_help my request' 17 | sys.exit() 18 | 19 | # Get question from command line 20 | question = sys.argv[1] 21 | # stackoverflow api url 22 | api_url = 'http://api.stackoverflow.com/1.1/search?&intitle=' + urllib.quote_plus(question) 23 | 24 | # Make request 25 | response = urllib2.urlopen(api_url) 26 | # read response 27 | json_data = response.read() 28 | # Get questions 29 | questions = json.loads(zlib.decompress( json_data, 16+zlib.MAX_WBITS ))['questions'] 30 | 31 | # print questions with links 32 | for q in questions: 33 | # return 34 | print (q['title'] + ' ' + '--> http://stackoverflow.com/questions/' + str(q['question_id'])).encode('utf-8') 35 | -------------------------------------------------------------------------------- /plugins/hacker_news.py: -------------------------------------------------------------------------------- 1 | """ 2 | Ybot hacker news plugin. 3 | 4 | This plugin has two modes: 5 | 6 | * Get all news from front page 7 | * Get N last news from front page 8 | 9 | Usage: 10 | 11 | Ybot hacker_news 12 | Ybot hacker_news 5 13 | 14 | """ 15 | 16 | import sys 17 | import urllib 18 | import urllib2 19 | import simplejson as json 20 | 21 | # Get news list 22 | def get_news_list(): 23 | # list news 24 | list_news = [] 25 | # News counter 26 | i = 1 27 | try: 28 | # Api url 29 | api_url = 'http://api.ihackernews.com/page' 30 | # header 31 | header = { "Content-Type": "application/json" } 32 | # Get front page 33 | response = urllib2.urlopen(urllib2.Request(api_url, '', header)) 34 | # read response 35 | json_data = response.read() 36 | # Get news items 37 | items = json.loads(json_data)['items'] 38 | # Make string titles 39 | for item in items: 40 | # add news to list 41 | list_news.extend([str(i) + '. ' + item['title'] + '--> ' + item['url'] + '\n']) 42 | i += 1 43 | # return list news 44 | return list_news 45 | # server error 46 | except urllib2.HTTPError: 47 | # return empty list 48 | return [] 49 | 50 | # Header 51 | front = 'Last hacker news:\n' 52 | 53 | if len(sys.argv) > 1: 54 | try: 55 | # get news count 56 | news_count = int(sys.argv[1]) 57 | # get all news 58 | all_news = get_news_list() 59 | if all_news == []: 60 | print "Sorry, but server not response now, please try later." 61 | else: 62 | # get news 63 | news = all_news[:news_count] 64 | # print news 65 | for x in news: 66 | front += x 67 | print front.encode('utf-8') 68 | except ValueError: 69 | print "hacker_news argument must be a number" 70 | else: 71 | # get all news 72 | all_news = get_news_list() 73 | # print news 74 | for x in all_news: 75 | front += x 76 | print front.encode('utf-8') 77 | -------------------------------------------------------------------------------- /plugins/help.py: -------------------------------------------------------------------------------- 1 | print """ 2 | ============================================================================================================= 3 | Ybot help: 4 | 5 | Hi, i'm Ybot. I'm chat bot. You can send me different commands and i will execute it for you. I'm you helper. 6 | All my commands must start with my chat login, and there are command and command's arguments after it. 7 | 8 | For example: Ybot math 1 + 1 9 | 10 | * Ybot - is my login 11 | * math - is command 12 | * 1 + 1 - are arguments 13 | 14 | My internal commands: 15 | 16 | * announce - Send messages to all chat rooms. 17 | * name? - Send bot name to chat. 18 | * hi - say hi to Ybot 19 | * hello - say hello to Ybot 20 | * bye - say bye to Ybot 21 | * history - get command history 22 | * thanks - say thank you to Ybot 23 | * plugins? - all commands name. 24 | 25 | ============================================================================================================= 26 | """ 27 | -------------------------------------------------------------------------------- /plugins/ip.py: -------------------------------------------------------------------------------- 1 | # 2 | # Return Ybot external IP address 3 | # 4 | # Usage: 5 | # 6 | # Ybot ip 7 | # 8 | 9 | import urllib 10 | import urllib2 11 | import simplejson as json 12 | 13 | # api url 14 | url = 'http://jsonip.com' 15 | # send response 16 | response = urllib2.urlopen('http://jsonip.com/').read() 17 | # print response 18 | print 'My ip is: ' + json.loads(response)['ip'] -------------------------------------------------------------------------------- /plugins/math.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # 4 | # Ybot math plugin 5 | # Usage: Ybot math 1 +1 6 | # 7 | 8 | require 'net/http' 9 | require 'cgi' 10 | 11 | # New math expression 12 | expr = '' 13 | 14 | if ARGV.length != 1 15 | puts 'Wrong argument\nUsage: Ybot math 1 + 1' 16 | exit 17 | end 18 | 19 | # Collect math expr in one string 20 | ARGV.each do |arg| 21 | expr += arg 22 | end 23 | 24 | uri = URI('http://www.calcatraz.com/calculator/api?c=' + CGI.escape(expr)) 25 | res = Net::HTTP.get_response(uri) 26 | 27 | result = res.body.strip! 28 | 29 | if result == "answer" || result.nil? 30 | puts 'Wrong expression' 31 | else 32 | # print result 33 | puts 'Answer: ' + result 34 | end 35 | -------------------------------------------------------------------------------- /plugins/memory/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all clean test eunit deps doc dialyzer 2 | 3 | PROJECT=$(basename $(CURDIR)) 4 | REBAR=../../rebar 5 | DIALYZER=dialyzer 6 | 7 | DIALYZER_OPTS = 8 | #DIALYZER_OPTS = -Werror_handling -Wrace_conditions -Wunmatched_returns # -Wunderspecs 9 | 10 | # Dependencies that should be included in a cached dialyzer PLT file. 11 | #DIALYZER_DEPS = deps/*/ebin 12 | DIALYZER_DEPS = deps/lager/ebin \ 13 | deps/ibrowse/ebin \ 14 | deps/mochijson2/ebin \ 15 | deps/cowboy/ebin 16 | 17 | DEPS_PLT = $(PROJECT).plt 18 | 19 | ERLANG_DIALYZER_APPS = asn1 \ 20 | compiler \ 21 | crypto \ 22 | edoc \ 23 | erts \ 24 | eunit \ 25 | gs \ 26 | hipe \ 27 | inets \ 28 | kernel \ 29 | mnesia \ 30 | observer \ 31 | public_key \ 32 | runtime_tools \ 33 | ssl \ 34 | stdlib \ 35 | syntax_tools \ 36 | tools \ 37 | webtool \ 38 | xmerl 39 | 40 | EUNIT_FLAGS=-sasl errlog_type error 41 | 42 | all: deps compile 43 | 44 | deps: 45 | $(REBAR) get-deps 46 | 47 | compile: 48 | 49 | ifeq ($(NODEPS),true) 50 | $(REBAR) compile skip_deps=true 51 | else 52 | $(REBAR) compile 53 | endif 54 | 55 | clean: 56 | $(REBAR) clean 57 | 58 | test: all 59 | rm -rf .eunit 60 | $(REBAR) eunit skip_deps=true 61 | 62 | eunit: 63 | 64 | ifeq ($(SUITES),) # comma separated module names 65 | ERL_FLAGS="$(EUNIT_FLAGS)" $(REBAR) eunit skip_deps=true 66 | else 67 | ERL_FLAGS="$(EUNIT_FLAGS)" $(REBAR) eunit skip_deps=true suites=$(SUITES) 68 | endif 69 | 70 | # Only include local PLT if we have deps that we are going to analyze 71 | ifeq ($(strip $(DIALYZER_DEPS)),) 72 | dialyzer: ~/.dialyzer_plt 73 | $(DIALYZER) $(DIALYZER_OPTS) -r ebin 74 | else 75 | dialyzer: ~/.dialyzer_plt $(DEPS_PLT) 76 | $(DIALYZER) $(DIALYZER_OPTS) --plts ~/.dialyzer_plt $(DEPS_PLT) -r ebin 77 | 78 | $(DEPS_PLT): 79 | $(DIALYZER) --build_plt $(DIALYZER_DEPS) --output_plt $(DEPS_PLT) 80 | endif 81 | 82 | ~/.dialyzer_plt: 83 | @echo "ERROR: Missing ~/.dialyzer_plt. Please wait while a new PLT is compiled." 84 | $(DIALYZER) --build_plt --apps $(ERLANG_DIALYZER_APPS) 85 | @echo "now try your build again" 86 | 87 | doc: 88 | $(REBAR) doc skip_deps=true 89 | -------------------------------------------------------------------------------- /plugins/memory/src/ybot_plugin_memory.app.src: -------------------------------------------------------------------------------- 1 | {application, ybot_plugin_memory, 2 | [ 3 | {description, "Ybot plugin for memory command"}, 4 | {vsn, "0.1alfa"}, 5 | {registered, []}, 6 | {modules, [ybot_plugin_memory_app]}, 7 | {applications, [ 8 | kernel, 9 | stdlib, 10 | compiler 11 | ]}, 12 | {mod, {ybot_plugin_memory_app, []}}, 13 | {env, []} 14 | ]}. 15 | -------------------------------------------------------------------------------- /plugins/memory/src/ybot_plugin_memory.erl: -------------------------------------------------------------------------------- 1 | %%%----------------------------------------------------------------------------- 2 | %%% @author Martin Wiso 3 | %%% @doc 4 | %%% Ybot memory plugin using OTP 5 | %%% @end 6 | %%% Created : 20 Mar 2013 by tgrk 7 | %%%----------------------------------------------------------------------------- 8 | -module(ybot_plugin_memory). 9 | 10 | %% API 11 | -export([start/0, 12 | stop/0, 13 | execute/1 14 | ]). 15 | 16 | %%%============================================================================= 17 | %%% API 18 | %%%============================================================================= 19 | start() -> 20 | application:start(ybot_plugin_memory). 21 | 22 | stop() -> 23 | application:stop(ybot_plugin_memory). 24 | 25 | %% @doc execute plugin action 26 | execute([]) -> 27 | handle_command(list, [], []); 28 | execute(["list"]) -> 29 | handle_command(list, [], []); 30 | execute(["help"]) -> 31 | handle_command(help, [], []); 32 | execute(["add"]) -> 33 | handle_command(help, [], []); 34 | execute(["delete"]) -> 35 | handle_command(help, [], []); 36 | execute([Key]) -> 37 | handle_command(list, Key, []); 38 | execute([Cmd, Key | Value]) -> 39 | handle_command(list_to_atom(Cmd), Key, string:join(Value, " ")). 40 | 41 | %%%============================================================================= 42 | %%% Internal functionality 43 | %%%============================================================================= 44 | 45 | %% @doc handle plugin command 46 | handle_command(help, [], []) -> 47 | "Usage: memory add key value\n" 48 | " memory list\n" 49 | " memory delete key\n"; 50 | 51 | handle_command(delete, Key, []) -> 52 | case ybot_brain:get_by_key(to_bin(Key)) of 53 | [] -> 54 | "Key is not found"; 55 | [{memory, Id, _, _, _, _} | _Rest] -> 56 | ybot_brain:delete(Id), 57 | "Memory deleted." 58 | end; 59 | 60 | handle_command(add, _Key, []) -> 61 | "Not complete memory"; 62 | 63 | handle_command(add, Key, Value) -> 64 | ybot_brain:post(<<"memory">>, to_bin(Key), to_bin(Value)), 65 | "New memory added."; 66 | 67 | handle_command(list, [], []) -> 68 | case memories_to_list(ybot_brain:get_by_plugin(<<"memory">>)) of 69 | [] -> 70 | % memory empty 71 | "My memory is empty."; 72 | Mem -> 73 | Mem 74 | end; 75 | 76 | handle_command(list, Key, []) -> 77 | case memories_to_list(ybot_brain:get_by_key(to_bin(Key))) of 78 | [] -> 79 | % memory empty 80 | "There is nothing about " ++ Key ++ " in memory"; 81 | Mem -> 82 | Mem 83 | end. 84 | 85 | memories_to_list(Memories) -> 86 | lists:flatten(lists:map(fun memory_to_list/1, Memories)). 87 | 88 | memory_to_list({memory, _Id, _Plugin, Key, Value, _Created}) -> 89 | to_list(Key) ++ " = " ++ to_list(Value) ++ "\n". 90 | 91 | %% @doc utils functions 92 | to_list(List) when is_list(List) -> 93 | List; 94 | to_list(Bin) -> 95 | binary_to_list(Bin). 96 | 97 | to_bin(List) when is_binary(List) -> 98 | List; 99 | to_bin(List) -> 100 | list_to_binary(List). 101 | -------------------------------------------------------------------------------- /plugins/memory/src/ybot_plugin_memory_app.erl: -------------------------------------------------------------------------------- 1 | %%%----------------------------------------------------------------------------- 2 | %%% @author Martin Wiso 3 | %%% @doc 4 | %%% Ybot memory plugin using OTP application 5 | %%% @end 6 | %%% Created : 20 Mar 2013 by tgrk 7 | %%%----------------------------------------------------------------------------- 8 | -module(ybot_plugin_memory_app). 9 | 10 | -behaviour(application). 11 | 12 | %% Application callbacks 13 | -export([start/2, stop/1]). 14 | 15 | %%============================================================================= 16 | %% Application callbacks 17 | %%============================================================================= 18 | start(_StartType, _StartArgs) -> 19 | ybot_plugin_memory_sup:start_link(). 20 | 21 | stop(_State) -> 22 | ok. 23 | 24 | %%%============================================================================= 25 | %%% Internal functionality 26 | %%%============================================================================= 27 | -------------------------------------------------------------------------------- /plugins/memory/src/ybot_plugin_memory_sup.erl: -------------------------------------------------------------------------------- 1 | %%%----------------------------------------------------------------------------- 2 | %%% @author Martin Wiso 3 | %%% @doc 4 | %%% Ybot memory plugin using OTP supervisor 5 | %%% @end 6 | %%% Created : 20 Mar 2013 by tgrk 7 | %%%----------------------------------------------------------------------------- 8 | -module(ybot_plugin_memory_sup). 9 | 10 | -behaviour(supervisor). 11 | 12 | %% API 13 | -export([start_link/0]). 14 | 15 | %% Supervisor callbacks 16 | -export([init/1]). 17 | 18 | -define(CHILD(I, Type, Params), 19 | {I, {I, start_link, Params}, permanent, 5000, Type, [I]}). 20 | 21 | %%============================================================================= 22 | %% API functions 23 | %%============================================================================= 24 | start_link() -> 25 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 26 | 27 | %%============================================================================= 28 | %% Supervisor callbacks 29 | %%============================================================================= 30 | init([]) -> 31 | {ok, {{one_for_one, 5, 10}, []}}. 32 | 33 | %%%============================================================================ 34 | %%% Internal functions 35 | %%%============================================================================ -------------------------------------------------------------------------------- /plugins/notifications/ackbar.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # 4 | # Ybot show an Admiral Ackbar 5 | # Usage: Ybot ackbar 6 | # 7 | 8 | url_list = ["http://dayofthejedi.com/wp-content/uploads/2011/03/171.jpg", 9 | "http://dayofthejedi.com/wp-content/uploads/2011/03/152.jpg", 10 | "http://farm4.static.flickr.com/3572/3637082894_e23313f6fb_o.jpg", 11 | "http://www.youtube.com/watch?v=dddAi8FF3F4", 12 | "http://6.asset.soup.io/asset/0610/8774_242b_500.jpeg", 13 | "http://files.g4tv.com/ImageDb3/279875_S/steampunk-ackbar.jpg", 14 | "http://farm6.staticflickr.com/5126/5725607070_b80e61b4b3_z.jpg", 15 | "http://www.x929.ca/shows/newsboy/wp-content/uploads/admiral-ackbar-mouse-trap.jpg", 16 | "http://farm6.static.flickr.com/5291/5542027315_ba79daabfb.jpg", 17 | "http://farm5.staticflickr.com/4074/4751546688_5c76b0e308_z.jpg", 18 | "http://farm6.staticflickr.com/5250/5216539895_09f963f448_z.jpg"] 19 | 20 | puts url_list[rand(url_list.length)] -------------------------------------------------------------------------------- /plugins/ping.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/env python 2 | 3 | print "PONG" -------------------------------------------------------------------------------- /plugins/pugme.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # 4 | # Ybot pugme plugin 5 | # Usage: Ybot pugme 6 | # 7 | 8 | require 'net/http' 9 | require 'json' 10 | 11 | uri = URI('http://pugme.herokuapp.com/random') 12 | res = Net::HTTP.get_response(uri) 13 | 14 | # get url 15 | result = JSON.parse(res.body)['pug'] 16 | 17 | # return url 18 | puts result 19 | -------------------------------------------------------------------------------- /plugins/ruby.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Eval simple ruby expression Ybot plugin 3 | # 4 | # Usage: 5 | # 6 | # Ybot ruby "some_string".length 7 | 8 | if ARGV.length != 1 9 | puts 'Wrong argument\nUsage: Ybot ruby "some_string".length' 10 | exit 11 | end 12 | 13 | begin 14 | # Get argument 15 | expr = ARGV[0] 16 | # return 17 | puts eval(expr) 18 | rescue 19 | puts 'Wrong expression' 20 | end -------------------------------------------------------------------------------- /plugins/shorten_url.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # 4 | # Ybot url shortener plugin 5 | # Usage: 6 | # 7 | # Ybot short_url https://twitter.com/ 8 | # 9 | 10 | import sys 11 | import urllib 12 | import urllib2 13 | import simplejson as json 14 | 15 | if len(sys.argv) != 2: 16 | print 'Wrong arguments\nUsage: Ybot short_url https://twitter.com/' 17 | exit(0) 18 | 19 | # Get url from command args 20 | url = sys.argv[1] 21 | # Google api url 22 | api_url = "https://www.googleapis.com/urlshortener/v1/url" 23 | # Request headers 24 | header = { "Content-Type": "application/json" } 25 | # Request body params 26 | params = { "longUrl": url } 27 | 28 | # Make request 29 | response = urllib2.urlopen(urllib2.Request(api_url, json.dumps(params), header)) 30 | # Got json data 31 | json_data = response.read() 32 | 33 | # Return shorten url 34 | print json.loads(json_data)['id'] -------------------------------------------------------------------------------- /plugins/today?.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/sh 2 | 3 | # 4 | # Ybot today plugin. Show curent day of the week. 5 | # 6 | 7 | case $( uname -s ) in 8 | Darwin) echo echo `date "+%A"`;; 9 | *) echo `date --date=${dateinfile#?_} "+%A"`;; 10 | esac 11 | -------------------------------------------------------------------------------- /plugins/translate.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # 4 | # Ybot translate text with Google translate 5 | # Usage => Ybot translate en/gb some text 6 | # 7 | 8 | require 'uri' 9 | require 'net/http' 10 | 11 | # check argument 12 | if ARGV.length != 1 13 | puts 'Wrong usage. Usage => ybot: translate en/de some text' 14 | exit 15 | end 16 | 17 | languages = ["af","sq","ar", "az", "eu", "bn","be","bg","ca","zh-CN","zh-TW","hr","cs","da","nl","en","eo","et","tl","fi","fr","gl", 18 | "ka","de","el","gu","ht","iw","hi","hu","is","id","ga","it","ja","kn","ko","la","lv","lt","mk","ms","mt","no","fa","pl", 19 | "pt","ro","ru","sr","sk","sl","es","sw","sv","ta","te","th","tr","uk","ur","vi","cy","yi"] 20 | 21 | # get arg 22 | arg = ARGV[0] 23 | 24 | # get from lang 25 | from = arg.split(' ')[0].split('/')[0] 26 | 27 | # get to lang 28 | to = arg.split(' ')[0].split('/')[1] 29 | 30 | if (languages.include? from) == false 31 | puts 'This language ' + from + ' is not supported' 32 | exit 33 | end 34 | 35 | if (languages.include? to) == false 36 | puts 'This language ' + to + ' is not supported' 37 | exit 38 | end 39 | 40 | # get text to translate 41 | text = arg.split(to)[1].lstrip 42 | 43 | # Make request 44 | uri = URI.parse('http://translate.google.com/?text=' + URI::escape(text.to_s) + '&hl=' + to.to_s + '&langpair=' + from.to_s) 45 | http = Net::HTTP.new(uri.host, uri.port) 46 | request = Net::HTTP::Get.new(uri.request_uri) 47 | 48 | # parse response and return translated text 49 | body = http.request(request).body 50 | puts body.split('TRANSLATED_TEXT=\'')[1].split('\'')[0] 51 | -------------------------------------------------------------------------------- /plugins/url.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Url encode/decode with Ybot 3 | * 4 | * Usage: 5 | * Ybot url encode some_url 6 | * Ybot url decode some_url 7 | */ 8 | 9 | import java.net.URLEncoder 10 | import java.net.URLDecoder 11 | 12 | object Url { 13 | def main(args: Array[String]) { 14 | // check params 15 | if (args.length != 1) { 16 | println("Wrong usage\nUsage:\nYbot url encode some_url\nYbot url decode some_url") 17 | } 18 | // get method 19 | val method = args(0).trim().split(" ")(0) 20 | // match method 21 | method.trim match { 22 | case "encode" => encode(args(0)) 23 | case "decode" => decode(args(0)) 24 | case _ => println("Wrong usage\nUsage:\nYbot url encode some_url\nYbot url decode some_url " + method.trim) 25 | } 26 | } 27 | 28 | def encode(url : String) : Unit = { 29 | // encode url 30 | println(java.net.URLEncoder.encode(url.split("encode")(1).trim(), "utf8")) 31 | } 32 | 33 | def decode(url : String) : Unit = { 34 | // decode url 35 | println(java.net.URLDecoder.decode(url.split("decode")(1).trim(), "utf8")) 36 | } 37 | } -------------------------------------------------------------------------------- /plugins/wat.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # 4 | # Ybot WAT plugin 5 | # Usage: Ybot wat 6 | # 7 | 8 | require 'net/http' 9 | require 'json' 10 | 11 | uri = URI('http://watme.herokuapp.com/random') 12 | res = Net::HTTP.get_response(uri) 13 | 14 | # get url 15 | result = JSON.parse(res.body) 16 | 17 | # return url 18 | puts result["wat"] 19 | -------------------------------------------------------------------------------- /priv/skype.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import sys, os 4 | import time 5 | import urllib2 6 | import urllib 7 | import thread 8 | 9 | import Skype4Py 10 | 11 | # get host 12 | host = sys.argv[1] 13 | # get port 14 | port = sys.argv[2] 15 | 16 | """ 17 | Init skype bot 18 | """ 19 | class SkypeBot(object): 20 | # init skype connection 21 | def __init__(self): 22 | self.Skype = Skype4Py.Skype() 23 | self.Skype.Attach() 24 | self.Skype.OnMessageStatus = self.MessageStatus 25 | 26 | # get incoming message 27 | def MessageStatus(self, msg, status): 28 | if status == Skype4Py.cmsReceived: 29 | try: 30 | if (msg == 'name?'): 31 | msg.Chat.SendMessage(self.Skype.CurrentUser.Handle) 32 | # Check message 33 | elif msg.Body.split(' ')[0] == self.Skype.CurrentUser.Handle: 34 | # send request to Ybot 35 | response = urllib2.urlopen(host + ':' + str(port), data = urllib.urlencode({msg.Body : msg.Body})) 36 | # send response 37 | msg.Chat.SendMessage(response.read()) 38 | except URLError: 39 | exit(0) 40 | 41 | # create bot 42 | bot = SkypeBot() 43 | 44 | # Send ping to Ybot every minute 45 | # if ping failed, exit from script. 46 | def host_ping(): 47 | try: 48 | urllib2.urlopen(host + ':' + str(port)) 49 | except: 50 | os._exit(1) 51 | time.sleep(30) 52 | host_ping() 53 | 54 | thread.start_new_thread(host_ping, ()) 55 | 56 | while True: 57 | time.sleep(1) 58 | -------------------------------------------------------------------------------- /priv/webadmin/css/ybot.css: -------------------------------------------------------------------------------- 1 | a{ 2 | cursor: pointer; 3 | } 4 | label{ 5 | font-size: 17px; 6 | } 7 | li{ 8 | font-size: 17px; 9 | } 10 | .hero-unit{ 11 | position: fixed; 12 | left: 340px; 13 | top : 4%; 14 | right: 0%; 15 | bottom: 0%; 16 | margin-bottom:0%; 17 | } 18 | 19 | #menu ul{ 20 | margin-left : 0%; 21 | list-style: none; 22 | } 23 | 24 | #menu li{ 25 | display: inline; 26 | } 27 | 28 | .sidebar-nav{ 29 | width: 300px; 30 | top: 4%; 31 | left:0px; 32 | position:fixed; 33 | } -------------------------------------------------------------------------------- /priv/webadmin/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Ybot web interface 5 | 6 | 7 | 8 | 9 | 10 | 11 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 |
33 |
34 |
35 | 36 |
37 |
38 | 39 |
40 |
41 | 42 |
43 |
44 | 45 |
46 |
47 | 48 |
49 |
50 | 51 |
52 |
53 |

54 | Hello, i'm Ybot chat robot. Choose any of menu's item from side bar and start to configure me. 55 |

56 | 57 |

What can you do with Ybot:

58 |
    59 |
  • Chat bot with many different plugins.
  • 60 |
  • Bridge between http and differnt chat systems like irc, xmpp and etc...
  • 61 |
  • Notification chat bot. See @erlnews twitter bot.
  • 62 |
  • Email bot.
  • 63 |
  • Logging (see lager_ybot_backend).
  • 64 |
  • And many many more...
  • 65 |
66 |
67 |
68 |
69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /priv/webadmin/js/ybot.js: -------------------------------------------------------------------------------- 1 | var app = angular.module('ybot', ['ui.bootstrap']); 2 | 3 | var req_url = window.location.pathname + 'admin'; 4 | 5 | /* 6 | * This is main angular controller for Ybot web interface 7 | */ 8 | function YbotController ($scope, $location, $http) { 9 | 10 | $scope.home = function(){ 11 | $scope.selection = 'home'; 12 | $scope.header = 'Ybot configuration' 13 | 14 | var data = {'method' : 'get_start_page', 'params' : []}; 15 | 16 | // 17 | // Send request for front page 18 | // 19 | $http({'method' : 'POST', 'url' : '/admin', 'data' : data}).success(function (data) { 20 | $scope.transports = data.transport.split('\n').splice(0, data.transport.split('\n').length - 1); 21 | $scope.plugins = data.plugins.split('\n').splice(0, data.plugins.split('\n').length - 1); 22 | $scope.is_history = data.is_history; 23 | $scope.history_limit = data.history_limit; 24 | $scope.is_observer = data.is_observer; 25 | $scope.observer_timeout = data.observer_timeout; 26 | $scope.storage_type = data.storage_type; 27 | }); 28 | } 29 | 30 | $scope.plugnis_obs_history = function(){ 31 | $scope.selection = 'observer_and_history'; 32 | 33 | // 34 | // Update history settings 35 | // 36 | $scope.update_history_settings = function(historyTimeout){ 37 | var is_history = document.getElementById("history_checked").checked; 38 | // request body 39 | var data = {'method' : 'update_history', 'params' : {'timeout' : historyTimeout, 'is_history' : is_history}}; 40 | // 41 | // Send request for updating history settings 42 | // 43 | $http({'method' : 'POST', 'url' : '/admin', 'data' : data}).success(function (data) {}); 44 | } 45 | 46 | // 47 | // Update observer settings 48 | // 49 | $scope.update_observer_settings = function(observerTimeout){ 50 | var is_observer = document.getElementById("observer_checked").checked; 51 | // request body 52 | var data = {'method' : 'update_observer', 'params' : {'timeout' : observerTimeout, 'is_observer' : is_observer}}; 53 | // 54 | // Send request for updating observer settings 55 | // 56 | $http({'method' : 'POST', 'url' : '/admin', 'data' : data}).success(function (data) {}); 57 | } 58 | } 59 | 60 | $scope.upload_plugin = function(){ 61 | $scope.selection = 'upload_plugin'; 62 | 63 | // 64 | // upload new plugin 65 | // 66 | $scope.upload_new_plugin = function(newPluginPath){ 67 | var data = {'method' : 'upload_plugin', 'params' : {'upload_plugin_path' : newPluginPath}} 68 | $http({'method' : 'POST', 'url' : '/admin', 'data' : data}).success(function (data) {}); 69 | } 70 | } 71 | 72 | $scope.transport = function(){ 73 | $scope.selection = 'transport'; 74 | 75 | var data = {'method' : 'get_runned_transports', 'params' : []}; 76 | $http({'method' : 'POST', 'url' : '/admin', 'data' : data}).success(function (data) { 77 | $scope.runned_transports = data.transport.split('\n').splice(0, data.transport.split('\n').length - 1); 78 | }); 79 | } 80 | 81 | $scope.activate_new_transport = function(){ 82 | $scope.selection = 'activate_transport'; 83 | 84 | $scope.start_irc = function(nick, password, channel, channel_key, host, port, ssl, timeout){ 85 | var data = {'method' : 'start_irc', 'params' : {'irc_login' : nick, 'irc_password': password, 86 | 'irc_channel': channel, 'irc_channel_key' : channel_key, 87 | 'irc_server_host': host, 'irc_server_port' : port, 88 | 'irc_use_ssl' : ssl, 'irc_reconnect_timeout' : timeout}}; 89 | $http({'method' : 'POST', 'url' : '/admin', 'data' : data}).success(function (data) {}); 90 | } 91 | 92 | $scope.start_xmpp = function(jabber_login, jabber_password, jabber_room, jabber_nick, jabber_server, jabber_resource, jabber_port, 93 | jabber_ssl, jabber_reconnect_timeout){ 94 | var data = {'method' : 'start_xmpp', params : {'xmpp_login' : jabber_login, 'xmpp_password': jabber_password, 95 | 'xmpp_room':jabber_room, 'xmpp_nick':jabber_nick, 'xmpp_server': jabber_server, 96 | 'xmpp_resource':jabber_resource, 'xmpp_port':jabber_port, 97 | 'xmpp_ssl':jabber_ssl, 'xmpp_reconnect_timeout':jabber_reconnect_timeout}}; 98 | $http({'method' : 'POST', 'url' : '/admin', 'data' : data}).success(function (data) {}); 99 | } 100 | 101 | $scope.start_campfire = function(campfire_nick, campfire_token, campfire_room_id, campfire_subdomain, campfire_reconnect_timeout){ 102 | var data = {'method' : 'start_campfire', params : {'login':campfire_nick, 'token':campfire_token, 103 | 'room':campfire_room_id, 'subdomain':campfire_subdomain, 104 | 'reconnect_timeout': campfire_reconnect_timeout}}; 105 | $http({'method' : 'POST', 'url' : '/admin', 'data' : data}).success(function (data) {}); 106 | } 107 | 108 | $scope.start_hipchat = function(hipchat_jid, hipchat_password, hipchat_room, hipchat_nick, hipchat_reconnect_timeout){ 109 | var data = {'method' : 'start_hipchat', params : {'hipchat_jid' : hipchat_jid, 'hipchat_password' : hipchat_password, 110 | 'hipchat_room' : hipchat_room, 'hipchat_nick':hipchat_nick, 111 | 'hipchat_reconnect_timeout' : hipchat_reconnect_timeout}}; 112 | $http({'method' : 'POST', 'url' : '/admin', 'data' : data}).success(function (data) {}); 113 | } 114 | 115 | $scope.start_skype = function(skype_http_host, skype_http_port){ 116 | var data = {'method' : 'start_skype', params : {'skype_http_host' : skype_http_host, 'skype_http_port' : skype_http_port}}; 117 | $http({'method' : 'POST', 'url' : '/admin', 'data' : data}).success(function (data) {}); 118 | } 119 | 120 | $scope.start_http = function(http_host, http_port, http_bot_nick){ 121 | var data = {'method' : 'start_http', params : {'http_host':http_host,'http_port':http_port,'http_bot_nick':http_bot_nick}}; 122 | $http({'method' : 'POST', 'url' : '/admin', 'data' : data}).success(function (data) {}); 123 | } 124 | 125 | $scope.start_flowdock = function(flowdock_nick, flowdock_login, flowdock_password, flowdock_org, flowdock_flow){ 126 | var data = {'method' : 'start_flowdock', params : {'flowdock_nick':flowdock_nick,'flowdock_login':flowdock_login, 127 | 'flowdock_password':flowdock_password, 'flowdock_org':flowdock_org, 128 | 'flowdock_flow':flowdock_flow}}; 129 | $http({'method' : 'POST', 'url' : '/admin', 'data' : data}).success(function (data) {}); 130 | } 131 | 132 | $scope.start_talkerapp = function(talkerapp_nick, talkerapp_room, talkerapp_token){ 133 | var data = {'method' : 'start_talkerapp', params : {'talkerapp_nick' : talkerapp_nick,'talkerapp_room': talkerapp_room, 134 | 'talkerapp_token':talkerapp_token}}; 135 | $http({'method' : 'POST', 'url' : '/admin', 'data' : data}).success(function (data) {}); 136 | } 137 | } 138 | 139 | $scope.storage = function(){ 140 | $scope.selection = 'storage'; 141 | 142 | var data = {'method' : 'get_storage_info', params : []}; 143 | $http({'method' : 'POST', 'url' : '/admin', 'data' : data}).success(function (data) { 144 | $scope.storage_http_host = data.storage_host; 145 | $scope.storage_http_port = data.storage_port; 146 | }); 147 | 148 | } 149 | }; -------------------------------------------------------------------------------- /priv/webadmin/views/activate_transport.html: -------------------------------------------------------------------------------- 1 |
2 |

Start new transport

3 | 4 | 5 | 6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 26 |
27 |
28 | 29 | 30 |
31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 |
50 | 52 |
53 |
54 | 55 | 56 |
57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 |
68 | 69 |
70 |
71 | 72 | 73 |
74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 |
85 | 86 |
87 |
88 | 89 | 90 |
91 | 92 | 93 | 94 | 95 |
96 | 97 |
98 |
99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 |
108 | 109 |
110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 |
123 | 124 |
125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 |
134 | 135 |
136 |
137 |
-------------------------------------------------------------------------------- /priv/webadmin/views/home.html: -------------------------------------------------------------------------------- 1 |

{{header}}

2 |
3 | 4 |
5 |

Transports

6 |
    7 |
  • 8 | {{transport}} 9 |
  • 10 |
11 |
12 |

Plugins

13 |
    14 |
  • 15 | {{plugin}} 16 |
  • 17 |
18 |
19 |

History and Plugin observer

20 | 21 | 22 | 23 | 24 |
25 |

Ybot storage

26 | 27 |
-------------------------------------------------------------------------------- /priv/webadmin/views/observer.html: -------------------------------------------------------------------------------- 1 |
2 |

Plugin observer settings

3 |
-------------------------------------------------------------------------------- /priv/webadmin/views/sidebar.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /priv/webadmin/views/storage.html: -------------------------------------------------------------------------------- 1 |
2 |

Ybot backend storage

3 | 4 | 5 | 6 |
-------------------------------------------------------------------------------- /priv/webadmin/views/transport.html: -------------------------------------------------------------------------------- 1 |
2 |

Transports

3 |
    4 |
  • 5 | {{runned_transport}} 6 |
  • 7 |
8 |
-------------------------------------------------------------------------------- /priv/webadmin/views/upload_plugin.html: -------------------------------------------------------------------------------- 1 |
2 |

Upload plugins

3 | 4 | 5 |
6 | 7 |
-------------------------------------------------------------------------------- /rebar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OtpChatBot/Ybot/5ce05fea0eb9001d1c0ff89702729f4c80743872/rebar -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | %% -*- mode: Erlang; -*- 2 | 3 | {erl_opts, [ 4 | %% Add debug_info for debugging 5 | debug_info, 6 | 7 | %% Lager 8 | {parse_transform, lager_transform}, 9 | 10 | %% Fail on warning 11 | %% warnings_as_errors, 12 | 13 | %% Causes warnings to be emitted for malformed format 14 | %% strings as arguments to io:format and similar functions. 15 | warn_format, 16 | 17 | %% Causes a warning to be emitted if the export_all option has also been given. 18 | warn_export_all, 19 | 20 | %% Causes warnings to be emitted for all implicitly exported 21 | %% variables referred to after the primitives where they were first defined. 22 | warn_export_vars, 23 | 24 | %% Causes warnings to be emitted for "fresh" variables in functional 25 | %% objects or list comprehensions with the same name as some already defined variable. 26 | warn_shadow_vars, 27 | 28 | %% Causes warnings to be emitted for calls to old type testing BIFs 29 | warn_obsolete_guard 30 | ]}. 31 | 32 | %% Subdirectories? 33 | {sub_dirs, ["plugins/*"]}. 34 | 35 | %% Dependencies 36 | {deps, [ 37 | {lager, "1.2.1", 38 | {git, "git://github.com/basho/lager.git", {tag, "1.2.1"}}}, 39 | {ibrowse, "4.0.1", 40 | {git, "git://github.com/cmullaparthi/ibrowse.git", {tag, "v4.0.1"}}}, 41 | {reloader, "0.1", 42 | {git, "http://github.com/bjnortier/reloader.git", "master"}}, 43 | {cowboy, ".*", 44 | {git, "git://github.com/extend/cowboy.git", "master"}}, 45 | {mimetypes, ".*", 46 | {git, "git://github.com/spawngrid/mimetypes.git", "master"}}, 47 | {jiffy, ".*", 48 | {git, "git://github.com/davisp/jiffy.git", "master"}}, 49 | {gen_smtpc, ".*", 50 | {git, "git://github.com/0xAX/gen_smtpc.git", "master"}} 51 | ]}. 52 | -------------------------------------------------------------------------------- /src/channel/email/ybot_mail_client.erl: -------------------------------------------------------------------------------- 1 | %%%----------------------------------------------------------------------------- 2 | %%% @author 0xAX 3 | %%% @doc 4 | %%% Ybot mail client. 5 | %%% @end 6 | %%%----------------------------------------------------------------------------- 7 | -module(ybot_mail_client). 8 | 9 | -behaviour(gen_server). 10 | 11 | %% api 12 | -export([start_link/4, send_message/2]). 13 | 14 | %% gen_server callbacks 15 | -export([init/1, 16 | handle_call/3, 17 | handle_cast/2, 18 | handle_info/2, 19 | terminate/2, 20 | code_change/3]). 21 | 22 | -record(state, { 23 | from = <<>>, 24 | fromPassword = <<>>, 25 | to = [], 26 | connectionOptions = [] 27 | }). 28 | 29 | start_link(From, FromPassword, To, Options) -> 30 | gen_server:start_link(?MODULE, [From, FromPassword, To, Options], []). 31 | 32 | %% @doc send mail message 33 | -spec send_message(SmtpClientPid :: pid(), Messsage :: string()) -> ok. 34 | send_message(SmtpClientPid, Messsage) -> 35 | % send message 36 | gen_server:cast(SmtpClientPid, {send_message, "", Messsage}). 37 | 38 | init([From, FromPassword, To, Options]) -> 39 | % init interanal state 40 | {ok, #state{from = From, fromPassword = FromPassword, to =To, connectionOptions = Options}}. 41 | 42 | handle_call(_Request, _From, State) -> 43 | {reply, ignored, State}. 44 | 45 | handle_cast({send_message, _From, Messsage}, State) -> 46 | % send mail message 47 | lists:foreach(fun(To) -> 48 | gen_smtpc:send({binary_to_list(State#state.from), binary_to_list(State#state.fromPassword)}, 49 | To, "Message from Ybot chat robot", Messsage, State#state.connectionOptions) 50 | end, 51 | State#state.to), 52 | % return 53 | {noreply, State}; 54 | 55 | handle_cast(_Msg, State) -> 56 | {noreply, State}. 57 | 58 | handle_info(_Info, State) -> 59 | {noreply, State}. 60 | 61 | terminate(_Reason, _State) -> 62 | ok. 63 | 64 | code_change(_OldVsn, State, _Extra) -> 65 | {ok, State}. 66 | 67 | %% Internal functions -------------------------------------------------------------------------------- /src/channel/email/ybot_mail_client_sup.erl: -------------------------------------------------------------------------------- 1 | %%%----------------------------------------------------------------------------- 2 | %%% @author 0xAX 3 | %%% @doc 4 | %%% Ybot smtp client process supervisor. 5 | %%% @end 6 | %%%----------------------------------------------------------------------------- 7 | -module(ybot_mail_client_sup). 8 | 9 | -behaviour(supervisor). 10 | 11 | %% API 12 | -export([start_link/0, start_smtp_client/4]). 13 | 14 | %% Supervisor callbacks 15 | -export([init/1]). 16 | 17 | 18 | %% =================================================================== 19 | %% API functions 20 | %% =================================================================== 21 | 22 | start_link() -> 23 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 24 | 25 | %% @doc Start new smtp client 26 | %% @end 27 | -spec start_smtp_client(From :: binary(), FromPassword :: binary(), To :: [binary()], Options :: [{atom(), any()}]) 28 | -> {ok, Pid :: pid()} | {error, Reason :: term()}. 29 | start_smtp_client(From, FromPassword, To, Options) -> 30 | supervisor:start_child(?MODULE, [From, FromPassword, To, Options]). 31 | 32 | init([]) -> 33 | ChildSpec = [ 34 | 35 | {ybot_mail_client, 36 | {ybot_mail_client, start_link, []}, 37 | temporary, 2000, worker, [] 38 | } 39 | ], 40 | 41 | % init 42 | {ok,{{simple_one_for_one, 10, 60}, ChildSpec}}. -------------------------------------------------------------------------------- /src/channel/twitter/ybot_twitter_client.erl: -------------------------------------------------------------------------------- 1 | %%%----------------------------------------------------------------------------- 2 | %%% @author 0xAX 3 | %%% @doc 4 | %%% Ybot twitter client. 5 | %%% @end 6 | %%%----------------------------------------------------------------------------- 7 | -module(ybot_twitter_client). 8 | 9 | -behaviour(gen_server). 10 | 11 | %% api 12 | -export([start_link/4, send_message/2]). 13 | 14 | %% gen_server callbacks 15 | -export([init/1, 16 | handle_call/3, 17 | handle_cast/2, 18 | handle_info/2, 19 | terminate/2, 20 | code_change/3]). 21 | 22 | -record(state, { 23 | % consumer key 24 | consumer = <<>>, 25 | % consumer secret 26 | consumer_secret = <<>>, 27 | % access token 28 | access_token = <<>>, 29 | % access token secret 30 | access_token_secret = <<>> 31 | }). 32 | 33 | start_link(Consumer, ConsumerSecret, AccessToken, AccessTokenSecret) -> 34 | gen_server:start_link(?MODULE, [Consumer, ConsumerSecret, AccessToken, AccessTokenSecret], []). 35 | 36 | %% @doc update twitter status 37 | -spec send_message(TwitterClient :: pid(), Messsage :: string()) -> ok. 38 | send_message(TwitterClient, Messsage) -> 39 | % send message 40 | gen_server:cast(TwitterClient, {send_message, "", Messsage}). 41 | 42 | init([Consumer, ConsumerSecret, AccessToken, AccessTokenSecret]) -> 43 | % init interanal state 44 | {ok, #state{consumer = Consumer, consumer_secret = ConsumerSecret, 45 | access_token = AccessToken, access_token_secret = AccessTokenSecret}}. 46 | 47 | handle_call(_Request, _From, State) -> 48 | {reply, ignored, State}. 49 | 50 | handle_cast({send_message, _From, Messsage}, State) -> 51 | Consumer = {binary_to_list(State#state.consumer), binary_to_list(State#state.consumer_secret), hmac_sha1}, 52 | % update twitter status 53 | oauth:post("https://api.twitter.com/1.1/statuses/update.json", [{"status", Messsage}], Consumer, 54 | binary_to_list(State#state.access_token), binary_to_list(State#state.access_token_secret)), 55 | % return 56 | {noreply, State}; 57 | 58 | handle_cast(_Msg, State) -> 59 | {noreply, State}. 60 | 61 | handle_info(_Info, State) -> 62 | {noreply, State}. 63 | 64 | terminate(_Reason, _State) -> 65 | ok. 66 | 67 | code_change(_OldVsn, State, _Extra) -> 68 | {ok, State}. 69 | 70 | %% Internal functions -------------------------------------------------------------------------------- /src/channel/twitter/ybot_twitter_sup.erl: -------------------------------------------------------------------------------- 1 | %%%----------------------------------------------------------------------------- 2 | %%% @author 0xAX 3 | %%% @doc 4 | %%% Ybot twitter client process supervisor. 5 | %%% @end 6 | %%%----------------------------------------------------------------------------- 7 | -module(ybot_twitter_sup). 8 | 9 | -behaviour(supervisor). 10 | 11 | %% API 12 | -export([start_link/0, start_twitter_client/4]). 13 | 14 | %% Supervisor callbacks 15 | -export([init/1]). 16 | 17 | 18 | %% =================================================================== 19 | %% API functions 20 | %% =================================================================== 21 | 22 | start_link() -> 23 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 24 | 25 | %% @doc Start new twitter client 26 | %% @end 27 | -spec start_twitter_client(Consumer :: binary(), ConsumerSecret :: binary(), AccessToken :: binary(), AccessTokenSecret :: binary()) -> 28 | {ok, Pid :: pid()} | {error, Reason :: term()}. 29 | start_twitter_client(Consumer, ConsumerSecret, AccessToken, AccessTokenSecret) -> 30 | supervisor:start_child(?MODULE, [Consumer, ConsumerSecret, AccessToken, AccessTokenSecret]). 31 | 32 | init([]) -> 33 | ChildSpec = [ 34 | 35 | {ybot_twitter_client, 36 | {ybot_twitter_client, start_link, []}, 37 | temporary, 2000, worker, [] 38 | } 39 | ], 40 | 41 | % init 42 | {ok,{{simple_one_for_one, 10, 60}, ChildSpec}}. -------------------------------------------------------------------------------- /src/transport/campfire/README.md: -------------------------------------------------------------------------------- 1 | Ybot campfire transport 2 | ======================== 3 | 4 | This is [campfire](http://campfirenow.com/) transport for [Ybot](https://github.com/0xAX/Ybot) chat robot. 5 | 6 | Usage 7 | ========= 8 | 9 | For using Ybot with campfire, you must to configure it in configuration file ybot.config, something like that: 10 | 11 | ```erlang 12 | [ 13 | {ybot, 14 | [ 15 | % list of transport 16 | {transports, [ 17 | % Campfire transport 18 | {campfire, % campfire login 19 | <<"ybot">>, 20 | % campfire token 21 | <<"ybot_token">>, 22 | % campfire room id 23 | 100100, 24 | % campfire sub-domain 25 | <<"home100">>, 26 | [{reconnect_timeout, 5000}] 27 | } 28 | ] 29 | }, 30 | 31 | % Loading new plugins during work or not 32 | {checking_new_plugins, false}, 33 | % Checking new plugins timeout 34 | {checking_new_plugins_timeout, 60000}, 35 | 36 | % Save commands history 37 | {commands_history, false}, 38 | % Command history limit 39 | {history_command_limit_count, 1000}, 40 | 41 | % plugins directory path 42 | {plugins_path, "plugins/"} 43 | ] 44 | } 45 | ]. 46 | ``` 47 | 48 | Author 49 | ======================== 50 | 51 | If you have any questions/suggestions, write me at twitter to [0xAX](https://twitter.com/0xAX) -------------------------------------------------------------------------------- /src/transport/campfire/campfire_client.erl: -------------------------------------------------------------------------------- 1 | %%%----------------------------------------------------------------------------- 2 | %%% @author 0xAX 3 | %%% @doc 4 | %%% Ybot campfire client. 5 | %%% @end 6 | %%%----------------------------------------------------------------------------- 7 | -module(campfire_client). 8 | 9 | -behaviour(gen_server). 10 | 11 | -export([start_link/5]). 12 | 13 | %% gen_server callbacks 14 | -export([init/1, 15 | handle_call/3, 16 | handle_cast/2, 17 | handle_info/2, 18 | terminate/2, 19 | code_change/3]). 20 | 21 | -record(state, { 22 | % handler module 23 | callback = null, 24 | % campfire room id 25 | room = 0, 26 | % campfire api token 27 | token = <<>>, 28 | % campfire sub_domain 29 | domain = <<>>, 30 | % request id 31 | req_id = null, 32 | % reconnect timeout 33 | reconnect_timeout = 0 :: integer() 34 | }). 35 | 36 | start_link(CallbackModule, Room, Token, Domain, ReconnectTimeout) -> 37 | gen_server:start_link(?MODULE, [CallbackModule, Room, Token, Domain, ReconnectTimeout], []). 38 | 39 | init([CallbackModule, Room, Token, Domain, ReconnectTimeout]) -> 40 | % Start http stream 41 | gen_server:cast(self(), stream), 42 | % init state 43 | {ok, #state{callback = CallbackModule, room = Room, token = Token, domain = Domain, reconnect_timeout = ReconnectTimeout}}. 44 | 45 | handle_call(_Request, _From, State) -> 46 | {reply, ignored, State}. 47 | 48 | handle_cast({send_message, _From, Message}, State) -> 49 | MessageList = string:tokens(Message, "\n"), 50 | % Send message 51 | lists:foreach(fun(M) -> 52 | % Make url 53 | Url = "https://" ++ binary_to_list(State#state.domain) ++ ".campfirenow.com/room/" ++ integer_to_list(State#state.room) ++ "/speak.xml", 54 | % Make message 55 | Mes = "TextMessage" ++ M ++ "", 56 | % Send message 57 | ibrowse:send_req(Url, [{"Content-Type", "application/xml"}, {basic_auth, {binary_to_list(State#state.token), "x"}}], post, Mes) 58 | end, MessageList), 59 | % return 60 | {noreply, State}; 61 | 62 | %% @doc Start http campfire stream 63 | handle_cast(stream, State) -> 64 | % First of all leave from campfire room 65 | ok = leave_room(State#state.domain, State#state.room, State#state.token), 66 | % Now try to join to the room 67 | ok = join_room(State#state.domain, State#state.room, State#state.token), 68 | % Make url for stream 69 | Url = "https://streaming.campfirenow.com/room/" ++ integer_to_list(State#state.room) ++ "/live.json", 70 | % Start campfire stream 71 | {_, ReqId} = ibrowse:send_req(Url, [{"Content-Type", "application/json"}], get, 72 | [], [{basic_auth, {binary_to_list(State#state.token), "x"}}, {stream_to, {self(), once}}], infinity), 73 | % Save request id 74 | {noreply, State#state{req_id = ReqId}}; 75 | 76 | handle_cast(_Msg, State) -> 77 | {noreply, State}. 78 | 79 | handle_info({ibrowse_async_headers, ReqId, _Status, _Headers}, State) -> 80 | % Next stream 81 | ibrowse:stream_next(ReqId), 82 | % return 83 | {noreply, State}; 84 | 85 | %% @doc 86 | handle_info({ibrowse_async_headers, ReqId, _Body}, State) -> 87 | ibrowse:stream_next(ReqId), 88 | {noreply, State}; 89 | 90 | %% @doc Timeout request error 91 | handle_info({ibrowse_async_response, _OldReqId, {error, req_timedout}}, State) -> 92 | % Make url for new Stream 93 | Url = "https://streaming.campfirenow.com/room/" ++ integer_to_list(State#state.room) ++ "/live.json", 94 | % Start new stream 95 | {_, ReqId} = ibrowse:send_req(Url, [{"Content-Type", "application/json"}], get, 96 | [], [{basic_auth, {binary_to_list(State#state.token), "x"}}, {stream_to, {self(), once}}]), 97 | % Save new request id 98 | {noreply, State#state{req_id = ReqId}}; 99 | 100 | %% @doc 101 | handle_info({ibrowse_async_response, ReqId, Data}, State) -> 102 | ok = ibrowse:stream_next(ReqId), 103 | % Parse response 104 | case Data of 105 | " " -> 106 | {noreply, State}; 107 | _ -> 108 | % Send message to handler 109 | State#state.callback ! {incoming_message, Data}, 110 | % Return state 111 | {noreply, State} 112 | end; 113 | 114 | handle_info({ibrowse_async_response_end, _ReqId}, State) -> 115 | {noreply, State}; 116 | 117 | %% @doc connection timeout 118 | handle_info({error, {conn_failed, {error, _}}}, State) -> 119 | try_reconnect(State); 120 | 121 | handle_info(_Info, State) -> 122 | {noreply, State}. 123 | 124 | terminate(_Reason, _State) -> 125 | ok. 126 | 127 | code_change(_OldVsn, State, _Extra) -> 128 | {ok, State}. 129 | 130 | %% Internal functions 131 | leave_room(Domain, Room, Token) -> 132 | % Make url 133 | Url = "https://" ++ binary_to_list(Domain) ++ ".campfirenow.com/room/" ++ integer_to_list(Room) ++ "/leave.json", 134 | % Make content type 135 | ContentType = [{"Content-Type", "application/json"}], 136 | % send leave request 137 | ibrowse:send_req(Url, ContentType, post, [], [{basic_auth, {binary_to_list(Token), "x"}}]), 138 | % return 139 | ok. 140 | 141 | %% @doc Join to room 142 | join_room(Domain, Room, Token) -> 143 | % Make url 144 | Url = "https://" ++ binary_to_list(Domain) ++ ".campfirenow.com/room/" ++ integer_to_list(Room) ++ "/join.json", 145 | % Make content type 146 | ContentType = [{"Content-Type", "application/json"}], 147 | % Send join request 148 | ibrowse:send_req(Url, ContentType, post, [], [{basic_auth, {binary_to_list(Token), "x"}}]), 149 | % return 150 | ok. 151 | 152 | %% @doc try reconnect 153 | -spec try_reconnect(State :: #state{}) -> {normal, stop, State} | {noreply, State}. 154 | try_reconnect(#state{reconnect_timeout = Timeout} = State) -> 155 | case Timeout > 0 of 156 | true -> 157 | % no need in reconnect 158 | {normal, stop, State}; 159 | false -> 160 | % sleep 161 | timer:sleep(Timeout), 162 | % Try reconnect 163 | gen_server:cast(self(), stream), 164 | % return 165 | {noreply, State} 166 | end. -------------------------------------------------------------------------------- /src/transport/campfire/campfire_handler.erl: -------------------------------------------------------------------------------- 1 | %%%----------------------------------------------------------------------------- 2 | %%% @author 0xAX 3 | %%% @doc 4 | %%% Ybot campfire incoming message handler. 5 | %%% @end 6 | %%%----------------------------------------------------------------------------- 7 | -module(campfire_handler). 8 | 9 | -behaviour(gen_server). 10 | 11 | -export([start_link/0]). 12 | 13 | %% gen_server callbacks 14 | -export([init/1, 15 | handle_call/3, 16 | handle_cast/2, 17 | handle_info/2, 18 | terminate/2, 19 | code_change/3]). 20 | 21 | -record(state, { 22 | % bot nick in campfire room 23 | campfire_nick = <<>> :: binary(), 24 | % campfire client process pid 25 | campfire_client_pid :: pid(), 26 | % parser process pid 27 | parser_pid :: pid() 28 | }). 29 | 30 | start_link() -> 31 | gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). 32 | 33 | init([]) -> 34 | {ok, #state{}}. 35 | 36 | handle_call(_Request, _From, State) -> 37 | {reply, ignored, State}. 38 | 39 | handle_cast({campfire_client, ClientPid, ParserPid, Login}, State) -> 40 | {noreply, State#state{campfire_client_pid = ClientPid, parser_pid = ParserPid, campfire_nick = Login}}; 41 | 42 | handle_cast(_Msg, State) -> 43 | {noreply, State}. 44 | 45 | handle_info({incoming_message, IncomingMessage}, State) -> 46 | % Get Ybot Nick from current chat 47 | Nick = binary_to_list(State#state.campfire_nick), 48 | % Decode json message 49 | Message = lists:flatten(string:tokens(IncomingMessage, "\r\n")), 50 | {Json} = try 51 | jiffy:decode(Message) 52 | catch _ : _ -> 53 | {""} 54 | end, 55 | % Get body 56 | case get_body(Json) of 57 | [] -> 58 | % do nothing 59 | pass; 60 | Body -> 61 | % Send message to parser 62 | gen_server:cast(State#state.parser_pid, {incoming_message, State#state.campfire_client_pid, Nick, "", binary_to_list(Body)}) 63 | end, 64 | % return 65 | {noreply, State}; 66 | 67 | handle_info(_Info, State) -> 68 | {noreply, State}. 69 | 70 | terminate(_Reason, _State) -> 71 | ok. 72 | 73 | code_change(_OldVsn, State, _Extra) -> 74 | {ok, State}. 75 | 76 | %% Internal functions 77 | get_body([]) -> 78 | []; 79 | get_body([H | Json]) -> 80 | case H of 81 | {<<"body">>, Body} -> 82 | Body; 83 | _ -> 84 | get_body(Json) 85 | end. -------------------------------------------------------------------------------- /src/transport/campfire/campfire_sup.erl: -------------------------------------------------------------------------------- 1 | %%%----------------------------------------------------------------------------- 2 | %%% @author 0xAX 3 | %%% @doc 4 | %%% Ybot campfire client root supervisor. 5 | %%% @end 6 | %%%----------------------------------------------------------------------------- 7 | -module(campfire_sup). 8 | 9 | -behaviour(supervisor). 10 | 11 | %% API 12 | -export([start_link/0, start_campfire_client/5]). 13 | 14 | %% Supervisor callbacks 15 | -export([init/1]). 16 | 17 | 18 | %% =================================================================== 19 | %% API functions 20 | %% =================================================================== 21 | 22 | start_link() -> 23 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 24 | 25 | %% @doc Start new campfire client 26 | %% @end 27 | -spec start_campfire_client(CallbackModule :: atom() | pid(), 28 | Room :: binary(), 29 | Token :: binary(), 30 | Domain :: binary(), 31 | ReconnectTimeout :: integer()) 32 | -> {ok, Pid :: pid()} | {error, Reason :: term()}. 33 | start_campfire_client(CallbackModule, Room, Token, Domain, ReconnectTimeout) -> 34 | % run new campfire client 35 | supervisor:start_child(?MODULE, [CallbackModule, Room, Token, Domain, ReconnectTimeout]). 36 | 37 | init([]) -> 38 | % campfire client 39 | ChildSpec = [ 40 | 41 | % start campfire client 42 | {campfire_client, 43 | {campfire_client, start_link, []}, 44 | temporary, 2000, worker, [] 45 | } 46 | ], 47 | 48 | % init 49 | {ok,{{simple_one_for_one, 10, 60}, ChildSpec}}. -------------------------------------------------------------------------------- /src/transport/flowdock/README.md: -------------------------------------------------------------------------------- 1 | Ybot flowdock transport 2 | ======================== 3 | 4 | This is [flowdock](https://www.flowdock.com/) transport for [Ybot](https://github.com/0xAX/Ybot) chat robot. 5 | 6 | Usage 7 | ========= 8 | 9 | For using Ybot with flowdock, you must to configure it in configuration file ybot.config, something like that: 10 | 11 | ```erlang 12 | [ 13 | {ybot, 14 | [ 15 | % list of transport 16 | {transports, [ 17 | % flowdock config 18 | {flowdock, 19 | % Nick in chat 20 | <<"ybot">>, 21 | % Flowdock login 22 | <<"ybot@gmail.com">>, 23 | % Flowdock password 24 | <<"password">>, 25 | % Flowdock organization 26 | <<"ybot_org">>, 27 | % Flow 28 | <<"ybot_flow">> 29 | } 30 | ] 31 | }, 32 | 33 | % Loading new plugins during work or not 34 | {checking_new_plugins, false}, 35 | % Checking new plugins timeout 36 | {checking_new_plugins_timeout, 60000}, 37 | 38 | % Save commands history 39 | {commands_history, false}, 40 | % Command history limit 41 | {history_command_limit_count, 1000}, 42 | 43 | % plugins directory path 44 | {plugins_path, "plugins/"} 45 | ] 46 | } 47 | ]. 48 | ``` 49 | 50 | Author 51 | ======================== 52 | 53 | If you have any questions/suggestions, write me at twitter to [0xAX](https://twitter.com/0xAX) -------------------------------------------------------------------------------- /src/transport/flowdock/flowdock_client.erl: -------------------------------------------------------------------------------- 1 | %%%----------------------------------------------------------------------------- 2 | %%% @author 0xAX 3 | %%% @doc 4 | %%% Ybot flowdock chat client. 5 | %%% @end 6 | %%%----------------------------------------------------------------------------- 7 | -module (flowdock_client). 8 | 9 | -behaviour(gen_server). 10 | 11 | -export([start_link/6]). 12 | 13 | %% gen_server callbacks 14 | -export([init/1, 15 | handle_call/3, 16 | handle_cast/2, 17 | handle_info/2, 18 | terminate/2, 19 | code_change/3]). 20 | 21 | -record(state, { 22 | % incoming message handler pid 23 | callback, 24 | % request id 25 | req_id, 26 | % flowdock organization 27 | org, 28 | % flow 29 | flow, 30 | % flowdock login 31 | login, 32 | % flowdock password 33 | password, 34 | % reconnect timeout in microseconds 35 | reconnect_timeout = 0 36 | }). 37 | 38 | start_link(Callback, FlowDockOrg, Flow, Login, Password, ReconnectTimeout) -> 39 | gen_server:start_link(?MODULE, [Callback, FlowDockOrg, Flow, Login, Password, ReconnectTimeout], []). 40 | 41 | init([Callback, FlowDockOrg, Flow, Login, Password, ReconnectTimeout]) -> 42 | % Start http stream 43 | gen_server:cast(self(), stream), 44 | % init state 45 | {ok, #state{callback = Callback, org = FlowDockOrg, flow = Flow, login = Login, password = Password, reconnect_timeout = ReconnectTimeout}}. 46 | 47 | handle_call(_Request, _From, State) -> 48 | {reply, ignored, State}. 49 | 50 | %% @doc send message to flowdock 51 | handle_cast({send_message, _From, Message}, State) -> 52 | % Make url 53 | Url = "https://api.flowdock.com/flows/" ++ binary_to_list(State#state.org) ++ "/" ++ binary_to_list(State#state.flow) ++ "/messages", 54 | % Send message 55 | ibrowse:send_req(Url, [{"Content-Type", "multipart/form-data"}, 56 | {basic_auth, {binary_to_list(State#state.login), binary_to_list(State#state.password)}}], post, 57 | ["event=message&content=" ++ Message]), 58 | % return 59 | {noreply, State}; 60 | 61 | %% @doc Start http streaming 62 | handle_cast(stream, State) -> 63 | % Make url request 64 | Url = "https://stream.flowdock.com/flows/" ++ binary_to_list(State#state.org) ++ "/" ++ binary_to_list(State#state.flow) ++ "?active=true", 65 | % Send request 66 | {_, ReqId} = ibrowse:send_req(Url, [{"Content-Type", "application/json"}], get, [], 67 | [{basic_auth, {binary_to_list(State#state.login), binary_to_list(State#state.password)}}, {stream_to, {self(), once}}], infinity), 68 | % save request id and return 69 | {noreply, State#state{req_id = ReqId}}; 70 | 71 | handle_cast(_Msg, State) -> 72 | {noreply, State}. 73 | 74 | handle_info({ibrowse_async_headers, ReqId, _Status, _Headers}, State) -> 75 | % Next stream 76 | ibrowse:stream_next(ReqId), 77 | % return 78 | {noreply, State}; 79 | 80 | handle_info({ibrowse_async_response, _OldReqId, {error, req_timedout}}, State) -> 81 | % Make url request 82 | Url = "https://stream.flowdock.com/flows/" ++ binary_to_list(State#state.org) ++ "/" ++ binary_to_list(State#state.flow) ++ "?active=true", 83 | % Send request 84 | {_, ReqId} = ibrowse:send_req(Url, [{"Content-Type", "application/json"}], get, [], 85 | [{basic_auth, {binary_to_list(State#state.login), binary_to_list(State#state.password)}}, {stream_to, {self(), once}}], infinity), 86 | % Save new request id 87 | {noreply, State#state{req_id = ReqId}}; 88 | 89 | %% @doc Get incoming message from flowdock chat 90 | handle_info({ibrowse_async_response, ReqId, Data}, State) -> 91 | ok = ibrowse:stream_next(ReqId), 92 | % Parse response 93 | case Data of 94 | " " -> 95 | % Do nothing 96 | ok; 97 | _ -> 98 | % Try to decode json incoming data 99 | TryJsonDecode = try 100 | jiffy:decode(Data) 101 | catch _ : _ -> 102 | "" 103 | end, 104 | % Check data 105 | case TryJsonDecode of 106 | "" -> 107 | ok; 108 | % this is json incoming message 109 | _ -> 110 | % Get json 111 | {Json} = TryJsonDecode, 112 | case lists:member({<<"event">>, <<"message">>}, Json) of 113 | true -> 114 | % this is incoming message. 115 | {_, IncomingMessage} = lists:keyfind(<<"content">>, 1, Json), 116 | % send it to handler 117 | State#state.callback ! {incoming_message, IncomingMessage}; 118 | _ -> 119 | % do nothing 120 | pass 121 | end 122 | end 123 | end, 124 | % return state 125 | {noreply, State}; 126 | 127 | handle_info({ibrowse_async_headers, ReqId, _Body}, State) -> 128 | ibrowse:stream_next(ReqId), 129 | {noreply, State}; 130 | 131 | handle_info({ibrowse_async_response_end, _ReqId}, State) -> 132 | {noreply, State}; 133 | 134 | %% @doc connection timeout 135 | handle_info({error, {conn_failed, {error, _}}}, State) -> 136 | % try to reconnect or exit 137 | try_reconnect(State); 138 | 139 | handle_info(_Info, State) -> 140 | {noreply, State}. 141 | 142 | terminate(_Reason, _State) -> 143 | ok. 144 | 145 | code_change(_OldVsn, State, _Extra) -> 146 | {ok, State}. 147 | 148 | %% @doc try reconnect 149 | -spec try_reconnect(State :: #state{}) -> {normal, stop, State} | {noreply, State}. 150 | try_reconnect(#state{reconnect_timeout = Timeout} = State) -> 151 | case Timeout > 0 of 152 | true -> 153 | % no need in reconnect 154 | {normal, stop, State}; 155 | false -> 156 | % sleep 157 | timer:sleep(Timeout), 158 | % Try reconnect 159 | gen_server:cast(self(), stream), 160 | % return 161 | {noreply, State} 162 | end. -------------------------------------------------------------------------------- /src/transport/flowdock/flowdock_handler.erl: -------------------------------------------------------------------------------- 1 | %%%----------------------------------------------------------------------------- 2 | %%% @author 0xAX 3 | %%% @doc 4 | %%% Ybot flowdock incoming message handler. 5 | %%% @end 6 | %%%----------------------------------------------------------------------------- 7 | 8 | -module(flowdock_handler). 9 | 10 | -behaviour(gen_server). 11 | 12 | -export([start_link/0]). 13 | 14 | %% gen_server callbacks 15 | -export([init/1, 16 | handle_call/3, 17 | handle_cast/2, 18 | handle_info/2, 19 | terminate/2, 20 | code_change/3]). 21 | 22 | -record(state, { 23 | % bot nick in handler room 24 | flowdock_nick = <<>> :: binary(), 25 | % flowdock client process pid 26 | flowdock_client_pid :: pid(), 27 | % process parser pid 28 | parser_pid :: pid() 29 | }). 30 | 31 | start_link() -> 32 | gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). 33 | 34 | init([]) -> 35 | {ok, #state{}}. 36 | 37 | handle_call(_Request, _From, State) -> 38 | {reply, ignored, State}. 39 | 40 | handle_cast({flowdock_client, ClientPid, ParserPid, Login}, State) -> 41 | {noreply, State#state{flowdock_client_pid = ClientPid, parser_pid = ParserPid, flowdock_nick = Login}}; 42 | 43 | handle_cast(_Msg, State) -> 44 | {noreply, State}. 45 | 46 | %% @doc handle incoming message 47 | handle_info({incoming_message, IncomingMessage}, State) -> 48 | % Get Ybot Nick from current chat 49 | Nick = binary_to_list(State#state.flowdock_nick), 50 | % Send message to parser 51 | gen_server:cast(State#state.parser_pid, {incoming_message, State#state.flowdock_client_pid, Nick, "", binary_to_list(IncomingMessage)}), 52 | % return 53 | {noreply, State}; 54 | 55 | handle_info(_Info, State) -> 56 | {noreply, State}. 57 | 58 | terminate(_Reason, _State) -> 59 | ok. 60 | 61 | code_change(_OldVsn, State, _Extra) -> 62 | {ok, State}. 63 | 64 | %% Internal functions -------------------------------------------------------------------------------- /src/transport/flowdock/flowdock_sup.erl: -------------------------------------------------------------------------------- 1 | %%%----------------------------------------------------------------------------- 2 | %%% @author 0xAX 3 | %%% @doc 4 | %%% Ybot flowdock root supervisor. 5 | %%% @end 6 | %%%----------------------------------------------------------------------------- 7 | -module(flowdock_sup). 8 | 9 | -behaviour(supervisor). 10 | 11 | %% API 12 | -export([start_link/0, start_flowdock_client/6]). 13 | 14 | %% Supervisor callbacks 15 | -export([init/1]). 16 | 17 | 18 | %% =================================================================== 19 | %% API functions 20 | %% =================================================================== 21 | 22 | start_link() -> 23 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 24 | 25 | %% @doc Start new flowdock client 26 | %% @end 27 | -spec start_flowdock_client(CallbackModule :: atom() | pid(), 28 | FlowDockOrg :: binary(), 29 | Flow :: binary(), 30 | Login :: binary(), 31 | Password :: binary(), 32 | ReconnectTimeout :: integer()) 33 | -> {ok, Pid :: pid()} | {error, Reason :: term()}. 34 | start_flowdock_client(CallbackModule, FlowDockOrg, Flow, Login, Password, ReconnectTimeout) -> 35 | % run new flowdock client 36 | supervisor:start_child(?MODULE, [CallbackModule, FlowDockOrg, Flow, Login, Password, ReconnectTimeout]). 37 | 38 | init([]) -> 39 | % flowdock client 40 | ChildSpec = [ 41 | 42 | % start flowdock client 43 | {flowdock_client, 44 | {flowdock_client, start_link, []}, 45 | temporary, 2000, worker, [] 46 | } 47 | ], 48 | 49 | % init 50 | {ok,{{simple_one_for_one, 10, 60}, ChildSpec}}. -------------------------------------------------------------------------------- /src/transport/http/README.md: -------------------------------------------------------------------------------- 1 | Ybot http transport 2 | ==================== 3 | 4 | This is `http` transport for [Ybot](https://github.com/0xAX/Ybot) chat robot. 5 | 6 | Usage 7 | ========= 8 | 9 | ```erlang 10 | [ 11 | {ybot, 12 | [ 13 | % list of transport 14 | {transports, [ 15 | % Http Ybot interface 16 | {http, 17 | % Http server host 18 | <<"localhost">>, 19 | % Http server port 20 | 8080, 21 | % Ybot nick 22 | <<"Ybot">> 23 | } 24 | ] 25 | }, 26 | 27 | % Loading new plugins during work or not 28 | {checking_new_plugins, false}, 29 | % Checking new plugins timeout 30 | {checking_new_plugins_timeout, 60000}, 31 | 32 | % Save commands history 33 | {commands_history, false}, 34 | % Command history limit 35 | {history_command_limit_count, 1000}, 36 | 37 | % plugins directory path 38 | {plugins_path, "plugins/"} 39 | ] 40 | } 41 | ]. 42 | ``` 43 | 44 | At that moment you can communicate with Ybot via http with two ways: 45 | 46 | 1. Send simple http post request with command in body: 47 | 48 | > curl -X POST -d "Ybot date" http://localhost:8080 49 | 50 | 1. Send http post request with application/json header and json body: 51 | 52 | ```javascript 53 | { 54 | "type" : "command_type()", 55 | "content": "Ybot haker_news 5" 56 | } 57 | ``` 58 | 59 | Where `command_type()`: 60 | 61 | * `broadcast` - Message from `content` field will be sending in all Ybot connected chats. 62 | * `response` - Ybot parse `contet` field, execute message, execute plugin, and send result back. 63 | 64 | Author 65 | ======================== 66 | 67 | If you have any questions/suggestions, write me at twitter to [0xAX](https://twitter.com/0xAX) -------------------------------------------------------------------------------- /src/transport/http/http_handler.erl: -------------------------------------------------------------------------------- 1 | %%%----------------------------------------------------------------------------- 2 | %%% @author 0xAX 3 | %%% @doc 4 | %%% Ybot http requests handler. 5 | %%% @end 6 | %%%----------------------------------------------------------------------------- 7 | -module(http_handler). 8 | 9 | -export([init/3]). 10 | -export([handle/2]). 11 | -export([terminate/3]). 12 | 13 | init(_Transport, Req, []) -> 14 | {ok, Req, undefined}. 15 | 16 | handle(Req, State) -> 17 | % Get request method 18 | {Method, Req2} = cowboy_req:method(Req), 19 | % Check method 20 | case Method of 21 | % Post request. Execute plugin. 22 | <<"POST">> -> 23 | % Try to get body request 24 | HasBody = cowboy_req:has_body(Req2), 25 | % Check body 26 | case HasBody of 27 | true -> 28 | % Get body 29 | {ok, [{Body, _}], _} = cowboy_req:body_qs(Req2), 30 | % Get headers 31 | {Headers, _} = cowboy_req:headers(Req2), 32 | % handle requets 33 | do_post(Body, Headers, Req2); 34 | false -> 35 | % Send error message 36 | cowboy_req:reply(400, [], <<"Missing body.">>, Req) 37 | end; 38 | % Other methods 39 | _ -> 40 | % do nothing 41 | pass 42 | end, 43 | {ok, Req2, State}. 44 | 45 | terminate(_Reason, _Req, _State) -> 46 | ok. 47 | 48 | %% @doc Handle post requets 49 | do_post(Data, Headers, Req) -> 50 | % Check content type 51 | Message = case lists:keyfind(<<"content-type">>, 1, Headers) of 52 | false -> 53 | % do nothing 54 | Data; 55 | {_, ContentType} -> 56 | case lists:member("application/json", string:tokens(binary_to_list(ContentType), ",")) of 57 | true -> 58 | % try to decode json 59 | try 60 | {DecodeJson} = jiffy:decode(Data), 61 | % Get type 62 | {_, Type} = lists:keyfind(<<"type">>, 1, DecodeJson), 63 | %% Get command 64 | {_, JsonCommand} = lists:keyfind(<<"content">>, 1, DecodeJson), 65 | % return 66 | {Type, JsonCommand} 67 | catch _ : _ -> 68 | wrong_json 69 | end; 70 | false -> 71 | Data 72 | end 73 | end, 74 | case Message of 75 | wrong_json -> 76 | cowboy_req:reply(400, [], "Wrong json data", Req); 77 | {CommandType, Command} -> 78 | % Check command type 79 | case CommandType of 80 | <<"broadcast">> -> 81 | ybot_utils:broadcast(Command); 82 | <<"response">> -> 83 | handle_data(Command, Req); 84 | % Send error message 85 | _ -> 86 | cowboy_req:reply(400, [], "Wrong command type", Req) 87 | end; 88 | _ -> 89 | handle_data(Data, Req) 90 | end. 91 | 92 | %% doc Handle incoming data 93 | handle_data(Data, Req) -> 94 | % Match incoming message 95 | case string:tokens(binary_to_list(Data), " \r\n") of 96 | [_BotNick, "hi"] -> 97 | % Send response 98 | cowboy_req:reply(200, [], "Hello :)", Req); 99 | [_BotNick, "bye"] -> 100 | % Send response 101 | cowboy_req:reply(200, [], "Good bue", Req); 102 | [_BotNick, "history"] -> 103 | % Get history 104 | History = gen_server:call(ybot_history, {get_history, self()}), 105 | % Send response 106 | cowboy_req:reply(200, [], History, Req); 107 | [_BotNick, "plugins?"] -> 108 | % Get plugins 109 | Plugins = gen_server:call(ybot_manager, get_plugins), 110 | PluginNames = lists:map(fun({_, _, Pl, _}) -> Pl end, Plugins), 111 | % Send plugins label 112 | cowboy_req:reply(200, [], "Plugins: " ++ string:join(PluginNames, ", ") ++ "\n" ++ "That's all :)", Req); 113 | [_BotNick, Command | _] -> 114 | % Get command arguments 115 | Args = string:tokens(ybot_utils:split_at_end(binary_to_list(Data), Command), "\r\n"), 116 | % Try to execute plugin and resend result 117 | case handle_command(Command, Args, self()) of 118 | wrong_plugin -> 119 | % send response 120 | cowboy_req:reply(200, [], <<"I don't know anything about ">>, Req); 121 | Result -> 122 | % send response 123 | cowboy_req:reply(200, [], Result, Req) 124 | end; 125 | % this is not our command 126 | _ -> 127 | % do nothing 128 | pass 129 | end. 130 | 131 | %% @doc Try to find plugin and execute it 132 | handle_command(Command, Args, TransportPid) -> 133 | % Get plugin metadata 134 | TryToFindPlugin = gen_server:call(ybot_manager, {get_plugin, Command}), 135 | % Check plugin 136 | case TryToFindPlugin of 137 | wrong_plugin -> 138 | % plugin not found 139 | wrong_plugin; 140 | {plugin, "erlang", PluginName, AppModule} -> 141 | PluginMod = list_to_atom(AppModule), 142 | % execute plugin 143 | Result = PluginMod:execute(Args), 144 | % Save command to history 145 | ok = gen_server:cast(ybot_history, {update_history, TransportPid, "Ybot " ++ PluginName ++ " " ++ Args ++ "\n"}), 146 | % send result to chat 147 | Result; 148 | {plugin, Lang, _PluginName, PluginPath} -> 149 | % execute plugin 150 | Result = os:cmd(Lang ++ " " ++ PluginPath ++ " \'" ++ Args ++ "\'"), 151 | % Save command to history 152 | ok = gen_server:cast(ybot_history, {update_history, TransportPid, "Ybot " ++ Command ++ " " ++ Args ++ "\n"}), 153 | % return result 154 | Result 155 | end. -------------------------------------------------------------------------------- /src/transport/http/http_server.erl: -------------------------------------------------------------------------------- 1 | %%%----------------------------------------------------------------------------- 2 | %%% @author 0xAX 3 | %%% @doc 4 | %%% Ybot http server. 5 | %%% @end 6 | %%%----------------------------------------------------------------------------- 7 | -module(http_server). 8 | 9 | -behaviour(gen_server). 10 | 11 | -export([start_link/2]). 12 | 13 | %% gen_server callbacks 14 | -export([init/1, 15 | handle_call/3, 16 | handle_cast/2, 17 | handle_info/2, 18 | terminate/2, 19 | code_change/3]). 20 | 21 | %% @doc internal state 22 | -record(state, { 23 | % bot nick 24 | nick = "" :: string() 25 | }). 26 | 27 | start_link(Host, Port) -> 28 | gen_server:start_link(?MODULE, [Host, Port], []). 29 | 30 | init([Host, Port]) -> 31 | % start server 32 | ok = gen_server:cast(self(), {start_serve, Host, Port}), 33 | % init state 34 | {ok, #state{}}. 35 | 36 | handle_call(_Request, _From, State) -> 37 | {reply, ignored, State}. 38 | 39 | %% @doc start serve 40 | handle_cast({start_serve, Host, Port}, State) -> 41 | % cowboy dispatches 42 | Dispatch = cowboy_router:compile([ 43 | {binary_to_list(Host), [{'_', http_handler, []}]} 44 | ]), 45 | 46 | {ok, _} = cowboy:start_http(my_http_listener, 3, [{port, Port}], [ 47 | {env, [{dispatch, Dispatch}]} 48 | ]), 49 | 50 | % return 51 | {noreply, State}; 52 | 53 | %% @doc Set bot nick 54 | handle_cast({bot_nick, BotNick}, State) -> 55 | {noreply, State#state{nick = binary_to_list(BotNick)}}; 56 | 57 | handle_cast(_Msg, State) -> 58 | {noreply, State}. 59 | 60 | handle_info(_Info, State) -> 61 | {noreply, State}. 62 | 63 | terminate(_Reason, _State) -> 64 | ok. 65 | 66 | code_change(_OldVsn, State, _Extra) -> 67 | {ok, State}. 68 | 69 | %% Internal functions 70 | -------------------------------------------------------------------------------- /src/transport/http/http_sup.erl: -------------------------------------------------------------------------------- 1 | %%%----------------------------------------------------------------------------- 2 | %%% @author 0xAX 3 | %%% @doc 4 | %%% Ybot http root supervisor. 5 | %%% @end 6 | %%%----------------------------------------------------------------------------- 7 | -module(http_sup). 8 | 9 | -behaviour(supervisor). 10 | 11 | %% API 12 | -export([start_link/0, start_http/2]). 13 | 14 | %% Supervisor callbacks 15 | -export([init/1]). 16 | 17 | 18 | %% =================================================================== 19 | %% API functions 20 | %% =================================================================== 21 | 22 | start_link() -> 23 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 24 | 25 | %% @doc Start http server 26 | %% @end 27 | -spec start_http(Host :: binary(), 28 | Port :: integer()) 29 | -> {ok, Pid :: pid()} | {error, Reason :: term()}. 30 | start_http(Host, Port) -> 31 | % run http server 32 | supervisor:start_child(?MODULE, [Host, Port]). 33 | 34 | init([]) -> 35 | % start ranch application 36 | application:start(ranch), 37 | % start cowboy application 38 | application:start(cowboy), 39 | 40 | % http server child process 41 | ChildSpec = [ 42 | 43 | {http_server, 44 | {http_server, start_link, []}, 45 | temporary, 2000, worker, [] 46 | } 47 | ], 48 | 49 | % init 50 | {ok,{{simple_one_for_one, 10, 60}, ChildSpec}}. -------------------------------------------------------------------------------- /src/transport/irc/README.md: -------------------------------------------------------------------------------- 1 | Ybot irc transport 2 | ======================== 3 | 4 | This is `irc` transport for [Ybot](https://github.com/0xAX/Ybot) chat robot. 5 | 6 | Usage 7 | ========= 8 | 9 | For using Ybot with irc, you must to configure it in configuration file ybot.config, something like that: 10 | 11 | ```erlang 12 | [ 13 | {ybot, 14 | [ 15 | % list of transport 16 | {transports, [ 17 | % Irc transport 18 | {irc, 19 | % Irc nick 20 | <<"ybot">>, 21 | % Irc channels list with keys 22 | [{<<"#linknet">>, <<>>}, {<<"#help">>, <<>>}], 23 | % Irc server host / pass 24 | {<<"irc.freenode.net">>, <<>>}, 25 | % Options 26 | [ 27 | % Port number 28 | {port, 6667}, 29 | % Use ssl connection or not 30 | {use_ssl, false}, 31 | % Reconnect timeout. Put 0 if you no need it. 32 | {reconnect_timeout, 5000} 33 | ] 34 | } 35 | ] 36 | }, 37 | 38 | % Loading new plugins during work or not 39 | {checking_new_plugins, false}, 40 | % Checking new plugins timeout 41 | {checking_new_plugins_timeout, 60000}, 42 | 43 | % Save commands history 44 | {commands_history, false}, 45 | % Command history limit 46 | {history_command_limit_count, 1000}, 47 | 48 | % plugins directory path 49 | {plugins_path, "plugins/"} 50 | ] 51 | } 52 | ]. 53 | ``` 54 | 55 | Author 56 | ======================== 57 | 58 | If you have any questions/suggestions, write me at twitter to [0xAX](https://twitter.com/0xAX) -------------------------------------------------------------------------------- /src/transport/irc/irc_handler.erl: -------------------------------------------------------------------------------- 1 | %%%----------------------------------------------------------------------------- 2 | %%% @author 0xAX 3 | %%% @doc 4 | %%% Handler of irc messages. 5 | %%% @end 6 | %%%----------------------------------------------------------------------------- 7 | -module(irc_handler). 8 | 9 | -behavior(gen_server). 10 | 11 | -export([start_link/0]). 12 | 13 | %% gen_server callbacks 14 | -export([init/1, 15 | handle_call/3, 16 | handle_cast/2, 17 | handle_info/2, 18 | terminate/2, 19 | code_change/3]). 20 | 21 | % state 22 | -record(state, { 23 | % irc bot nick 24 | nick = <<>> :: binary(), 25 | % irc client pid 26 | irc_client_pid :: pid(), 27 | % command parser process 28 | parser_pid :: pid() 29 | }). 30 | 31 | start_link() -> 32 | gen_server:start_link(?MODULE, [], []). 33 | 34 | init([]) -> 35 | {ok, #state{}}. 36 | 37 | handle_call(_Request, _From, State) -> 38 | {reply, ignored, State}. 39 | 40 | handle_cast({irc_client, ClientPid, ParserPid, BotNick}, State) -> 41 | % save irc client pid 42 | {noreply, State#state{irc_client_pid = ClientPid, parser_pid = ParserPid, nick = BotNick}}; 43 | 44 | handle_cast(stop, State) -> 45 | {stop, normal, State}; 46 | 47 | handle_cast(_Msg, State) -> 48 | {noreply, State}. 49 | 50 | %% @doc Receive incoming message from irc chat 51 | handle_info({incoming_message, IncomingMessage, From}, State) -> 52 | % Get bot chat nick 53 | Nick = binary_to_list(State#state.nick), 54 | % Send message to parser 55 | gen_server:cast(State#state.parser_pid, {incoming_message, State#state.irc_client_pid, Nick, From, IncomingMessage}), 56 | % return 57 | {noreply, State}; 58 | 59 | handle_info(_Info, State) -> 60 | {noreply, State}. 61 | 62 | terminate(_Reason, _State) -> 63 | ok. 64 | 65 | code_change(_OldVsn, State, _Extra) -> 66 | {ok, State}. 67 | 68 | %% Internal functions 69 | -------------------------------------------------------------------------------- /src/transport/irc/irc_lib_sup.erl: -------------------------------------------------------------------------------- 1 | %%%----------------------------------------------------------------------------- 2 | %%% @author 0xAX 3 | %%% @doc 4 | %%% Irc transport root supervisor. 5 | %%% @end 6 | %%%----------------------------------------------------------------------------- 7 | -module(irc_lib_sup). 8 | 9 | -behaviour(supervisor). 10 | 11 | %% API 12 | -export([start_link/0, start_irc_client/7]). 13 | 14 | %% Supervisor callbacks 15 | -export([init/1]). 16 | 17 | 18 | %% =================================================================== 19 | %% API functions 20 | %% =================================================================== 21 | 22 | start_link() -> 23 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 24 | 25 | %% @doc Start new irc client 26 | %% @end 27 | -spec start_irc_client(CallbackModule :: atom() | pid(), 28 | Host :: binary(), 29 | Port :: integer(), 30 | Channel :: {binary(), binary()}, 31 | Nick :: binary(), 32 | UseSsl :: boolean(), 33 | ReconnectTimeout :: integer()) 34 | -> {ok, Pid :: pid()} | {error, Reason :: term()}. 35 | start_irc_client(CallbackModule, Host, Port, Channel, Nick, UseSsl, ReconnectTimeout) -> 36 | % Check use ssl or not 37 | SocketMod = case UseSsl of 38 | true -> ssl; 39 | false -> gen_tcp 40 | end, 41 | Child = {irc_lib_client, 42 | {irc_lib_client, start_link, [CallbackModule, Host, Port, SocketMod, Channel, Nick, ReconnectTimeout]}, 43 | temporary, 2000, worker, [] 44 | }, 45 | % run new irc client 46 | supervisor:start_child(?MODULE, Child). 47 | 48 | init([]) -> 49 | % init 50 | {ok,{{one_for_one, 2, 60}, []}}. 51 | -------------------------------------------------------------------------------- /src/transport/skype/README.md: -------------------------------------------------------------------------------- 1 | Ybot skype transport 2 | ======================== 3 | 4 | This is [skype](http://www.skype.com/) transport for [Ybot](https://github.com/0xAX/Ybot) chat robot. 5 | 6 | Usage 7 | ======================== 8 | 9 | There is `python` script - skype.py which you can find [here](https://github.com/0xAX/Ybot/blob/master/priv/skype.py), 10 | which uses skype api for works with skype. Also if you want to use Ybot with skype, it depends on [Skype4Py](https://github.com/awahlig/skype4py) library, which you can install with: 11 | 12 | ``` 13 | pip install Skype4Py 14 | ``` 15 | 16 | or download source code and 17 | 18 | ``` 19 | python setup.py install 20 | ``` 21 | 22 | `skype.py` script receives skype messages and sends it to Ybot via http, got response from Ybot and sends it back to skype, all simple. 23 | After Ybot configuring, you must configure your skype. Open `/etc/dbus-1/system.d/skype.conf` and add: 24 | 25 | ```xml 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | ``` 36 | 37 | After skype configuring, you must have run skype in the same machine as Ybot. Now configure Ybot. 38 | We must run http interface and skype interface in Ybot: 39 | 40 | ```erlang 41 | [ 42 | {ybot, 43 | [ 44 | % list of transport 45 | {transports, [ 46 | % Irc transport 47 | {irc, 48 | 49 | % Http Ybot interface 50 | {http, 51 | % Http server host 52 | <<"localhost">>, 53 | % Http server port 54 | 8080, 55 | % Ybot nick 56 | <<"Ybot">> 57 | }, 58 | 59 | % skype 60 | {skype, 61 | % use skype or not 62 | true, 63 | % Ybot http interface host 64 | <<"http://localhost">>, 65 | % Ybot http interface port 66 | 8080 67 | } 68 | ] 69 | }, 70 | 71 | % Loading new plugins during work or not 72 | {checking_new_plugins, false}, 73 | % Checking new plugins timeout 74 | {checking_new_plugins_timeout, 60000}, 75 | 76 | % Save commands history 77 | {commands_history, false}, 78 | % Command history limit 79 | {history_command_limit_count, 1000}, 80 | 81 | % plugins directory path 82 | {plugins_path, "plugins/"} 83 | ] 84 | } 85 | ]. 86 | ``` 87 | 88 | Author 89 | ======================== 90 | 91 | If you have any questions/suggestions, write me at twitter to [0xAX](https://twitter.com/0xAX) -------------------------------------------------------------------------------- /src/transport/skype/skype.erl: -------------------------------------------------------------------------------- 1 | %%%----------------------------------------------------------------------------- 2 | %%% @author 0xAX 3 | %%% @doc 4 | %%% Ybot skype handler. 5 | %%% @end 6 | %%%----------------------------------------------------------------------------- 7 | -module(skype). 8 | 9 | -behaviour(gen_server). 10 | 11 | -export([start_link/1]). 12 | 13 | %% gen_server callbacks 14 | -export([init/1, 15 | handle_call/3, 16 | handle_cast/2, 17 | handle_info/2, 18 | terminate/2, 19 | code_change/3]). 20 | 21 | %% @doc internal state 22 | -record(state, { 23 | }). 24 | 25 | start_link(Command) -> 26 | process_flag(trap_exit, true), 27 | 28 | gen_server:start_link(?MODULE, [Command], []). 29 | 30 | init([Command]) -> 31 | % start python script 32 | gen_server:cast(self(), {command, Command}), 33 | % init 34 | {ok, #state{}}. 35 | 36 | handle_call(_Request, _From, State) -> 37 | {reply, ignored, State}. 38 | 39 | %% @doc start skype script 40 | handle_cast({command, Command}, State) -> 41 | % Run 42 | open_port({spawn, Command}, [exit_status]), 43 | % return 44 | {noreply, State}; 45 | 46 | handle_cast(_Msg, State) -> 47 | {noreply, State}. 48 | 49 | handle_info(_Info, State) -> 50 | {noreply, State}. 51 | 52 | terminate(_Reason, _State) -> 53 | % return 54 | ok. 55 | 56 | code_change(_OldVsn, State, _Extra) -> 57 | {ok, State}. 58 | 59 | %% Internal functions -------------------------------------------------------------------------------- /src/transport/talkerapp/README.md: -------------------------------------------------------------------------------- 1 | Ybot Talkerapp transport 2 | ======================== 3 | 4 | This is [talkerapp](http://talkerapp.com/) transport for [Ybot](https://github.com/0xAX/Ybot) chat robot. 5 | 6 | Usage 7 | ========= 8 | 9 | For using Ybot with `talkerapp`, you must to configure it in configuration file ybot.config, something like that: 10 | 11 | ```erlang 12 | [ 13 | {ybot, 14 | [ 15 | % list of transport 16 | {transports, [ 17 | 18 | % talkerapp 19 | {talkerapp, 20 | % talkerapp nick 21 | <<"ybot">>, 22 | % room 23 | <<"ybot_test">>, 24 | % token 25 | <<"api_access_token">> 26 | } 27 | 28 | % Loading new plugins during work or not 29 | {checking_new_plugins, false}, 30 | % Checking new plugins timeout 31 | {checking_new_plugins_timeout, 60000}, 32 | 33 | % Save commands history 34 | {commands_history, false}, 35 | % Command history limit 36 | {history_command_limit_count, 1000}, 37 | 38 | % plugins directory path 39 | {plugins_path, "plugins/"} 40 | ] 41 | } 42 | ]. 43 | ``` 44 | 45 | Author 46 | ======================== 47 | 48 | If you have any questions/suggestions, write me at twitter to [0xAX](https://twitter.com/0xAX) -------------------------------------------------------------------------------- /src/transport/talkerapp/talker_app_client.erl: -------------------------------------------------------------------------------- 1 | %%%----------------------------------------------------------------------------- 2 | %%% @author 0xAX 3 | %%% @doc 4 | %%% Talkerapp transport client. 5 | %%% @end 6 | %%%----------------------------------------------------------------------------- 7 | -module(talker_app_client). 8 | 9 | -behaviour(gen_server). 10 | 11 | -export([start_link/5]). 12 | 13 | %% gen_server callbacks 14 | -export([init/1, 15 | handle_call/3, 16 | handle_cast/2, 17 | handle_info/2, 18 | terminate/2, 19 | code_change/3]). 20 | 21 | %% @doc Default port for Talkerapp. 22 | -define(PORT, 8500). 23 | 24 | %% @doc Internal state. 25 | -record(state, { 26 | % callback module with handler 27 | callback :: pid(), 28 | % connected socket 29 | socket = null, 30 | % user access token 31 | token = <<>> :: binary(), 32 | % Bot nick 33 | bot_nick = <<>> :: binary(), 34 | % talkerapp room 35 | room = <<>> :: binary(), 36 | % talkerapp reconnect timeout 37 | reconnect_timeout = 0 :: integer() 38 | }). 39 | 40 | %%%============================================================================= 41 | %%% API 42 | %%%============================================================================= 43 | 44 | start_link(Callback, BotNick, Room, Token, RecTimeout) -> 45 | gen_server:start_link(?MODULE, [Callback, BotNick, Room, Token, RecTimeout], []). 46 | 47 | %%%============================================================================= 48 | %%% talker_app client callback 49 | %%%============================================================================= 50 | 51 | init([Callback, BotNick, Room, Token, RecTimeout]) -> 52 | % start connection 53 | gen_server:cast(self(), {connect, Token, Room}), 54 | % init state and return 55 | {ok, #state{bot_nick = BotNick, callback = Callback, token = Token, room = Room, reconnect_timeout = RecTimeout}}. 56 | 57 | handle_call(_Request, _From, State) -> 58 | {reply, ignored, State}. 59 | 60 | %% @doc send message 61 | handle_cast({send_message, _From, Message}, State) -> 62 | % Make json message 63 | JsonMessage = "{\"type\":\"message\",\"content\" :\"" ++ Message ++ "\"}", 64 | % Send message 65 | ssl:send(State#state.socket, JsonMessage), 66 | % return state 67 | {noreply, State}; 68 | 69 | %% @doc connect to talkerapp 70 | handle_cast({connect, Token, Room}, State) -> 71 | % Connect to talkerapp.com 72 | case ssl:connect("talkerapp.com", ?PORT, [{verify, 0}]) of 73 | {ok, Socket} -> 74 | AuthMessage = "{\"type\":\"connect\",\"room\":\"" ++ binary_to_list(Room) ++ "\",\"token\":\"" ++ binary_to_list(Token) ++ "\"}", 75 | % Try to auth 76 | ssl:send(Socket, AuthMessage), 77 | % Send ping in loop 78 | erlang:send_after(25000, self(), ping), 79 | % save socket and return 80 | {noreply, State#state{socket = Socket}}; 81 | {error, Reason} -> 82 | % Some log 83 | lager:error("Unable to connect to talkerapp server with reason ~s", [Reason]), 84 | % return state 85 | {noreply, State} 86 | end; 87 | 88 | handle_cast(_Msg, State) -> 89 | {noreply, State}. 90 | 91 | %% @doc Incoming message 92 | handle_info({ssl, _Socket, Data}, State) -> 93 | % Decode json data 94 | {DecodeJson} = jiffy:decode(Data), 95 | case lists:member({<<"type">>,<<"message">>}, DecodeJson) of 96 | true -> 97 | % Get user data 98 | UserName = get_user_name(DecodeJson), 99 | if UserName == State#state.bot_nick -> 100 | % do nothing 101 | pass; 102 | true -> 103 | % Send incoming message to handler 104 | Message = get_message(DecodeJson), 105 | State#state.callback ! {incoming_message, Message} 106 | end; 107 | false -> 108 | % do nothing 109 | pass 110 | end, 111 | % return 112 | {noreply, State}; 113 | 114 | handle_info({ssl_closed, Reason}, State) -> 115 | % Some log 116 | lager:info("ssl_closed with reason: ~p~n", [Reason]), 117 | % try reconnect 118 | try_reconnect(State); 119 | 120 | handle_info({ssl_error, _Socket, Reason}, State) -> 121 | % Some log 122 | lager:error("tcp_error: ~p~n", [Reason]), 123 | % try reconnect 124 | try_reconnect(State); 125 | 126 | %% @doc send ping 127 | handle_info(ping, State) -> 128 | % Send ping 129 | ssl:send(State#state.socket, "{\"type\":\"ping\"}"), 130 | % Send ping in loop 131 | erlang:send_after(25000, self(), ping), 132 | % return 133 | {noreply, State}; 134 | 135 | handle_info(_Info, State) -> 136 | {noreply, State}. 137 | 138 | terminate(_Reason, _State) -> 139 | ok. 140 | 141 | code_change(_OldVsn, State, _Extra) -> 142 | {ok, State}. 143 | 144 | %%%============================================================================= 145 | %%% Internal functions 146 | %%%============================================================================= 147 | get_user_name([H | Json]) -> 148 | case H of 149 | {<<"user">>, _UserData} -> 150 | {<<"user">>, {Data}} = H, 151 | [{<<"name">>, UserName}] = lists:flatten(lists:filter(fun(D) -> {Label, _} = D, 152 | case Label of 153 | <<"name">> -> true; 154 | _ -> false 155 | end 156 | end, Data)), 157 | UserName; 158 | _ -> 159 | get_user_name(Json) 160 | end. 161 | 162 | get_message([H | Json]) -> 163 | case H of 164 | {<<"content">>, Message} -> 165 | Message; 166 | _ -> 167 | get_message(Json) 168 | end. 169 | 170 | %% @doc try reconnect 171 | -spec try_reconnect(State :: #state{}) -> {normal, stop, State} | {noreply, State}. 172 | try_reconnect(#state{reconnect_timeout = Timeout, token = Token, room = Room} = State) -> 173 | case Timeout > 0 of 174 | true -> 175 | % no need in reconnect 176 | {normal, stop, State}; 177 | false -> 178 | % sleep 179 | timer:sleep(Timeout), 180 | % Try reconnect 181 | gen_server:cast(self(), {connect, Token, Room}), 182 | % return 183 | {noreply, State} 184 | end. -------------------------------------------------------------------------------- /src/transport/talkerapp/talker_app_sup.erl: -------------------------------------------------------------------------------- 1 | %%%----------------------------------------------------------------------------- 2 | %%% @author 0xAX 3 | %%% @doc 4 | %%% Talkerapp transport root supervisor. 5 | %%% @end 6 | %%%----------------------------------------------------------------------------- 7 | -module(talker_app_sup). 8 | 9 | -behaviour(supervisor). 10 | 11 | %% API 12 | -export([start_link/0, start_talkerapp_client/5]). 13 | 14 | %% Supervisor callbacks 15 | -export([init/1]). 16 | 17 | 18 | %% =================================================================== 19 | %% API functions 20 | %% =================================================================== 21 | 22 | start_link() -> 23 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 24 | 25 | %% @doc Start new talkerapp client 26 | %% @end 27 | -spec start_talkerapp_client(Callback :: pid(), BotNick :: binary(), Room :: binary(), Token :: binary(), RecTimeout :: integer()) 28 | -> {ok, Pid :: pid()} | {error, Reason :: term()}. 29 | start_talkerapp_client(Callback, BotNick, Room, Token, RecTimeout) -> 30 | % Child 31 | Child = {talker_app_client, 32 | {talker_app_client, start_link, [Callback, BotNick, Room, Token, RecTimeout]}, 33 | temporary, 2000, worker, [] 34 | }, 35 | % run new talkerapp client 36 | supervisor:start_child(?MODULE, Child). 37 | 38 | init([]) -> 39 | % init 40 | {ok,{{one_for_one, 2, 60}, []}}. 41 | -------------------------------------------------------------------------------- /src/transport/talkerapp/talkerapp_handler.erl: -------------------------------------------------------------------------------- 1 | %%%----------------------------------------------------------------------------- 2 | %%% @author 0xAX 3 | %%% @doc 4 | %%% Ybot talkerapp incoming message handler. 5 | %%% @end 6 | %%%----------------------------------------------------------------------------- 7 | -module(talkerapp_handler). 8 | 9 | -behaviour(gen_server). 10 | 11 | -export([start_link/0]). 12 | 13 | %% gen_server callbacks 14 | -export([init/1, 15 | handle_call/3, 16 | handle_cast/2, 17 | handle_info/2, 18 | terminate/2, 19 | code_change/3]). 20 | 21 | -record(state, { 22 | % bot nick in handler room 23 | bot_nick = <<>> :: binary(), 24 | % talker_app client process pid 25 | client_pid :: pid(), 26 | % parser process pid 27 | parser_pid :: pid() 28 | }). 29 | 30 | %%%============================================================================= 31 | %%% API 32 | %%%============================================================================= 33 | 34 | start_link() -> 35 | gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). 36 | 37 | %%%============================================================================= 38 | %%% talker_app handler callbacks 39 | %%%============================================================================= 40 | 41 | init([]) -> 42 | {ok, #state{}}. 43 | 44 | handle_call(_Request, _From, State) -> 45 | {reply, ignored, State}. 46 | 47 | handle_cast({talkerapp_client, ClientPid, ParserPid, Login}, State) -> 48 | {noreply, State#state{client_pid = ClientPid, parser_pid = ParserPid, bot_nick = Login}}; 49 | 50 | handle_cast(_Msg, State) -> 51 | {noreply, State}. 52 | 53 | handle_info({incoming_message, IncomingMessage}, State) -> 54 | % Get Ybot Nick from current chat 55 | Nick = binary_to_list(State#state.bot_nick), 56 | % Send message to parser 57 | gen_server:cast(State#state.parser_pid, {incoming_message, State#state.client_pid, Nick, "", binary_to_list(IncomingMessage)}), 58 | % return 59 | {noreply, State}; 60 | 61 | handle_info(_Info, State) -> 62 | {noreply, State}. 63 | 64 | terminate(_Reason, _State) -> 65 | ok. 66 | 67 | code_change(_OldVsn, State, _Extra) -> 68 | {ok, State}. 69 | 70 | %%%============================================================================= 71 | %%% Internal functions 72 | %%%============================================================================= -------------------------------------------------------------------------------- /src/transport/xmpp/README.md: -------------------------------------------------------------------------------- 1 | XMPP based chats 2 | ======================= 3 | 4 | This is `xmpp` transport for [Ybot](https://github.com/0xAX/Ybot) chat robot. 5 | 6 | Jabber 7 | ================ 8 | 9 | For using Ybot with `jabber`, you must to configure it in configuration file ybot.config, something like that: 10 | 11 | ```erlang 12 | [ 13 | {ybot, 14 | [ 15 | % list of transport 16 | {transports, [ 17 | % Xmpp Multi-user chat Login Password Room Nick Host Resource 18 | {xmpp, <<"ybot2@jabber.org">>, 19 | <<"password">>, 20 | <<"ybot_test@conference.jabber.org">>, 21 | <<"YbotNick">>, 22 | <<"jabber.org">>, 23 | <<"home">>, 24 | [{port, 5222}, {use_ssl, false}, {reconnect_timeout, 5000}] 25 | } 26 | ] 27 | }, 28 | 29 | % Loading new plugins during work or not 30 | {checking_new_plugins, false}, 31 | % Checking new plugins timeout 32 | {checking_new_plugins_timeout, 20000}, 33 | 34 | % Save commands history 35 | {commands_history, true}, 36 | % Command history limit 37 | {history_command_limit_count, 100}, 38 | 39 | % plugins directory path 40 | {plugins_path, "plugins/"} 41 | ] 42 | } 43 | ]. 44 | ``` 45 | 46 | HipChat 47 | ================ 48 | 49 | For using Ybot with [HipChat](https://www.hipchat.com/), you must to configure it in configuration file ybot.config, something like that: 50 | 51 | ```erlang 52 | [ 53 | {ybot, 54 | [ 55 | % list of transport 56 | {transports, [ 57 | % Hipchat config 58 | {hipchat, 59 | % Bot Hipchat Username 60 | <<"00000_00000@chat.hipchat.com">>, 61 | % Bot Hipchat password 62 | <<"password">>, 63 | % Hipchat room 64 | <<"ybot_test@conf.hipchat.com">>, 65 | % Server 66 | <<"chat.hipchat.com">>, 67 | % Resource 68 | <<"bot">>, 69 | % Hipchat nick 70 | <<"ybot">>, 71 | % Options 72 | [ 73 | {reconnect_timeout, 0} 74 | ] 75 | }, 76 | ] 77 | }, 78 | 79 | ..... 80 | ..... 81 | ..... 82 | ] 83 | } 84 | ]. 85 | ``` 86 | Little note about `hipchat_nick`. You must get it in the `xmpp settings` in your HipChat account page. 87 | 88 | Author 89 | ======================== 90 | 91 | If you have any questions/suggestions, write me at twitter to [0xAX](https://twitter.com/0xAX) -------------------------------------------------------------------------------- /src/transport/xmpp/xmpp.hrl: -------------------------------------------------------------------------------- 1 | % XMPP new stream 2 | -define(STREAM (Server), ""). -------------------------------------------------------------------------------- /src/transport/xmpp/xmpp_handler.erl: -------------------------------------------------------------------------------- 1 | %%%----------------------------------------------------------------------------- 2 | %%% @author 0xAX 3 | %%% @doc 4 | %%% Xmpp messages handler. 5 | %%% @end 6 | %%%----------------------------------------------------------------------------- 7 | -module(xmpp_handler). 8 | 9 | -behavior(gen_server). 10 | 11 | -export([start_link/0]). 12 | 13 | %% gen_server callbacks 14 | -export([init/1, 15 | handle_call/3, 16 | handle_cast/2, 17 | handle_info/2, 18 | terminate/2, 19 | code_change/3]). 20 | 21 | % state 22 | -record(state, { 23 | % xmpp bot nick 24 | nick = "", 25 | % xmpp client pid 26 | xmpp_client_pid :: pid(), 27 | % parser process pid 28 | parser_pid :: pid() 29 | }). 30 | 31 | %%%============================================================================= 32 | %%% API 33 | %%%============================================================================= 34 | 35 | start_link() -> 36 | gen_server:start_link(?MODULE, [], []). 37 | 38 | %%%============================================================================= 39 | %%% xmpp_handler callbacks 40 | %%%============================================================================= 41 | 42 | init([]) -> 43 | {ok, #state{}}. 44 | 45 | handle_call(_Request, _From, State) -> 46 | {reply, ignored, State}. 47 | 48 | handle_cast({xmpp_client, ClientPid, ParserPid, BotNick}, State) -> 49 | % Get first symbol of bot nick name 50 | [NickSymb | _] = binary_to_list(BotNick), 51 | % Check this symbol. If it's '@' this is Hipchat. 52 | Nick = case NickSymb of 53 | $@ -> 54 | % return nick 55 | binary_to_list(BotNick); 56 | _ -> 57 | % get nick part 58 | [TempNick | _] = string:tokens(binary_to_list(BotNick), "@"), 59 | % return nick 60 | TempNick 61 | end, 62 | % save xmpp client pid 63 | {noreply, State#state{xmpp_client_pid = ClientPid, parser_pid = ParserPid, nick = Nick}}; 64 | 65 | handle_cast(_Msg, State) -> 66 | {noreply, State}. 67 | 68 | handle_info({incoming_message, From, IncomingMessage}, #state{nick = Nick} = State) -> 69 | % Send message to parser 70 | gen_server:cast(State#state.parser_pid, {incoming_message, State#state.xmpp_client_pid, Nick, From, IncomingMessage}), 71 | % return 72 | {noreply, State}; 73 | 74 | handle_info(_Info, State) -> 75 | {noreply, State}. 76 | 77 | terminate(_Reason, _State) -> 78 | ok. 79 | 80 | code_change(_OldVsn, State, _Extra) -> 81 | {ok, State}. 82 | 83 | %%%============================================================================= 84 | %%% Internal functions 85 | %%%============================================================================= -------------------------------------------------------------------------------- /src/transport/xmpp/xmpp_sup.erl: -------------------------------------------------------------------------------- 1 | %%%----------------------------------------------------------------------------- 2 | %%% @author 0xAX 3 | %%% @doc 4 | %%% Xmpp root supervisor. 5 | %%% @end 6 | %%%----------------------------------------------------------------------------- 7 | -module(xmpp_sup). 8 | 9 | -behaviour(supervisor). 10 | 11 | %% API 12 | -export([start_link/0, start_xmpp_client/10]). 13 | 14 | %% Supervisor callbacks 15 | -export([init/1]). 16 | 17 | %% =================================================================== 18 | %% API functions 19 | %% =================================================================== 20 | start_link() -> 21 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 22 | 23 | %% @doc Start new xmpp muc client 24 | -spec start_xmpp_client(CallbackModule :: atom() | pid(), 25 | Login :: binary(), 26 | Password :: binary(), 27 | Server :: binary(), 28 | Port :: integer(), 29 | Room :: binary(), 30 | Nick :: binary(), 31 | Resource :: binary(), 32 | UseSsl :: boolean(), 33 | ReconnectTimeout :: integer()) 34 | -> {ok, Pid :: pid()} | {error, Reason :: term()}. 35 | start_xmpp_client(CallbackModule, Login, Password, Server, Port, Room, Nick, Resource, UseSsl, ReconnectTimeout ) -> 36 | % Match socket mode 37 | SocketMode = case UseSsl of 38 | true -> 39 | ssl; 40 | false -> 41 | gen_tcp 42 | end, 43 | % Xmpp child 44 | Child = {xmpp_client, 45 | {xmpp_client, start_link, [CallbackModule, Login, Password, Server, Port, Room, Nick, Resource, SocketMode, ReconnectTimeout]}, 46 | temporary, 2000, worker, [] 47 | }, 48 | 49 | % run new xmpp client 50 | supervisor:start_child(?MODULE, Child). 51 | 52 | %% =================================================================== 53 | %% Supervisor callbacks 54 | %% =================================================================== 55 | init([]) -> 56 | % init and start 57 | {ok,{{one_for_one, 2, 60}, []}}. 58 | -------------------------------------------------------------------------------- /src/transport/xmpp/xmpp_xml.erl: -------------------------------------------------------------------------------- 1 | %%%----------------------------------------------------------------------------- 2 | %%% @author 0xAX 3 | %%% @doc 4 | %%% Xml util functions. 5 | %%% @end 6 | %%%----------------------------------------------------------------------------- 7 | -module(xmpp_xml). 8 | 9 | %% API 10 | -export([auth_plain/1, 11 | create_session/0, 12 | bind/1, 13 | muc/1, 14 | message/2, 15 | private_message/2, 16 | presence/0, 17 | presence_subscribed/1]). 18 | 19 | -include("xmpp.hrl"). 20 | 21 | %% @doc send online status to all 22 | presence() -> 23 | % xml data structure 24 | XmlData = [{'presence', [{'priority', '50'}, {'show', 'chat'}, {'status', 'Hello, from Ybot'}], []}], 25 | % convert to xml 26 | lists:flatten(xmerl:export_simple(XmlData, xmerl_xml, [{prolog, ""}])). 27 | 28 | %% @doc send 'subscribed' presence 29 | -spec presence_subscribed(To :: string()) -> string(). 30 | presence_subscribed(To) -> 31 | % xml data structure 32 | XmlData = [{'presence', [{'to', To}, {'type', 'subscribed'}], []}], 33 | % convert to xml 34 | lists:flatten(xmerl:export_simple(XmlData, xmerl_xml, [{prolog, ""}])). 35 | 36 | %% @doc xmpp plain authentication 37 | -spec auth_plain(Login :: string()) -> string(). 38 | auth_plain(Login) -> 39 | % xml structure data 40 | XmlData = [{'auth', [{'xmlns', 'urn:ietf:params:xml:ns:xmpp-sasl'}, 41 | {'mechanism', 'PLAIN'} 42 | ], [Login] 43 | } 44 | ], 45 | % convert to xml 46 | lists:flatten(xmerl:export_simple(XmlData, xmerl_xml, [{prolog, ""}])). 47 | 48 | %% @doc bind xmpp resource 49 | -spec bind(Resource :: string()) -> string(). 50 | bind(Resource) -> 51 | % xml structure data 52 | XmlData = [{'iq', [{'type', 'set'}, {'id', '9746'}], 53 | [{'bind', 54 | [{'xmlns', 'urn:ietf:params:xml:ns:xmpp-bind'}], 55 | [{'resource', [], [Resource]}] 56 | }] 57 | }], 58 | % convert to xml 59 | lists:flatten([xmerl:export_simple(XmlData, xmerl_xml, [{prolog, ""}])]). 60 | 61 | %% @doc create new session 62 | -spec create_session() -> string(). 63 | create_session() -> 64 | % xml structure data 65 | XmlData = [{'iq', [{'type', 'set'}, {'id', '9746'}], 66 | [{'session', 67 | [{'xmlns', 'urn:ietf:params:xml:ns:xmpp-session'}], [] 68 | }] 69 | }], 70 | % convert to xml 71 | lists:flatten([xmerl:export_simple(XmlData, xmerl_xml, [{prolog, ""}])]). 72 | 73 | %% @doc send private message 74 | -spec private_message(To :: string(), Message :: string()) -> string(). 75 | private_message(To, Message) -> 76 | % xml data structure 77 | XmlData = [{'message', [{'to', To}, {'type', "chat"}], 78 | [{'body', [Message]}] 79 | }], 80 | 81 | % convert to xml 82 | lists:flatten(xmerl:export_simple(XmlData, xmerl_xml, [{prolog, ""}])). 83 | 84 | %% @doc Send message to muc 85 | -spec message(Room :: string(), Message :: string()) -> string(). 86 | message(Room, Message) -> 87 | % xml structure data 88 | XmlData = [{'message', [{'to', Room}, {'type', "groupchat"}], 89 | [{'body', [Message]}] 90 | }], 91 | % convert to xml 92 | lists:flatten(xmerl:export_simple(XmlData, xmerl_xml, [{prolog, ""}])). 93 | 94 | %% @doc Join to muc 95 | -spec muc(Room :: string()) -> string(). 96 | muc(Room) -> 97 | % xml structure data 98 | XmlData = [{'presence', [{'to', Room}], 99 | [{'x', [{'xmlns', 'http://jabber.org/protocol/muc'}], 100 | [{'history', [{'maxchars', "0"}], [""]}] 101 | }] 102 | } 103 | ], 104 | % convert to xml 105 | lists:flatten(xmerl:export_simple(XmlData, xmerl_xml, [{prolog, ""}])). -------------------------------------------------------------------------------- /src/web/web_admin.erl: -------------------------------------------------------------------------------- 1 | %%%----------------------------------------------------------------------------- 2 | %%% @author 0xAX 3 | %%% @doc 4 | %%% Ybot web admin main process. 5 | %%% @end 6 | %%%----------------------------------------------------------------------------- 7 | -module(web_admin). 8 | 9 | -behaviour(gen_server). 10 | 11 | -export([start_link/0]). 12 | -export([docroot/1]). 13 | 14 | %% gen_server callbacks 15 | -export([init/1, 16 | handle_call/3, 17 | handle_cast/2, 18 | handle_info/2, 19 | terminate/2, 20 | code_change/3]). 21 | 22 | -record(state, {}). 23 | 24 | 25 | %% =================================================================== 26 | %% API functions 27 | %% =================================================================== 28 | 29 | start_link() -> 30 | gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). 31 | 32 | docroot(Append) -> 33 | priv_dir() ++ "webadmin/" ++ Append. 34 | 35 | 36 | %% =================================================================== 37 | %% web admin process callbacks 38 | %% =================================================================== 39 | 40 | init([]) -> 41 | % start server 42 | ok = gen_server:cast(self(), start_serve), 43 | % init internal state 44 | {ok, #state{}}. 45 | 46 | handle_call(_Request, _From, State) -> 47 | {reply, ignored, State}. 48 | 49 | handle_cast(start_serve, State) -> 50 | % Get web admin config 51 | {ok, WebAdmin} = application:get_env(ybot, web_admin), 52 | 53 | % Get Host 54 | {webadmin_host, Host} = lists:keyfind(webadmin_host, 1, WebAdmin), 55 | 56 | % Get Port 57 | {webadmin_port, Port} = lists:keyfind(webadmin_port, 1, WebAdmin), 58 | 59 | % Cowboy dispatch 60 | Dispatch = cowboy_router:compile([ 61 | {binary_to_list(Host), 62 | [ 63 | {"/css/[...]", cowboy_static, 64 | {dir, docroot("css"), [{mimetypes, cow_mimetypes, all}]}}, 65 | {"/js/[...]", cowboy_static, 66 | {dir, docroot("js"), [{mimetypes, cow_mimetypes, all}]}}, 67 | {"/views/[...]", cowboy_static, 68 | {dir, docroot("views"), [{mimetypes, cow_mimetypes, all}]}}, 69 | {"/", web_admin_req_handler, []} 70 | ]} 71 | ]), 72 | % start serving 73 | {ok, _} = cowboy:start_http(web_http_listener, 100, [{port, Port}], [{env, [{dispatch, Dispatch}]}]), 74 | % return 75 | {noreply, State}; 76 | 77 | handle_cast(_Msg, State) -> 78 | {noreply, State}. 79 | 80 | handle_info(_Info, State) -> 81 | {noreply, State}. 82 | 83 | terminate(_Reason, _State) -> 84 | ok. 85 | 86 | code_change(_OldVsn, State, _Extra) -> 87 | {ok, State}. 88 | 89 | 90 | %% =================================================================== 91 | %% Internal functions 92 | %% =================================================================== 93 | 94 | priv_dir() -> 95 | case code:priv_dir(ybot) of 96 | {error, bad_name} -> 97 | {ok, Cwd} = file:get_cwd(), 98 | Cwd ++ "/" ++ "priv/"; 99 | Priv -> 100 | Priv ++ "/" 101 | end. 102 | -------------------------------------------------------------------------------- /src/web/ybot_web_admin_sup.erl: -------------------------------------------------------------------------------- 1 | %%%----------------------------------------------------------------------------- 2 | %%% @author 0xAX 3 | %%% @doc 4 | %%% Ybot web admin root supervisor. 5 | %%% @end 6 | %%%----------------------------------------------------------------------------- 7 | -module(ybot_web_admin_sup). 8 | 9 | -behaviour(supervisor). 10 | 11 | %% API 12 | -export([start_link/0, start_web_admin/0]). 13 | 14 | %% Supervisor callbacks 15 | -export([init/1]). 16 | 17 | 18 | %% =================================================================== 19 | %% API functions 20 | %% =================================================================== 21 | 22 | start_link() -> 23 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 24 | 25 | %% @doc Start http server 26 | %% @end 27 | -spec start_web_admin() -> {ok, Pid :: pid()} | {error, Reason :: term()}. 28 | start_web_admin() -> 29 | % run http server 30 | supervisor:start_child(?MODULE, []). 31 | 32 | init([]) -> 33 | % Check ranch and cowboy 34 | case whereis(ranch) of 35 | undefined -> 36 | % start ranch application 37 | application:start(ranch), 38 | % start cowboy application 39 | application:start(cowboy); 40 | _ -> 41 | ok 42 | end, 43 | 44 | % http server child process 45 | ChildSpec = [ 46 | {web_admin, 47 | {web_admin, start_link, []}, 48 | temporary, 2000, worker, [] 49 | } 50 | ], 51 | 52 | % init 53 | {ok,{{simple_one_for_one, 10, 60}, ChildSpec}}. 54 | -------------------------------------------------------------------------------- /src/ybot.app.src: -------------------------------------------------------------------------------- 1 | {application, ybot, 2 | [ 3 | {description, "Ybot - Github hubot inspired bot with different plugins and adapters."}, 4 | {vsn, "0.0.1"}, 5 | {registered, []}, 6 | {applications, [ 7 | kernel, 8 | stdlib, 9 | syntax_tools, 10 | crypto, 11 | public_key, 12 | ssl, 13 | mnesia, 14 | compiler, 15 | lager, 16 | inets, 17 | ranch, 18 | cowlib, 19 | cowboy, 20 | ibrowse, 21 | jiffy, 22 | reloader 23 | ]}, 24 | {mod, {ybot_app, []}}, 25 | {env, [{config, "ybot.config"}]} 26 | ]}. 27 | -------------------------------------------------------------------------------- /src/ybot.erl: -------------------------------------------------------------------------------- 1 | %%%----------------------------------------------------------------------------- 2 | %%% @author 0xAX 3 | %%% @doc 4 | %%% Ybot entry point. 5 | %%% @end 6 | %%%----------------------------------------------------------------------------- 7 | -module(ybot). 8 | 9 | -export([start/0, stop/0]). 10 | 11 | -spec start() -> ok. 12 | start() -> 13 | [application:start(A) || A <- deps() ++ [ybot]], 14 | ok. 15 | 16 | -spec stop() -> ok. 17 | stop() -> 18 | [application:stop(A) || A <- lists:reverse(deps()) ++ [ybot]], 19 | ok. 20 | 21 | %% Internal functions 22 | deps() -> 23 | [compiler, syntax_tools, asn1, lager, inets, crypto, public_key, ssl, 24 | mnesia, ranch, cowlib, cowboy, jiffy, ibrowse, reloader]. 25 | -------------------------------------------------------------------------------- /src/ybot_actor.erl: -------------------------------------------------------------------------------- 1 | %%%----------------------------------------------------------------------------- 2 | %%% @author 0xAX 3 | %%% @doc 4 | %%% Ybot main plugin executor. 5 | %%% @end 6 | %%%----------------------------------------------------------------------------- 7 | -module(ybot_actor). 8 | 9 | -behaviour(gen_server). 10 | 11 | -export([start_link/4, stop/0, handle_command/4]). 12 | 13 | %% gen_server callbacks 14 | -export([init/1, 15 | handle_call/3, 16 | handle_cast/2, 17 | handle_info/2, 18 | terminate/2, 19 | code_change/3 20 | ]). 21 | 22 | -type cmd() :: string(). 23 | -type cmd_args() :: [string()]. 24 | -type transport() :: pid(). 25 | 26 | -record(state, {}). 27 | 28 | 29 | %%============================================================================= 30 | %% API functions 31 | %%============================================================================= 32 | 33 | start_link(TransportPid, From, Command, Args) -> 34 | gen_server:start_link(?MODULE, [TransportPid, From, Command, Args], []). 35 | 36 | stop() -> 37 | gen_server:cast(?MODULE, stop). 38 | 39 | 40 | %%============================================================================= 41 | %% ybot_actor callbacks 42 | %%============================================================================= 43 | 44 | init([TransportPid, From, Command, Args]) -> 45 | % execute plugin 46 | gen_server:cast(self(), {execute, TransportPid, From, Command, Args}), 47 | 48 | % init 49 | {ok, #state{}}. 50 | 51 | handle_call(_Request, _From, State) -> 52 | {reply, ignored, State}. 53 | 54 | %% @doc Try to execute command 55 | handle_cast({execute, TransportPid, From, Command, Args}, State) -> 56 | try 57 | % Log 58 | lager:info("Command: ~s, ~p", [Command, Args]), 59 | 60 | % Handle received command 61 | handle_command(From, Command, Args, TransportPid), 62 | 63 | % stop actor 64 | stop() 65 | catch 66 | _:Reason -> 67 | lager:error("Unable to execute plugin! Error=~p, Stack=~p", 68 | [Reason, erlang:get_stacktrace()]) 69 | end, 70 | {noreply, State}; 71 | 72 | handle_cast(stop, State) -> 73 | {stop, normal, State}; 74 | 75 | handle_cast(_Msg, State) -> 76 | {noreply, State}. 77 | 78 | handle_info(_Info, State) -> 79 | {noreply, State}. 80 | 81 | terminate(_Reason, _State) -> 82 | ok. 83 | 84 | code_change(_OldVsn, State, _Extra) -> 85 | {ok, State}. 86 | 87 | 88 | %%============================================================================= 89 | %% Internal functions 90 | %%============================================================================= 91 | 92 | %% @doc Try to find plugin and execute it 93 | -spec handle_command(string(), cmd(), cmd_args(), transport()) -> ok | pass. 94 | handle_command(From, Command, Args, TransportPid) -> 95 | %% Get plugin metadata 96 | case gen_server:call(ybot_manager, {get_plugin, Command}) of 97 | wrong_plugin -> 98 | %% plugin not found 99 | send_message(TransportPid, From, "Sorry, but i don't know about this :("); 100 | 101 | {plugin, "erlang", PluginName, AppModule} -> 102 | %% execute Erlang/OTP plugin 103 | Module = list_to_atom(AppModule), 104 | Result = Module:execute(Args), 105 | ok = store_history(TransportPid, create_message(PluginName, Args)), 106 | send_message(TransportPid, From, Result); 107 | 108 | {plugin, Lang, _PluginName, PluginPath} -> 109 | %% execute plugins using command 110 | CmdArgs = join(os_escape(Args), " "), 111 | Cmd = Lang ++ " " ++ PluginPath ++ CmdArgs, 112 | %%lager:info("Exec: ~p", [Cmd]), 113 | Result = binary_to_list(unicode:characters_to_binary(os:cmd(Cmd))), 114 | ok = store_history(TransportPid, create_message(Command, CmdArgs)), 115 | send_message(TransportPid, From, Result) 116 | end. 117 | 118 | send_message(TransportPid, From, Message) -> 119 | gen_server:cast(TransportPid, {send_message, From, Message}). 120 | 121 | create_message(Name, Args) -> 122 | "Ybot " ++ Name ++ " " ++ Args ++ "\n". 123 | 124 | store_history(TransportPid, Message) -> 125 | gen_server:cast(ybot_history, {update_history, TransportPid, Message}). 126 | 127 | join([], _Sep) -> ""; 128 | join([H | T], Sep) -> 129 | lists:flatten([H | [[Sep, X] || X <- T]]). 130 | 131 | os_escape([]) -> 132 | []; 133 | os_escape(Args) -> 134 | os_escape(Args, [$",$ ]). 135 | 136 | os_escape([], Acc) -> 137 | lists:reverse([$"|Acc]); 138 | os_escape([$"|Args], Acc) -> 139 | os_escape(Args, [$",$\\|Acc]); 140 | os_escape([C|Args], Acc) -> 141 | os_escape(Args, [C|Acc]). 142 | -------------------------------------------------------------------------------- /src/ybot_app.erl: -------------------------------------------------------------------------------- 1 | %%%----------------------------------------------------------------------------- 2 | %%% @author 0xAX 3 | %%% @doc 4 | %%% Ybot application. 5 | %%% @end 6 | %%%----------------------------------------------------------------------------- 7 | -module(ybot_app). 8 | 9 | -behaviour(application). 10 | 11 | %% Application callbacks 12 | -export([start/2, stop/1]). 13 | 14 | %% =================================================================== 15 | %% Application callbacks 16 | %% =================================================================== 17 | 18 | start(_StartType, _StartArgs) -> 19 | ybot_sup:start_link(). 20 | 21 | stop(_State) -> 22 | ok. 23 | -------------------------------------------------------------------------------- /src/ybot_brain.erl: -------------------------------------------------------------------------------- 1 | %%%----------------------------------------------------------------------------- 2 | %%% @author tgrk 3 | %%% @doc 4 | %%% Ybot brain API. 5 | %%% @end 6 | %%%----------------------------------------------------------------------------- 7 | -module(ybot_brain). 8 | 9 | -behaviour(gen_server). 10 | 11 | -include("ybot.hrl"). 12 | 13 | -export([post/3, 14 | post/4, 15 | put/4, 16 | delete/1, 17 | get_by_id/1, 18 | get_by_key/1, 19 | get_by_value/1, 20 | get_by_plugin/1, 21 | get_all/0, 22 | get/2, 23 | 24 | start_link/1, 25 | stop/0 26 | ]). 27 | 28 | %% gen_server callbacks 29 | -export([init/1, handle_call/3, handle_cast/2, handle_info/2, 30 | terminate/2, code_change/3]). 31 | 32 | -define(SERVER, ?MODULE). 33 | 34 | -record(state, {storage=undefined}). 35 | 36 | 37 | %% API 38 | start_link(Type) -> 39 | gen_server:start_link({local, ?SERVER}, ?MODULE, [Type], []). 40 | 41 | stop() -> 42 | gen_server:cast({local,?MODULE}, stop). 43 | 44 | post(Plugin, Key, Value) -> 45 | gen_server:call(?MODULE, {post, [Plugin, Key, Value]}). 46 | 47 | post(Id, Plugin, Key, Value) -> 48 | gen_server:call(?MODULE, {post, [Id, Plugin, Key, Value]}). 49 | 50 | put(Id, Plugin, Key, Value) -> 51 | gen_server:call(?MODULE, {put, [Id, Plugin, Key, Value]}). 52 | 53 | get_by_id(Id) -> 54 | gen_server:call(?MODULE, {get_by_id, [Id]}). 55 | 56 | get_all() -> 57 | gen_server:call(?MODULE, get_all). 58 | 59 | get(Plugin, Key) -> 60 | gen_server:call(?MODULE, {get, [Plugin, Key]}). 61 | 62 | get_by_plugin(Plugin) -> 63 | gen_server:call(?MODULE, {get_by_plugin, [Plugin]}). 64 | 65 | get_by_key(Key) -> 66 | gen_server:call(?MODULE, {get_by_key, [Key]}). 67 | 68 | get_by_value(Value) -> 69 | gen_server:call(?MODULE, {get_by_value, [Value]}). 70 | 71 | delete(Id) -> 72 | gen_server:call(?MODULE, {delete, [Id]}). 73 | 74 | %% gen_server callbacks 75 | init([Type]) -> 76 | Db = start_storage(Type), 77 | {ok, #state{storage=Db}}. 78 | 79 | handle_call({post, [Plugin, Key, Value]}, _From, 80 | #state{storage = Db} = State) -> 81 | {reply, Db:post(Plugin, Key, Value), State}; 82 | handle_call({post, [Id, Plugin, Key, Value]}, _From, 83 | #state{storage = Db} = State) -> 84 | {reply, Db:post(Id, Plugin, Key, Value), State}; 85 | handle_call({put, [Id, Plugin, Key, Value]}, _From, 86 | #state{storage = Db} = State) -> 87 | {reply, Db:put(Id, Plugin, Key, Value), State}; 88 | handle_call({get_by_id, [Id]}, _From, #state{storage = Db} = State) -> 89 | {reply, Db:get_by_id(Id), State}; 90 | handle_call({get_by_key, [Key]}, _From, #state{storage = Db} = State) -> 91 | {reply, Db:get_by_key(Key), State}; 92 | handle_call(get_all, _From, #state{storage = Db} = State) -> 93 | {reply, Db:get_all(), State}; 94 | handle_call({get_by_plugin, [Plugin]}, _From, #state{storage = Db} = State) -> 95 | {reply, Db:get_by_plugin(Plugin), State}; 96 | handle_call({get_by_value, [Value]}, _From, #state{storage = Db} = State) -> 97 | {reply, Db:get_by_value(Value), State}; 98 | handle_call({get, [Plugin, Key]}, _From, #state{storage = Db} = State) -> 99 | {reply, Db:get(Plugin, Key), State}; 100 | handle_call({delete, [Id]}, _From, #state{storage = Db} = State) -> 101 | {reply, Db:delete(Id), State}; 102 | handle_call(_Request, _From, State) -> 103 | {reply, ok, State}. 104 | 105 | handle_cast(stop, #state{storage = Db} = State) -> 106 | Db:stop(), 107 | {noreply, State}; 108 | handle_cast(_Msg, State) -> 109 | {noreply, State}. 110 | 111 | handle_info(_Info, State) -> 112 | {noreply, State}. 113 | 114 | terminate(_Reason, #state{storage = Db}) -> 115 | Db:stop(), 116 | ok. 117 | 118 | code_change(_OldVsn, State, _Extra) -> 119 | {ok, State}. 120 | 121 | %% Internal functions 122 | start_storage(Type) -> 123 | % select and start brain storage backend 124 | Db = case Type of 125 | mnesia -> 126 | ybot_brain_mnesia; 127 | _ -> 128 | %TODO - use memory only? 129 | lager:error("Unknown brain storage module - ~s", [Type]), 130 | undefined 131 | end, 132 | Db:start(), 133 | lager:info("Brain ~s storage started", [Type]), 134 | Db. 135 | -------------------------------------------------------------------------------- /src/ybot_brain_api.erl: -------------------------------------------------------------------------------- 1 | %%%---------------------------------------------------------------------------- 2 | %%% File : ybot_brain_api.erl 3 | %%% Author : tgrk 4 | %%% Purpose : Ybot brain REST API 5 | %%%---------------------------------------------------------------------------- 6 | -module(ybot_brain_api). 7 | 8 | -include("ybot.hrl"). 9 | 10 | %% REST Callbacks 11 | -export([init/3, 12 | allowed_methods/2, 13 | content_types_provided/2, 14 | content_types_accepted/2, 15 | resource_exists/2, 16 | post_is_create/2, 17 | create_path/2, 18 | create_json/2, 19 | get_json/2, 20 | delete_resource/2, 21 | get_uuid/0 22 | ]). 23 | 24 | %% API 25 | init(_Transport, _Req, []) -> 26 | {upgrade, protocol, cowboy_rest}. 27 | 28 | allowed_methods(Req, State) -> 29 | {[<<"HEAD">>, <<"GET">>, <<"POST">>, <<"PUT">>, <<"DELETE">>], Req, State}. 30 | 31 | content_types_accepted(Req, State) -> 32 | {[ 33 | {{<<"application">>, <<"json">>, []}, create_json} 34 | ], Req, State}. 35 | 36 | content_types_provided(Req, State) -> 37 | {[ 38 | {{<<"application">>, <<"json">>, []}, get_json} 39 | ], Req, State}. 40 | 41 | resource_exists(Req, _State) -> 42 | case cowboy_req:binding(memory_id, Req) of 43 | {undefined, Req2} -> 44 | {true, Req2, all}; 45 | {Id, Req2} -> 46 | {true, Req2, hex_to_bin(Id)} 47 | end. 48 | 49 | post_is_create(Req, State) -> 50 | {true, Req, State}. 51 | 52 | create_path(Req, State) -> 53 | {get_uuid(), Req, State}. 54 | 55 | create_json(Req, Id) -> 56 | try 57 | {Method, Req1} = cowboy_req:method(Req), 58 | {ok, Body, Req2} = cowboy_req:body(Req1), 59 | Json = from_json(Body), 60 | store(Method, Id, deserialize(Json)), 61 | {true, Req2, Id} 62 | catch 63 | _:Reason -> 64 | lager:error("Unable to create memory. Invalid JSON!" " Error=~p, Stack=~p", [Reason, erlang:get_stacktrace()]), 65 | {false, Req, Id} 66 | end. 67 | 68 | get_json(Req, all) -> 69 | {Params, Req1} = cowboy_req:qs_vals(Req), 70 | {get_by_params(Params), Req1, all}; 71 | get_json(Req, Id) -> 72 | {get_by_id(Id), Req, Id}. 73 | 74 | delete_resource(Req, Id) -> 75 | ybot_brain:delete(Id), 76 | {true, Req, Id}. 77 | 78 | 79 | %% Internal functions 80 | store(<<"POST">>, all, List) -> 81 | ybot_brain:post(get_value(<<"plugin">>, List), 82 | get_value(<<"key">>, List), 83 | get_value(<<"value">>, List) 84 | ); 85 | store(<<"POST">>, Id, List) -> 86 | ybot_brain:post(Id, 87 | get_value(<<"plugin">>, List), 88 | get_value(<<"key">>, List), 89 | get_value(<<"value">>, List) 90 | ); 91 | store(<<"PUT">>, Id, List) -> 92 | ybot_brain:put(Id, 93 | get_value(<<"plugin">>, List), 94 | get_value(<<"key">>, List), 95 | get_value(<<"value">>, List) 96 | ). 97 | 98 | get_by_id(Id) -> 99 | case ybot_brain:get_by_id(Id) of 100 | [] -> false; 101 | [Item] -> to_json(serialize(Item)) 102 | end. 103 | 104 | get_by_params([]) -> 105 | to_json( 106 | serialize(ybot_brain:get_all()) 107 | ); 108 | get_by_params([{<<"plugin">>, Plugin}]) -> 109 | to_json( 110 | serialize(ybot_brain:get_by_plugin(Plugin)) 111 | ); 112 | get_by_params([{<<"key">>, Key}]) -> 113 | to_json( 114 | serialize(ybot_brain:get_by_key(Key)) 115 | ); 116 | get_by_params([{<<"value">>, Value}]) -> 117 | to_json( 118 | serialize(ybot_brain:get_by_value(Value)) 119 | ); 120 | get_by_params([{<<"plugin">>, Plugin}, {<<"key">>, Key}]) -> 121 | to_json( 122 | serialize(ybot_brain:get(Plugin, Key)) 123 | ). 124 | 125 | % Helpers 126 | from_json(Input) -> 127 | jiffy:decode(Input). 128 | 129 | deserialize(Item) -> 130 | case validate_json(Item) of 131 | true -> 132 | Item; 133 | false -> 134 | throw({invalid_json, Item}) 135 | end. 136 | 137 | serialize(Records) when is_list(Records) -> 138 | [serialize(R) || R <- Records]; 139 | serialize(Record) -> 140 | HexId = bin_to_hex(Record#memory.uuid), 141 | {[ 142 | {id, HexId}, 143 | {url, get_resource_url(HexId)}, 144 | {plugin, Record#memory.plugin}, 145 | {key, Record#memory.key}, 146 | {value, Record#memory.value}, 147 | {created, format_datetime(Record#memory.created)} 148 | ]}. 149 | 150 | validate_json({[{<<"plugin">>, _}, {<<"key">>, _}, {<<"value">>, _}]}) -> 151 | true; 152 | validate_json({[{<<"id">>, _}, {<<"plugin">>, _}, {<<"key">>, _}, {<<"value">>, _}]}) -> 153 | true; 154 | validate_json({[{<<"value">>, _}, {<<"created">>, _}, {<<"id">>, _}, {<<"key">>, _}, {<<"plugin">>, _}]}) -> 155 | true; 156 | validate_json(_Other) -> 157 | false. 158 | 159 | to_json(Input) -> 160 | ybot_utils:to_binary(jiffy:encode(Input)). 161 | 162 | get_value(Key, {List}) -> 163 | proplists:get_value(Key, List). 164 | 165 | format_datetime({{Y,M,D},{H,Mi,S}}) -> 166 | list_to_binary( 167 | lists:flatten( 168 | io_lib:format("~4B-~2..0B-~2..0B ~2B:~2..0B:~2..0B", 169 | [Y, M, D, H, Mi, S]) 170 | ) 171 | ). 172 | 173 | get_resource_url(Id) -> 174 | Host = ybot_utils:to_list(ybot_utils:get_config_val(brain_api_host)), 175 | Port = ybot_utils:to_list(ybot_utils:get_config_val(brain_api_port)), 176 | ybot_utils:to_binary("http://" ++ Host ++ ":" ++ Port ++ "/memories/" ++ ybot_utils:to_list(Id)). 177 | 178 | bin_to_hex(Bin) -> 179 | <<<<(binary:list_to_bin( 180 | case length(S = integer_to_list(I, 16)) of 1 -> [48|S]; 2 -> S end 181 | ))/bytes>> || <> <= Bin>>. 182 | 183 | hex_to_bin(Hex) when is_list(Hex) -> 184 | <<<<(list_to_integer([I1,I2], 16))>> || <> <= list_to_binary(Hex)>>; 185 | hex_to_bin(Hex) -> 186 | <<<<(list_to_integer([I1,I2], 16))>> || <> <= Hex>>. 187 | 188 | get_uuid() -> 189 | <<(crypto:rand_bytes(8))/bytes, 190 | (erlang:term_to_binary(erlang:now()))/bytes>>. -------------------------------------------------------------------------------- /src/ybot_brain_api_server.erl: -------------------------------------------------------------------------------- 1 | %%%----------------------------------------------------------------------------- 2 | %%% @author tgrk 3 | %%% @doc 4 | %%% Ybot brain HTTP API server. 5 | %%% @end 6 | %%%----------------------------------------------------------------------------- 7 | -module(ybot_brain_api_server). 8 | 9 | -behaviour(gen_server). 10 | 11 | -export([start_link/2]). 12 | 13 | %% gen_server callbacks 14 | -export([init/1, 15 | handle_call/3, 16 | handle_cast/2, 17 | handle_info/2, 18 | terminate/2, 19 | code_change/3]). 20 | 21 | -record(state, {}). 22 | 23 | %% API 24 | start_link(Host, Port) -> 25 | Host1 = binary_to_list(Host), 26 | lager:info("Start brain REST API: http://~s:~p/memories", [Host1, Port]), 27 | gen_server:start_link(?MODULE, [Host1, Port], []). 28 | 29 | init([Host, Port]) -> 30 | % start server 31 | ok = gen_server:cast(self(), {start_server, Host, Port}), 32 | % init state 33 | {ok, #state{}}. 34 | 35 | handle_call(_Request, _From, State) -> 36 | {reply, ignored, State}. 37 | 38 | %% @doc Start HTTP REST API 39 | handle_cast({start_server, Host, Port}, State) -> 40 | % cowboy routes 41 | Dispatch = cowboy_router:compile([ 42 | {Host, [{"/memories/[:memory_id]", ybot_brain_api, []}]} 43 | ]), 44 | 45 | {ok, _} = cowboy:start_http(http, 100, [{port, Port}], [ 46 | {env, [{dispatch, Dispatch}]} 47 | ]), 48 | {noreply, State}; 49 | 50 | handle_cast(_Msg, State) -> 51 | {noreply, State}. 52 | 53 | handle_info(_Info, State) -> 54 | {noreply, State}. 55 | 56 | terminate(_Reason, _State) -> 57 | ok. 58 | 59 | code_change(_OldVsn, State, _Extra) -> 60 | {ok, State}. 61 | -------------------------------------------------------------------------------- /src/ybot_brain_mnesia.erl: -------------------------------------------------------------------------------- 1 | %%%----------------------------------------------------------------------------- 2 | %%% @author tgrk 3 | %%% @doc 4 | %%% Ybot Mnesia brain API storage. 5 | %%% @end 6 | %%%----------------------------------------------------------------------------- 7 | -module(ybot_brain_mnesia). 8 | 9 | -include("ybot.hrl"). 10 | -include_lib("stdlib/include/qlc.hrl"). 11 | 12 | -export([post/3, 13 | post/4, 14 | put/4, 15 | delete/1, 16 | get_by_id/1, 17 | get_by_key/1, 18 | get_by_value/1, 19 | get_by_plugin/1, 20 | get_all/0, 21 | get/2, 22 | 23 | start/0, 24 | stop/0 25 | ]). 26 | 27 | %% API 28 | start() -> 29 | case mnesia:create_schema([node()]) of 30 | {error, {_,{already_exists, _}}} -> 31 | % if there is already ram_copies schema 32 | case mnesia:system_info(local_tables) of 33 | [schema] -> 34 | mnesia:change_table_copy_type(schema, node(), disc_copies), 35 | create_schema(); 36 | _ -> 37 | already_created 38 | end, 39 | start_db(); 40 | {error, Error} -> 41 | exit(Error); 42 | ok -> 43 | start_db(), 44 | create_schema(), 45 | ok 46 | end. 47 | 48 | stop() -> 49 | mnesia:stop(). 50 | 51 | post(Plugin, Key, Value) -> 52 | run(fun() -> 53 | mnesia:write( 54 | #memory{ 55 | uuid = ybot_brain_api:get_uuid(), 56 | plugin = Plugin, 57 | key = Key, 58 | value = Value, 59 | created = erlang:localtime() 60 | } 61 | ) 62 | end). 63 | 64 | post(Id, Plugin, Key, Value) -> 65 | run(fun() -> 66 | mnesia:write( 67 | #memory{ 68 | uuid = Id, 69 | plugin = Plugin, 70 | key = Key, 71 | value = Value, 72 | created = erlang:localtime() 73 | } 74 | ) 75 | end). 76 | 77 | put(Id, Plugin, Key, Value) -> 78 | run(fun() -> 79 | [R] = mnesia:wread({memory, Id}), 80 | mnesia:write( 81 | R#memory{ 82 | plugin = Plugin, 83 | key = Key, 84 | value = Value, 85 | created = erlang:localtime() 86 | } 87 | ) 88 | end). 89 | 90 | delete(Id) -> 91 | run(fun() -> mnesia:delete({memory, Id}) end). 92 | 93 | get_by_id(Id) -> 94 | run(fun() -> mnesia:wread({memory, Id}) end). 95 | 96 | get_all() -> 97 | run(fun() -> qlc:eval(qlc:q([X || X <- mnesia:table(memory)])) end). 98 | 99 | get_by_plugin(Plugin) -> 100 | run(fun() -> 101 | qlc:eval( 102 | qlc:q([X || X <- mnesia:table(memory), 103 | X#memory.plugin =:= Plugin]) 104 | ) 105 | end). 106 | 107 | get_by_key(Key) -> 108 | run(fun() -> 109 | qlc:eval( 110 | qlc:q([X || X <- mnesia:table(memory), 111 | X#memory.key =:= Key]) 112 | ) 113 | end). 114 | 115 | get_by_value(Value) -> 116 | run(fun() -> 117 | qlc:eval( 118 | qlc:q([X || X <- mnesia:table(memory), 119 | X#memory.value =:= Value]) 120 | ) 121 | end). 122 | 123 | get(Plugin, Key) -> 124 | run(fun() -> 125 | qlc:eval( 126 | qlc:q([X || X <- mnesia:table(memory), 127 | X#memory.plugin =:= Plugin, 128 | X#memory.key =:= Key 129 | ]) 130 | ) 131 | end). 132 | 133 | %% Internal functions 134 | start_db() -> 135 | case mnesia:start() of 136 | {error, Error1} -> 137 | exit(Error1); 138 | ok -> ok 139 | end. 140 | 141 | create_schema() -> 142 | mnesia:create_table(memory, 143 | [{attributes, record_info(fields, memory)}, 144 | {index, [plugin, key]}, 145 | {disc_copies, [node()]} 146 | ]). 147 | 148 | run(ExecFun) -> 149 | try 150 | {atomic, State} = mnesia:transaction(ExecFun), 151 | State 152 | catch 153 | _:Reason -> 154 | lager:error("Unable to run brain query! Error ~p", [Reason]), 155 | {error, Reason} 156 | end. 157 | -------------------------------------------------------------------------------- /src/ybot_brain_sup.erl: -------------------------------------------------------------------------------- 1 | %%%----------------------------------------------------------------------------- 2 | %%% @author tgrk 3 | %%% @doc 4 | %%% Ybot brain HTTP API supervisor. 5 | %%% @end 6 | %%%----------------------------------------------------------------------------- 7 | 8 | -module(ybot_brain_sup). 9 | 10 | -behaviour(supervisor). 11 | 12 | %% API 13 | -export([start_link/0]). 14 | 15 | %% Supervisor callbacks 16 | -export([init/1]). 17 | 18 | %%====================================================================== 19 | %% API functions 20 | %%====================================================================== 21 | 22 | start_link() -> 23 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 24 | 25 | %%====================================================================== 26 | %% Supervisor callback 27 | %%====================================================================== 28 | 29 | init([]) -> 30 | 31 | % Get brain backend storage 32 | {ok, BrainStorage} = application:get_env(ybot, brain_storage), 33 | 34 | % brain API configuration 35 | {ok, ApiHost} = application:get_env(ybot, brain_api_host), 36 | {ok, ApiPort} = application:get_env(ybot, brain_api_port), 37 | 38 | ChildSpec = [ 39 | {ybot_brain, 40 | {ybot_brain, start_link, [BrainStorage]}, 41 | permanent, 2000, worker, [] 42 | }, 43 | 44 | {ybot_brain_api_server, 45 | {ybot_brain_api_server, start_link, [ApiHost, ApiPort]}, 46 | permanent, 2000, worker, [] 47 | } 48 | ], 49 | 50 | {ok,{{one_for_one, 10, 60}, ChildSpec}}. 51 | -------------------------------------------------------------------------------- /src/ybot_history.erl: -------------------------------------------------------------------------------- 1 | %%%----------------------------------------------------------------------------- 2 | %%% @author 0xAX 3 | %%% @doc 4 | %%% Provides Ybot history storage. 5 | %%% @end 6 | %%%----------------------------------------------------------------------------- 7 | -module(ybot_history). 8 | 9 | -behaviour(gen_server). 10 | 11 | -export([start_link/1, stop/0]). 12 | 13 | %% gen_server callbacks 14 | -export([init/1, 15 | handle_call/3, 16 | handle_cast/2, 17 | handle_info/2, 18 | terminate/2, 19 | code_change/3]). 20 | 21 | %% @doc internal state 22 | -record(state, { 23 | % history command limit 24 | limit = 0, 25 | % command history list 26 | history = [] 27 | }). 28 | 29 | %%%============================================================================= 30 | %%% API 31 | %%%============================================================================= 32 | 33 | start_link(HistoryLimit) -> 34 | gen_server:start_link({local, ?MODULE}, ?MODULE, [HistoryLimit], []). 35 | 36 | stop() -> 37 | gen_server:cast(?MODULE, stop). 38 | 39 | %%%============================================================================= 40 | %%% ybot_history callbacks 41 | %%%============================================================================= 42 | 43 | init([HistoryLimit]) -> 44 | % init state 45 | {ok, #state{limit = HistoryLimit}}. 46 | 47 | %% @doc return command history 48 | handle_call({get_history, Transport}, _From, State) -> 49 | % Get history for Transport 50 | History = lists:flatten([Command || {Tr, Command}<- State#state.history, Tr == Transport]), 51 | % return history 52 | {reply, History, State}; 53 | 54 | %% @doc return history limit 55 | handle_call(get_history_limit, _From, State) -> 56 | {reply, State#state.limit, State}; 57 | 58 | handle_call(_Request, _From, State) -> 59 | {reply, ignored, State}. 60 | 61 | %% update history limit 62 | handle_cast({update_history_limit, HistoryLimit}, State) -> 63 | {noreply, State#state{limit = HistoryLimit}}; 64 | 65 | %% @doc stop history process 66 | handle_cast(stop, State) -> 67 | {stop, normal, State}; 68 | 69 | %% @doc update history 70 | handle_cast({update_history, From, Command}, State) -> 71 | % Check limit 72 | case length([{Transport, CommandHistory} || {Transport, CommandHistory} <- State#state.history, Transport == From]) >= State#state.limit of 73 | true -> 74 | % Save history 75 | {noreply, State#state{history = [{From, Command}]}}; 76 | false -> 77 | {noreply, State#state{history = lists:append(State#state.history, [{From, Command}])}} 78 | end; 79 | 80 | handle_cast(_Msg, State) -> 81 | {noreply, State}. 82 | 83 | handle_info(_Info, State) -> 84 | {noreply, State}. 85 | 86 | terminate(_Reason, _State) -> 87 | ok. 88 | 89 | code_change(_OldVsn, State, _Extra) -> 90 | {ok, State}. 91 | 92 | %%%============================================================================= 93 | %%% Internal functions 94 | %%%============================================================================= -------------------------------------------------------------------------------- /src/ybot_notification.erl: -------------------------------------------------------------------------------- 1 | %%%----------------------------------------------------------------------------- 2 | %%% @author 0xAX 3 | %%% @doc 4 | %%% Ybot notification manager 5 | %%% @end 6 | %%%----------------------------------------------------------------------------- 7 | -module(ybot_notification). 8 | 9 | -behaviour(gen_server). 10 | 11 | -export([start_link/2]). 12 | 13 | %% gen_server callbacks 14 | -export([init/1, 15 | handle_call/3, 16 | handle_cast/2, 17 | handle_info/2, 18 | terminate/2, 19 | code_change/3]). 20 | 21 | -record(state, {}). 22 | 23 | start_link(Notification, NotificationsDir) -> 24 | gen_server:start_link({local, ?MODULE}, ?MODULE, [Notification, NotificationsDir], []). 25 | 26 | init([Notification, NotificationsDir]) -> 27 | % init notification manager 28 | gen_server:cast(?MODULE, {init, Notification, NotificationsDir}), 29 | % return 30 | {ok, #state{}}. 31 | 32 | handle_call(_Request, _From, State) -> 33 | {reply, ignored, State}. 34 | 35 | handle_cast({init, Notification, NotificationsDir}, State) -> 36 | % traverse all notifications and start notification handler 37 | lists:foreach(fun(Not) -> 38 | % start notification handler 39 | ybot_notification_sup:start_notification_proc(Not, NotificationsDir) 40 | end, 41 | Notification), 42 | % return 43 | {noreply, State}; 44 | 45 | handle_cast(_Msg, State) -> 46 | {noreply, State}. 47 | 48 | handle_info(_Info, State) -> 49 | {noreply, State}. 50 | 51 | terminate(_Reason, _State) -> 52 | ok. 53 | 54 | code_change(_OldVsn, State, _Extra) -> 55 | {ok, State}. 56 | 57 | %% Internal functions -------------------------------------------------------------------------------- /src/ybot_notification_handler.erl: -------------------------------------------------------------------------------- 1 | %%%----------------------------------------------------------------------------- 2 | %%% @author 0xAX 3 | %%% @doc 4 | %%% Ybot notification handler. 5 | %%% @end 6 | %%%----------------------------------------------------------------------------- 7 | -module(ybot_notification_handler). 8 | 9 | -behaviour(gen_server). 10 | 11 | -export([start_link/2]). 12 | 13 | %% gen_server callbacks 14 | -export([init/1, 15 | handle_call/3, 16 | handle_cast/2, 17 | handle_info/2, 18 | terminate/2, 19 | code_change/3]). 20 | 21 | -record(state, { 22 | % transports for notifications 23 | transports = [], 24 | % plugin language 25 | lang = "", 26 | % plugin file path 27 | plugin_path = "", 28 | % notification timeout 29 | timeout = 0 30 | }). 31 | 32 | start_link(Notification, NotificationDir) -> 33 | gen_server:start_link(?MODULE, [Notification, NotificationDir], []). 34 | 35 | init([Notification, NotificationDir]) -> 36 | % some logs 37 | lager:info("Start notification for ~p", [Notification]), 38 | % init handler 39 | gen_server:cast(self(), {init_handler, Notification, NotificationDir}), 40 | % return 41 | {ok, #state{}}. 42 | 43 | handle_call(_Request, _From, State) -> 44 | {reply, ignored, State}. 45 | 46 | handle_cast({init_handler, {PluginName, TranportsList, Timeout}, NotificationDir}, State) -> 47 | % Get runned transports 48 | RunnedTransports = lists:flatten(lists:map(fun(Transp) -> 49 | % Check is notification for this transport or not 50 | IsTransport = lists:member(element(1, Transp), TranportsList), 51 | case IsTransport of 52 | true -> 53 | element(2, Transp); 54 | false -> 55 | [] 56 | end 57 | end, 58 | gen_server:call(ybot_manager, get_transports))), 59 | 60 | % Get necessary channels 61 | Channels = lists:flatten(lists:map(fun(Channel) -> 62 | % Check is notification for this channel or not 63 | IsChannel = lists:member(element(1, Channel), TranportsList), 64 | case IsChannel of 65 | true -> 66 | element(2, Channel); 67 | false -> 68 | [] 69 | end 70 | end, 71 | gen_server:call(ybot_manager, get_channels))), 72 | % Make transports list 73 | Transports = lists:append(RunnedTransports, Channels), 74 | % Get plugin file with extension 75 | [PluginWithExt | _] = filelib:wildcard(atom_to_list(PluginName) ++ "*", NotificationDir), 76 | % Get lang 77 | {plugin, Lang, Plugin, PluginWithExt} = ybot_manager:load_plugin(PluginWithExt), 78 | % Get full plugin path 79 | PluginPath = NotificationDir ++ PluginWithExt, 80 | % start notifications 81 | erlang:send_after(Timeout * 1000, self(), execute), 82 | % return 83 | {noreply, State#state{transports = Transports, lang = Lang, timeout = Timeout * 1000, plugin_path = PluginPath}}; 84 | 85 | handle_cast(_Msg, State) -> 86 | {noreply, State}. 87 | 88 | handle_info(execute, State) -> 89 | % execute plugin 90 | Result = os:cmd(State#state.lang ++ " " ++ State#state.plugin_path), 91 | io:format("Result ~p~n", [Result]), 92 | case Result of 93 | "" -> 94 | ok; 95 | "\n" -> 96 | ok; 97 | "\r\n" -> 98 | ok; 99 | _ -> 100 | % send notification to the chats 101 | lists:foreach(fun(Transport) -> 102 | % send message 103 | gen_server:cast(Transport, {send_message, "", Result}) 104 | end, 105 | State#state.transports) 106 | end, 107 | % start new notification 108 | erlang:send_after(State#state.timeout, self(), execute), 109 | % return 110 | {noreply, State}; 111 | 112 | handle_info(_Info, State) -> 113 | {noreply, State}. 114 | 115 | terminate(_Reason, _State) -> 116 | ok. 117 | 118 | code_change(_OldVsn, State, _Extra) -> 119 | {ok, State}. 120 | 121 | %% Internal functions -------------------------------------------------------------------------------- /src/ybot_notification_sup.erl: -------------------------------------------------------------------------------- 1 | %%%----------------------------------------------------------------------------- 2 | %%% @author 0xAX 3 | %%% @doc 4 | %%% Ybot notification process supervisor. 5 | %%% @end 6 | %%%----------------------------------------------------------------------------- 7 | -module(ybot_notification_sup). 8 | 9 | -behaviour(supervisor). 10 | 11 | %% API 12 | -export([start_link/0, start_notification_proc/2]). 13 | 14 | %% Supervisor callbacks 15 | -export([init/1]). 16 | 17 | 18 | %% =================================================================== 19 | %% API functions 20 | %% =================================================================== 21 | 22 | start_link() -> 23 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 24 | 25 | %% @doc Start new notification process 26 | %% @end 27 | -spec start_notification_proc({PluginName :: string(), NotificationTimeout :: integer()}, NotificationDir :: string()) -> 28 | {ok, Pid :: pid()} | {error, Reason :: term()}. 29 | start_notification_proc(Notification, NotificationDir) -> 30 | supervisor:start_child(?MODULE, [Notification, NotificationDir]). 31 | 32 | init([]) -> 33 | ChildSpec = [ 34 | 35 | {ybot_notification_handler, 36 | {ybot_notification_handler, start_link, []}, 37 | temporary, 2000, worker, [] 38 | } 39 | ], 40 | 41 | % init 42 | {ok,{{simple_one_for_one, 10, 60}, ChildSpec}}. -------------------------------------------------------------------------------- /src/ybot_parser.erl: -------------------------------------------------------------------------------- 1 | %%%----------------------------------------------------------------------------- 2 | %%% @author 0xAX 3 | %%% @doc 4 | %%% Ybot incoming message parser. 5 | %%% @end 6 | %%%----------------------------------------------------------------------------- 7 | -module(ybot_parser). 8 | 9 | -behaviour(gen_server). 10 | 11 | -export([start_link/0]). 12 | 13 | %% gen_server callbacks 14 | -export([init/1, 15 | handle_call/3, 16 | handle_cast/2, 17 | handle_info/2, 18 | terminate/2, 19 | code_change/3]). 20 | 21 | %% Internal state 22 | -record(state, {}). 23 | 24 | 25 | %%%============================================================================= 26 | %%% API 27 | %%%============================================================================= 28 | 29 | start_link() -> 30 | gen_server:start_link(?MODULE, [], []). 31 | 32 | 33 | %%%============================================================================= 34 | %%% ybot_parser callbacks 35 | %%%============================================================================= 36 | 37 | init([]) -> 38 | {ok, #state{}}. 39 | 40 | handle_call(_Request, _From, State) -> 41 | {reply, ignored, State}. 42 | 43 | 44 | %%----------------------------------------------------------------------------- 45 | %% @doc parse incoming message and execute plugin if command is successful 46 | %% @param incoming_message :: atom() 47 | %% @param TransportPid :: pid() 48 | %% @param Nick :: string() 49 | %% @param From :: string() 50 | %% @param IncomingMessage :: string() 51 | %%----------------------------------------------------------------------------- 52 | handle_cast({incoming_message, TransportPid, BotNick, From, Input}, State) -> 53 | [FirstWord | Rest] = string:tokens(Input, " \r\n"), 54 | handle_message(TransportPid, From, BotNick, [parse_nick(FirstWord) | Rest]), 55 | {noreply, State}; 56 | 57 | handle_cast(_Msg, State) -> 58 | {noreply, State}. 59 | 60 | handle_info(_Info, State) -> 61 | {noreply, State}. 62 | 63 | terminate(_Reason, _State) -> 64 | ok. 65 | 66 | code_change(_OldVsn, State, _Extra) -> 67 | {ok, State}. 68 | 69 | 70 | %%%============================================================================= 71 | %%% Internal functions 72 | %%%============================================================================= 73 | 74 | handle_message(TransportPid, From, BotNick, [BotNick]) -> 75 | send_message(TransportPid, From, "What?"); 76 | handle_message(TransportPid, From, BotNick, [BotNick, "name?"]) -> 77 | send_message(TransportPid, From, "My name is: " ++ BotNick); 78 | handle_message(_TransportPid, _From, _BotNick, [_BotNick, "announce" | Args]) -> 79 | ybot_utils:broadcast(string:tokens(Args, "\r\n")); 80 | handle_message(TransportPid, From, _BotNick, [_Nick, "hi"]) -> 81 | send_message(TransportPid, From, "Hello"); 82 | handle_message(TransportPid, From, _BotNick, [_Nick, "hello"]) -> 83 | send_message(TransportPid, From, "Hi!"); 84 | handle_message(TransportPid, From, _BotNick, [_Nick, "bye"]) -> 85 | send_message(TransportPid, From, "Good bye"); 86 | handle_message(TransportPid, From, _BotNick, [_Nick, "thanks"]) -> 87 | send_message(TransportPid, From, "by all means"); 88 | handle_message(TransportPid, From, _BotNick, [_Nick, "history"]) -> 89 | %% Check ybot_history process 90 | case whereis(ybot_history) of 91 | undefined -> 92 | send_message(TransportPid, From, "History is disabled"); 93 | _ -> 94 | %% Get history and send its content 95 | case gen_server:call(ybot_history, {get_history, TransportPid}) of 96 | [] -> send_message(TransportPid, From, "The history is empty"); 97 | History -> send_message(TransportPid, From, History) 98 | end 99 | end; 100 | handle_message(TransportPid, From, _BotNick, [_Nick, "plugins?"]) -> 101 | %% Get plugins and format output 102 | Plugins = gen_server:call(ybot_manager, get_plugins), 103 | PluginNames = string:join( 104 | lists:map(fun({_, _, Pl, _}) -> Pl end, Plugins), ", "), 105 | 106 | %% Send plugins 107 | send_message(TransportPid, From, "Plugins: " ++ PluginNames), 108 | send_message(TransportPid, From, "That's all :)"); 109 | handle_message(TransportPid, From, _BotNick, [_BotNick, Command | Args]) -> 110 | %% Start plugin process and send command 111 | ybot_actor:start_link(TransportPid, From, Command, Args); 112 | handle_message(_TransportPid, _From, _BotNick, _Message) -> 113 | pass. 114 | 115 | send_message(TransportPid, From, Message) -> 116 | gen_server:cast(TransportPid, {send_message, From, Message}). 117 | 118 | %% @doc Strip last permissible symbol from tail of nick 119 | parse_nick(FirstWord) -> 120 | [LastLetter | Rest] = lists:reverse(FirstWord), 121 | case lists:member(LastLetter, [$,, $:, 32]) of 122 | true -> lists:reverse(Rest); 123 | false -> FirstWord 124 | end. 125 | -------------------------------------------------------------------------------- /src/ybot_plugins_observer.erl: -------------------------------------------------------------------------------- 1 | %%%----------------------------------------------------------------------------- 2 | %%% @author 0xAX 3 | %%% @doc 4 | %%% Ybot new plugins observer. 5 | %%% @end 6 | %%%----------------------------------------------------------------------------- 7 | -module(ybot_plugins_observer). 8 | 9 | -behaviour(gen_server). 10 | 11 | -export([start_link/3, stop/0]). 12 | 13 | %% gen_server callbacks 14 | -export([init/1, 15 | handle_call/3, 16 | handle_cast/2, 17 | handle_info/2, 18 | terminate/2, 19 | code_change/3]). 20 | 21 | %% Public api 22 | -export([observe_new_plugins/2]). 23 | 24 | %% Internal state 25 | -record(state, { 26 | % current loaded plugins 27 | current_plugins = [], 28 | % plugins observer timeot 29 | timeout = 0, 30 | % plugins directory 31 | plugins_directory = "" 32 | }). 33 | 34 | 35 | %%============================================================================= 36 | %% API functions 37 | %%============================================================================= 38 | 39 | %% 40 | %% @doc Plugins directory observer 41 | %% PluginsDirectory - Directory with plugins 42 | %% Plugins - current plugins 43 | %% Timeout - observe timeout 44 | %% 45 | start_link(PluginsDirectory, Plugins, Timeout) -> 46 | gen_server:start_link({local, ?MODULE}, ?MODULE, [PluginsDirectory, Plugins, 47 | Timeout], []). 48 | 49 | %% @doc stop observer process 50 | stop() -> 51 | gen_server:cast(ybot_plugins_observer, stop). 52 | 53 | 54 | %%%============================================================================ 55 | %%% Observer callbacks 56 | %%%============================================================================ 57 | 58 | init([PluginsDirectory, Plugins, Timeout]) -> 59 | erlang:send_after(Timeout, self(), check_new_plugins), 60 | 61 | {ok, #state{current_plugins = Plugins, timeout = Timeout, 62 | plugins_directory = PluginsDirectory}}. 63 | 64 | handle_call(get_observer_timeout, _From, State) -> 65 | {reply, State#state.timeout, State}; 66 | 67 | handle_call(_Request, _From, State) -> 68 | {reply, ignored, State}. 69 | 70 | %% @doc update observing timeout 71 | handle_cast({update_timeout, Time}, State) -> 72 | {noreply, State#state{timeout = Time}}; 73 | 74 | handle_cast(stop, State) -> 75 | {stop, normal, State}; 76 | 77 | handle_cast(_Msg, State) -> 78 | {noreply, State}. 79 | 80 | %% @doc check new plugins 81 | handle_info(check_new_plugins, 82 | #state{timeout = Timeout, plugins_directory = PluginsDirectory, 83 | current_plugins = CurrentPlugins} = State) -> 84 | 85 | %% get all plugins 86 | Plugins = lists:append(ybot_utils:get_all_files(PluginsDirectory), 87 | ybot_utils:get_all_directories(PluginsDirectory)), 88 | 89 | % Try to get difference between plugins 90 | case Plugins -- CurrentPlugins of 91 | [] -> 92 | no_new_plugins; 93 | NewPluginsPaths -> 94 | %% Make new plugins 95 | NewPlugins = lists:flatmap( 96 | fun ybot_manager:load_plugin/1, NewPluginsPaths), 97 | 98 | %% update manager's plugins list 99 | gen_server:cast(ybot_manager, {update_plugins, NewPlugins}) 100 | end, 101 | 102 | %% Start new timer 103 | erlang:send_after(Timeout, self(), check_new_plugins), 104 | 105 | {noreply, State#state{current_plugins = CurrentPlugins}}; 106 | 107 | handle_info(_Info, State) -> 108 | {noreply, State}. 109 | 110 | terminate(_Reason, _State) -> 111 | ok. 112 | 113 | code_change(_OldVsn, State, _Extra) -> 114 | {ok, State}. 115 | 116 | 117 | %%%============================================================================ 118 | %%% Internal functions 119 | %%%============================================================================ 120 | 121 | %% @doc check observe new plugins after start or not. 122 | observe_new_plugins(PluginsDirectory, PluginsPaths) -> 123 | %% Check checking_new_plugins if enabled in config 124 | case application:get_env(ybot, checking_new_plugins) of 125 | {ok, true} -> 126 | %% Get new plugins checking timeout 127 | {ok, NewPluginsCheckingTimeout} = application:get_env(ybot, checking_new_plugins_timeout), 128 | 129 | %% Start new plugins observer 130 | ybot_plugins_observer:start_link(PluginsDirectory, PluginsPaths, NewPluginsCheckingTimeout); 131 | _ -> 132 | %% don't use new plugins 133 | pass 134 | end, 135 | ok. 136 | -------------------------------------------------------------------------------- /src/ybot_shell.erl: -------------------------------------------------------------------------------- 1 | %%%----------------------------------------------------------------------------- 2 | %%% @author 0xAX 3 | %%% @doc 4 | %%% Ybot shell. 5 | %%% @end 6 | %%%----------------------------------------------------------------------------- 7 | -module(ybot_shell). 8 | 9 | -behaviour(gen_server). 10 | 11 | -export([start_link/0]). 12 | 13 | %% gen_server callbacks 14 | -export([init/1, 15 | handle_call/3, 16 | handle_cast/2, 17 | handle_info/2, 18 | terminate/2, 19 | code_change/3]). 20 | 21 | %% API 22 | -export([plugins/0, 23 | act/1]). 24 | 25 | %% Internal state 26 | -record(state, { 27 | % ybot command parser 28 | parser_pid :: pid() 29 | }). 30 | 31 | start_link() -> 32 | gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). 33 | 34 | init([]) -> 35 | lager:info("Ybot shell start ~n"), 36 | % start parser 37 | {ok, Pid} = ybot_parser:start_link(), 38 | % init state 39 | {ok, #state{parser_pid = Pid}}. 40 | 41 | handle_call(_Request, _From, State) -> 42 | {reply, ignored, State}. 43 | 44 | %% @doc send command to Ybot 45 | handle_cast({command, Command}, State) -> 46 | % Send command to parser 47 | gen_server:cast(State#state.parser_pid, {incoming_message, self(), "Ybot", "", Command}), 48 | % return 49 | {noreply, State}; 50 | 51 | %% @doc print result 52 | handle_cast({send_message, _From, Result}, State) -> 53 | % print result 54 | lager:info("~p", [Result]), 55 | % return 56 | {noreply, State}; 57 | 58 | handle_cast(_Msg, State) -> 59 | {noreply, State}. 60 | 61 | handle_info(_Info, State) -> 62 | {noreply, State}. 63 | 64 | terminate(_Reason, _State) -> 65 | ok. 66 | 67 | code_change(_OldVsn, State, _Extra) -> 68 | {ok, State}. 69 | 70 | %%============================================================================= 71 | %% Internal functions 72 | %%============================================================================= 73 | 74 | %% @doc Print all plugins 75 | -spec plugins() -> done. 76 | plugins() -> 77 | case whereis(ybot_manager) of 78 | undefined -> 79 | lager:info("ybot_manager not runned"); 80 | _ -> 81 | % Get plugins 82 | Plugins = gen_server:call(ybot_manager, get_plugins), 83 | % Log 84 | lager:info("Plugins:"), 85 | % print plugins 86 | [lager:info("~p ~p", [Source, PluginName]) || {plugin, Source, PluginName, _} <- Plugins] 87 | end, 88 | % return 89 | done. 90 | 91 | %% @doc Ybot execute command 92 | %% Example: 93 | %% ybot:act("Ybot math 1 + 1"). 94 | %% @end 95 | -spec act(Command :: string()) -> done. 96 | act(Command) -> 97 | % Send command to ybot 98 | gen_server:cast(ybot, {command, Command}), 99 | % return 100 | done. -------------------------------------------------------------------------------- /src/ybot_sup.erl: -------------------------------------------------------------------------------- 1 | %%%----------------------------------------------------------------------------- 2 | %%% @author 0xAX 3 | %%% @doc 4 | %%% Ybot root supervisor. 5 | %%% @end 6 | %%%----------------------------------------------------------------------------- 7 | 8 | -module(ybot_sup). 9 | 10 | -behaviour(supervisor). 11 | 12 | %% API 13 | -export([start_link/0]). 14 | 15 | %% Supervisor callbacks 16 | -export([init/1]). 17 | 18 | %% =================================================================== 19 | %% API functions 20 | %% =================================================================== 21 | start_link() -> 22 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 23 | 24 | %% =================================================================== 25 | %% Supervisor callbacks 26 | %% =================================================================== 27 | init([]) -> 28 | % Get plugins directory 29 | {ok, PluginsDirectory} = application:get_env(ybot, plugins_path), 30 | 31 | % Get transports 32 | {ok, Transports} = application:get_env(ybot, transports), 33 | 34 | % Get write-only channels 35 | {ok, Channels} = application:get_env(ybot, channel), 36 | 37 | % Get notifications 38 | {ok, Notifications} = application:get_env(ybot, notification), 39 | 40 | % notification plugins directory 41 | NotificationsDir = PluginsDirectory ++ "notifications/", 42 | 43 | % Root supervisor childrens 44 | Childrens = [ 45 | 46 | % start http supervisor 47 | {http_sup, 48 | {http_sup, start_link, []}, 49 | permanent, brutal_kill, supervisor, [] 50 | }, 51 | 52 | {ybot_web_admin_sup, 53 | {ybot_web_admin_sup, start_link, []}, 54 | permanent, brutal_kill, supervisor, [] 55 | }, 56 | 57 | % start brain http api supervisor 58 | {ybot_brain_sup, 59 | {ybot_brain_sup, start_link, []}, 60 | permanent, brutal_kill, supervisor, [] 61 | }, 62 | 63 | % start flowdock supervisor 64 | {flowdock_sup, 65 | {flowdock_sup, start_link, []}, 66 | permanent, brutal_kill, supervisor, [] 67 | }, 68 | 69 | % run irc root supervisor 70 | {irc_lib_sup, 71 | {irc_lib_sup, start_link, []}, 72 | permanent, brutal_kill, supervisor, [] 73 | }, 74 | 75 | % run xmpp root supervisor 76 | {xmpp_sup, 77 | {xmpp_sup, start_link, []}, 78 | permanent, brutal_kill, supervisor, [] 79 | }, 80 | 81 | % run campfire root supervisor 82 | {campfire_sup, 83 | {campfire_sup, start_link, []}, 84 | permanent, brutal_kill, supervisor, [] 85 | }, 86 | 87 | % run talkerapp root supervisor 88 | {talker_app_sup, 89 | {talker_app_sup, start_link, []}, 90 | permanent, brutal_kill, supervisor, [] 91 | }, 92 | 93 | % start notification handlers supervisor 94 | {ybot_notification_sup, 95 | {ybot_notification_sup, start_link, []}, 96 | permanent, brutal_kill, supervisor, [] 97 | }, 98 | 99 | % start twitter supervisor 100 | {ybot_twitter_sup, 101 | {ybot_twitter_sup, start_link, []}, 102 | permanent, brutal_kill, supervisor, [] 103 | }, 104 | 105 | % start mail supervisor 106 | {ybot_mail_client_sup, 107 | {ybot_mail_client_sup, start_link, []}, 108 | permanent, brutal_kill, supervisor, [] 109 | }, 110 | 111 | % start manager with transports list 112 | {ybot_manager, 113 | {ybot_manager, start_link, [PluginsDirectory, Transports, Channels]}, 114 | permanent, brutal_kill, worker, [] 115 | }, 116 | 117 | % start notification manager 118 | {ybot_notification, 119 | {ybot_notification, start_link, [Notifications, NotificationsDir]}, 120 | permanent, brutal_kill, worker, [] 121 | }, 122 | 123 | % start ybot shell 124 | {ybot_shell, 125 | {ybot_shell, start_link, []}, 126 | permanent, brutal_kill, worker, [] 127 | } 128 | ], 129 | 130 | % init 131 | {ok, { {one_for_one, 1, 60}, Childrens} }. 132 | -------------------------------------------------------------------------------- /src/ybot_utils.erl: -------------------------------------------------------------------------------- 1 | %%%----------------------------------------------------------------------------- 2 | %%% @author 0xAX 3 | %%% @doc 4 | %%% Ybot utils functions. 5 | %%% @end 6 | %%%----------------------------------------------------------------------------- 7 | -module(ybot_utils). 8 | 9 | %% Public API 10 | -export([get_all_files/1, 11 | get_all_directories/1, 12 | get_priv_dir/0, 13 | split_at_end/2, 14 | to_binary/1, 15 | to_atom/1, 16 | to_list/1, 17 | to_int/1, 18 | broadcast/1, 19 | get_config_val/1, 20 | get_config_val/2, 21 | get_val/2 22 | ]). 23 | 24 | %% @doc get all files from directory 25 | -spec get_all_files(Dir :: string()) -> [string()]. 26 | get_all_files(Dir) -> 27 | FindFiles = fun(F, Acc) -> [F | Acc] end, 28 | filelib:fold_files(Dir, ".*", false, FindFiles, []). 29 | 30 | %% @doc get all sub directories from directory 31 | -spec get_all_directories(Pathx :: string()) -> [string()]. 32 | get_all_directories(Path) -> 33 | lists:filter(fun(X) -> filelib:is_dir(X) end, filelib:wildcard(Path ++ "/*")). 34 | 35 | %% @doc Take 2 string. Find SplitSnippet in String 36 | %% and return all string content which after SplitSnippet in string. 37 | %% example: 38 | %% 39 | %% > estring:split_at_end("Hello, my name is 0xAX", "name"). 40 | %% >> " is 0xAX" 41 | %% 42 | -spec split_at_end(String :: string(), SplitSnippet :: string()) -> string(). 43 | split_at_end(String, SplitSnippet) -> 44 | StartPosition = string:str(String, SplitSnippet), 45 | SplitSnippetLength = length(SplitSnippet), 46 | string:substr(String, StartPosition + SplitSnippetLength). 47 | 48 | %% @doc Get priv directory path 49 | -spec get_priv_dir() -> string(). 50 | get_priv_dir() -> 51 | % get current dir 52 | {ok, Cwd} = file:get_cwd(), 53 | % Return priv dir 54 | Cwd ++ "/priv/". 55 | 56 | %% @doc Ensures that is binary 57 | -spec to_binary(any()) -> binary(). 58 | to_binary(X) when is_list(X) -> list_to_binary(X); 59 | to_binary(X) when is_integer(X) -> list_to_binary(integer_to_list(X)); 60 | to_binary(X) when is_binary(X) -> X. 61 | 62 | %% @doc Ensures that is atom 63 | -spec to_atom(any()) -> atom(). 64 | to_atom(X) when is_atom(X) -> X; 65 | to_atom(X) when is_list(X) -> list_to_atom(X); 66 | to_atom(X) when is_binary(X) -> list_to_atom(binary_to_list(X)). 67 | 68 | %% @doc Ensures that is list 69 | -spec to_list(any()) -> list(). 70 | to_list(X) when is_binary(X) -> binary_to_list(X); 71 | to_list(X) when is_integer(X) -> integer_to_list(X); 72 | to_list(X) when is_float(X) -> float_to_list(X); 73 | to_list(X) when is_atom(X) -> atom_to_list(X); 74 | to_list(X) when is_list(X) -> X. 75 | 76 | %% @doc Ensures that this is integer 77 | -spec to_int(any()) -> integer(). 78 | to_int(X) when is_binary(X) -> list_to_integer(binary_to_list(X)); 79 | to_int(X) when is_integer(X) -> X; 80 | to_int(X) when is_float(X) -> list_to_integer(float_to_list(X)); 81 | to_int(X) when is_atom(X) -> list_to_integer(atom_to_list(X)); 82 | to_int(X) when is_list(X) -> list_to_integer(X); 83 | to_int(_) -> 0. 84 | 85 | join([H|T], Sep) -> 86 | lists:flatten([H | [[Sep, X] || X <- T]]). 87 | 88 | %% @doc Send Body to all chats 89 | -spec broadcast(any()) -> ok. 90 | broadcast(Body) -> 91 | % Get all runned transports pid list 92 | Transports = gen_server:call(ybot_manager, get_runnned_transports), 93 | % Send to messages 94 | lists:foreach(fun(TransportPid) -> 95 | % Send message 96 | gen_server:cast(TransportPid, 97 | {send_message, "", join(to_list(Body), "")} 98 | ) 99 | end, 100 | Transports). 101 | 102 | %% get parameter value from config 103 | get_config_val(Param) -> 104 | get_config_val(Param, []). 105 | 106 | get_config_val(Param, DefaultValue) -> 107 | case application:get_env(ybot, Param) of 108 | {_, ParamValue} -> 109 | ParamValue; 110 | _ -> 111 | DefaultValue 112 | end. 113 | 114 | get_val(Param, {Label, DefVal}) -> 115 | case application:get_env(ybot, Param) of 116 | undefined -> 117 | {Label, DefVal}; 118 | {_, ParamVal} -> 119 | {Label, ParamVal} 120 | end. 121 | -------------------------------------------------------------------------------- /src/ybot_validators.erl: -------------------------------------------------------------------------------- 1 | %%%----------------------------------------------------------------------------- 2 | %%% @author 0xAX 3 | %%% @doc 4 | %%% Ybot transports options validator. 5 | %%% @end 6 | %%%----------------------------------------------------------------------------- 7 | -module(ybot_validators). 8 | 9 | %% Public API 10 | -export([validate_transport_opts/1]). 11 | 12 | -type opt() :: integer() | binary() | string() | []. 13 | 14 | %% @doc Validate transport options 15 | -spec validate_transport_opts(Options :: [{atom(), opt()}]) -> ok | wrong_options. 16 | validate_transport_opts([]) -> 17 | wrong_options; 18 | validate_transport_opts(Options) when not is_list(Options) -> 19 | wrong_options; 20 | validate_transport_opts(Options) -> 21 | % Get irc opts 22 | PortOpts = lists:keyfind(port, 1, Options), 23 | % Get ssl opts 24 | SslOpts = lists:keyfind(use_ssl, 1, Options), 25 | % Check options 26 | if (PortOpts == false) or (SslOpts == false) -> 27 | wrong_options; 28 | true -> 29 | % get port 30 | {port, Port} = PortOpts, 31 | % get ssl 32 | {use_ssl, Ssl} = SslOpts, 33 | % Check this parameters 34 | if (not is_integer(Port)) == false or (not is_boolean(Ssl) == false) -> 35 | wrong_options; 36 | true -> 37 | ok 38 | end 39 | end. -------------------------------------------------------------------------------- /ybot.config.template: -------------------------------------------------------------------------------- 1 | %% -*- mode: Erlang; -*- 2 | 3 | %%% 4 | %%% Main Ybot config 5 | %%% 6 | 7 | [ 8 | {ybot, 9 | [ 10 | % list of transport 11 | {transports, [ 12 | % Irc transport 13 | %{irc, 14 | % % Irc nick 15 | % <<"ybott">>, 16 | % % Irc channel list with keys 17 | % [{<<"#erlplay">>, <<>>}, {<<"#help">>, <<>>}], 18 | % % Irc server host / pass 19 | % {<<"irc.freenode.net">>, <<>>}, 20 | % % Options 21 | % [ 22 | % % Port number 23 | % {port,7000}, 24 | % % Use ssl connection or not 25 | % {use_ssl, true}, 26 | % % Reconnect timeout. Put 0 if you no need it. 27 | % {reconnect_timeout, 5000} 28 | % ] 29 | %}, 30 | 31 | % Xmpp transport 32 | % {xmpp, % bot xmpp login 33 | % <<"ybot@jabber.org">>, 34 | % % bot xmpp password 35 | % <<"ybot_password">>, 36 | % % xmpp room 37 | % <<"room@conference.jabber.com">>, 38 | % % nick in this room 39 | % <<"MyYbot">>, 40 | % % xmpp server 41 | % <<"jabber.org">>, 42 | % % xmpp resource 43 | % <<"home">>, 44 | % % xmpp options 45 | % [ 46 | % % XMPP port 47 | % {port, 5222}, 48 | % % Use ssl or not 49 | % {use_ssl, false}, 50 | % % reconnect timeout 51 | % {reconnect_timeout, 5000} 52 | % ] 53 | %}, 54 | 55 | % Hipchat config 56 | % {hipchat, 57 | % % Bot Hipchat Username 58 | % <<"00000_00000@chat.hipchat.com">>, 59 | % % Bot Hipchat password 60 | % <<"password">>, 61 | % % Hipchat room 62 | % <<"ybot_test@conf.hipchat.com">>, 63 | % % Server 64 | % <<"chat.hipchat.com">>, 65 | % % Resource 66 | % <<"bot">>, 67 | % % Hipchat nick 68 | % <<"ybot">>, 69 | % % Options 70 | % [ 71 | % {reconnect_timeout, 0} 72 | % ] 73 | % }, 74 | 75 | % Campfire transport 76 | % {campfire, 77 | % % campfire login 78 | % <<"ybot">>, 79 | % % campfire token 80 | % <<"ybot_token">>, 81 | % % campfire room id 82 | % 100100, 83 | % % campfire sub-domain 84 | % <<"home100">>, 85 | % % Options 86 | % [{reconnect_timeout, 5000}] 87 | % }, 88 | 89 | % flowdock config 90 | % {flowdock, 91 | % % Nick in chat 92 | % <<"ybot">>, 93 | % % Flowdock login 94 | % <<"ybot@gmail.com">>, 95 | % % Flowdock password 96 | % <<"password">>, 97 | % % Flowdock organization 98 | % <<"ybot_org">>, 99 | % % Flow 100 | % <<"ybot_flow">>, 101 | % % Connection options 102 | % [{reconnect_timeout, 5000}] 103 | % }, 104 | 105 | % Http Ybot interface 106 | {http, 107 | % Http server host 108 | <<"localhost">>, 109 | % Http server port 110 | 8080, 111 | % Ybot nick 112 | <<"Ybot">> 113 | } 114 | 115 | % skype 116 | % {skype, 117 | % % use skype or not 118 | % true, 119 | % % Ybot http interface host 120 | % <<"http://localhost">>, 121 | % % Ybot http interface port 122 | % 8080 123 | % }, 124 | 125 | 126 | % talkerapp 127 | %{talkerapp, 128 | % % talkerapp nick 129 | % <<"ybot">>, 130 | % % room 131 | % <<"ybot_test">>, 132 | % % token 133 | % <<"">>, 134 | % % talkerapp options 135 | % [{reconnect_timeout, 5000}] 136 | %} 137 | ] 138 | }, 139 | 140 | % 141 | % Notifications 142 | % 143 | {notification, [ 144 | % % {plugin name :: atom(), [transport list :: atom()], timeout_in_seconds :: integer()} 145 | % {ackbar, [irc, twitter], 60} 146 | % 147 | ]}, 148 | 149 | % 150 | % Ybot channels (write only transports) 151 | % 152 | {channel, 153 | [ 154 | % 155 | % twitter channel 156 | % 157 | % {twitter, 158 | % % Consumer key 159 | % <<"">>, 160 | % % Consumer secret 161 | % <<"">>, 162 | % % Access token 163 | % <<"">>, 164 | % % Access token secret 165 | % <<"">> 166 | % }, 167 | 168 | % 169 | % mail channel 170 | % 171 | % {smtp, 172 | % % ybot mail 173 | % <<"">>, 174 | % % ybot mail password 175 | % <<"">>, 176 | % % send mail to 177 | % [], 178 | % % options 179 | % [{use_ssl, true}, {host, <<"smtp.gmail.com">>}, {port, 465}] 180 | % } 181 | ] 182 | }, 183 | 184 | % 185 | % Ybot web interface 186 | % 187 | {web_admin, 188 | [ 189 | % use web admin or not 190 | {use_web_admin, true}, 191 | % Web interface host 192 | {webadmin_host, <<"localhost">>}, 193 | % Web interface port 194 | {webadmin_port, 8000}, 195 | % Web interface authorization 196 | {webadmin_auth, true}, 197 | {webadmin_auth_user, "foo"}, 198 | {webadmin_auth_passwd, "bar"} 199 | ] 200 | }, 201 | 202 | % Loading new plugins during work or not (not required parameter) 203 | {checking_new_plugins, true}, 204 | % Checking new plugins timeout (not required parameter) 205 | {checking_new_plugins_timeout, 60000}, 206 | 207 | % Save commands history (not required parameter) 208 | {commands_history, false}, 209 | % Command history limit (not required parameter) 210 | {history_command_limit_count, 1000}, 211 | 212 | % Plugins directory path 213 | {plugins_path, "plugins/"}, 214 | 215 | % Default brain storage 216 | {brain_storage, mnesia}, 217 | % Mnesia REST API host 218 | {brain_api_host, <<"localhost">>}, 219 | % Mnesia REST API port 220 | {brain_api_port, 8090} 221 | ] 222 | } 223 | ]. 224 | --------------------------------------------------------------------------------