├── .gitignore ├── LICENSE ├── Makefile ├── README ├── ebin └── eirc.app ├── include └── eirc.hrl └── src ├── eirc.erl ├── eirc_bot_sup.erl ├── eirc_chan.erl ├── eirc_cl.erl ├── eirc_cl_sup.erl ├── eirc_example_bot.erl ├── eirc_lib.erl └── gen_eircbot.erl /.gitignore: -------------------------------------------------------------------------------- 1 | *.beam 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017, Mazen Harake 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 17 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 20 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 21 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 22 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 23 | POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all clean 2 | 3 | BEAMS := $(patsubst src/%, ebin/%, $(patsubst %.erl, %.beam, $(wildcard src/*.erl))) 4 | ECINCLUDES := -I include 5 | ECFLAGS := -pa ebin +debug_info 6 | 7 | all: $(BEAMS) 8 | 9 | ebin/%.beam: src/%.erl 10 | @echo "[ERLC]" $<": "$@ 11 | @erlc -o ebin/ $(ECINCLUDES) $(ECFLAGS) $< 12 | 13 | clean: 14 | rm -f ebin/*.beam 15 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | ================================================================================ 2 | Eirc - An IRC client library for Erlang 3 | ================================================================================ 4 | 5 | Eirc is an IRC client library which is useful if an Erlang application need to 6 | connect to an IRC server e.g. for a kind of notification system or similar. It 7 | aims to implement the full protocol specified in RFC2812 and all relevant parts 8 | in RFC1459. 9 | 10 | == Contact == 11 | For feedback, comments and/or suggestions 12 | 13 | Project: http://www.github.com/mazenharake/eirc 14 | Blog: http://mazenharake.wordpress.com 15 | IRC: #erlang @ freenode 16 | MailingList: http://www.erlang.org/faq.html 17 | 18 | == License == 19 | Please see the LICENSE file 20 | 21 | == Compiling == 22 | Just run make. 23 | 24 | == Example == 25 | To run a client with a bot attached do the following: 26 | 27 | 1) Compile 28 | $ make 29 | 30 | 2) Open a shell E.g. (from the eirc root dir). 31 | $ erl -pa ../eirc/ebin 32 | 33 | 3a) In the Erlang shell type (formatting for clarity): 34 | > application:start(eirc). 35 | ok 36 | 37 | > {ok, Client} = eirc:start_client(mybot, [{bots, [{example_bot, eirc_example_bot, []}]}]). 38 | Initiating bot... 39 | {ok,<0.39.0>} 40 | 41 | 3b) Another way of using using a bot is to install it 42 | > {ok, Client} = eirc:start_client(mybot, []). 43 | {ok,<0.39.0>} 44 | 45 | > eirc:install_bot(mybot, example_bot, eirc_example_bot, []). 46 | Initiating bot... 47 | ok 48 | 49 | 4) Connect the client and interact and what not 50 | > eirc:connect_and_logon(Client, "irc.freenode.net", 6667, "mazenharake"). 51 | Connected to "irc.freenode.net":6667 52 | Logging in as "mazenharake" 53 | ok 54 | NOTICE ("zelazny.freenode.net"): ["*** Looking up your hostname..."] 55 | NOTICE ("zelazny.freenode.net"): ["*** Checking Ident"] 56 | NOTICE ("zelazny.freenode.net"): ["*** Found your hostname"] 57 | NOTICE ("zelazny.freenode.net"): ["*** No Ident response"] 58 | Login successful 59 | RAW: "001"; ["eircbot","Welcome to the freenode Internet Relay Chat Network eircbot"] 60 | ... 61 | MODE: "eircbot" sets "+i" on "eircbot" (parameters: []) 62 | 63 | > eirc:join(Client, "#asecretchannel"). 64 | ok 65 | JOIN: "eircbot" joined "#asecretchannel" 66 | MODE: undefined sets "+ns" on "#asecretchannel" (parameters: [[]]) 67 | RAW: "353"; ["eircbot","@","#asecretchannel","@eircbot"] 68 | RAW: "366"; ["eircbot","#asecretchannel","End of /NAMES list."] 69 | 70 | JOIN: "mazenharake" joined "#asecretchannel" 71 | 72 | TEXT: From ("mazenharake") To ("#asecretchannel") - "Hello Bot!" 73 | 74 | 5) Disconnect 75 | > eirc:quit(Client, "The end of the road..."). 76 | ok 77 | QUIT: "mazenharake" quit ("Client Quit") 78 | RAW: "ERROR"; ["Closing Link: c-0af670d5.012-10-67626721.cust.bredbandsbolaget.se (Client Quit)"] 79 | Connection closed! 80 | 81 | 6) Reconnect or... shutdown the client 82 | > eirc:stop_client(mybot). 83 | Bot terminating shutdown... 84 | ok 85 | -------------------------------------------------------------------------------- /ebin/eirc.app: -------------------------------------------------------------------------------- 1 | {application, eirc, 2 | [{description, "IRC client(s) application"}, 3 | {vsn, "0.0.1"}, 4 | {modules, [eirc]}, 5 | {applications, []}, 6 | {mod, {eirc, []}}, 7 | {env, []}]}. 8 | -------------------------------------------------------------------------------- /include/eirc.hrl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) 2010, Mazen Harake 2 | %% All rights reserved. 3 | %% 4 | %% Redistribution and use in source and binary forms, with or without 5 | %% modification, are permitted provided that the following conditions are met: 6 | %% 7 | %% * Redistributions of source code must retain the above copyright notice, 8 | %% this list of conditions and the following disclaimer. 9 | %% * Redistributions in binary form must reproduce the above copyright 10 | %% notice, this list of conditions and the following disclaimer in the 11 | %% documentation and/or other materials provided with the distribution. 12 | %% 13 | %% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | %% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | %% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | %% ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 17 | %% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 | %% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19 | %% SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 20 | %% INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 21 | %% CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 22 | %% ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 23 | %% POSSIBILITY OF SUCH DAMAGE. 24 | 25 | %% Records 26 | -record(eirc_state, { 27 | event_handlers = [], server, port, socket, nick, pass, user, name, 28 | logged_on = false, autoping, chprefix, network, usrprefix, login_time, 29 | botsup, channels, debug 30 | }). 31 | 32 | -record(ircmsg, { server, nick, user, host, ctcp, cmd, args = [] }). 33 | 34 | %% Helpers 35 | -define(CRLF, "\r\n"). 36 | -define(CMD(Cmd), [Cmd, ?CRLF]). 37 | -define(CTCP(Cmd), [1,Cmd,1]). 38 | 39 | %% IRC Commands 40 | -define(PASS(Pwd), ?CMD(["PASS ",Pwd])). 41 | -define(NICK(Nick), ?CMD(["NICK ",Nick])). 42 | -define(USER(User, Name), ?CMD(["USER ",User," 0 * :",Name])). 43 | -define(PONG1(Nick), ?CMD(["PONG ",Nick])). 44 | -define(PONG2(Nick, To), ?CMD(["PONG ",Nick," ",To])). 45 | -define(PRIVMSG(Nick, Msg), ?CMD(["PRIVMSG ",Nick," :",Msg])). 46 | -define(NOTICE(Nick, Msg), ?CMD(["NOTICE ",Nick," :",Msg])). 47 | -define(JOIN(Chan, Key), ?CMD(["JOIN ",Chan," ",Key])). 48 | -define(PART(Chan), ?CMD(["PART ",Chan])). 49 | -define(QUIT(Msg), ?CMD(["QUIT :",Msg])). 50 | 51 | %% IRC Numeric Codes 52 | -define(RPL_WELCOME, "001"). 53 | -define(RPL_YOURHOST, "002"). 54 | -define(RPL_CREATED, "003"). 55 | -define(RPL_MYINFO, "004"). 56 | %% -define(RPL_BOUNCE, "005"). %% RFC2812 57 | -define(RPL_ISUPPORT, "005"). %% Defacto standard for server support 58 | -define(RPL_BOUNCE, "010"). %% Defacto replacement of "005" in RFC2812 59 | 60 | -define(RPL_STATSDLINE, "250"). 61 | -define(RPL_LUSERCLIENT, "251"). 62 | -define(RPL_LUSEROP, "252"). 63 | -define(RPL_LUSERUNKNOWN, "253"). 64 | -define(RPL_LUSERCHANNELS, "254"). 65 | -define(RPL_LUSERME, "255"). 66 | -define(RPL_LOCALUSERS, "265"). 67 | -define(RPL_GLOBALUSERS, "266"). 68 | 69 | -define(RPL_TOPIC, "332"). 70 | -define(RPL_NAMREPLY, "353"). 71 | -define(RPL_ENDOFNAMES, "366"). 72 | -define(RPL_MOTD, "372"). 73 | -define(RPL_MOTDSTART, "375"). 74 | -define(RPL_ENDOFMOTD, "376"). 75 | 76 | -define(ERR_NONICKNAMEGIVEN, "431"). 77 | -define(ERR_ERRONEUSNICKNAME, "432"). 78 | -define(ERR_NICKNAMEINUSE, "433"). 79 | -define(ERR_NICKCOLLISION, "436"). 80 | -define(ERR_UNAVAILRESOURCE, "437"). 81 | -define(ERR_NEEDMOREPARAMS, "461"). 82 | -define(ERR_ALREADYREGISTRED, "462"). 83 | -define(ERR_RESTRICTED, "484"). 84 | 85 | %% Code groups 86 | -define(LOGON_ERRORS, [?ERR_NONICKNAMEGIVEN, ?ERR_ERRONEUSNICKNAME, 87 | ?ERR_NICKNAMEINUSE, ?ERR_NICKCOLLISION, 88 | ?ERR_UNAVAILRESOURCE, ?ERR_NEEDMOREPARAMS, 89 | ?ERR_ALREADYREGISTRED, ?ERR_RESTRICTED]). 90 | -------------------------------------------------------------------------------- /src/eirc.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) 2010, Mazen Harake 2 | %% All rights reserved. 3 | %% 4 | %% Redistribution and use in source and binary forms, with or without 5 | %% modification, are permitted provided that the following conditions are met: 6 | %% 7 | %% * Redistributions of source code must retain the above copyright notice, 8 | %% this list of conditions and the following disclaimer. 9 | %% * Redistributions in binary form must reproduce the above copyright 10 | %% notice, this list of conditions and the following disclaimer in the 11 | %% documentation and/or other materials provided with the distribution. 12 | %% 13 | %% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | %% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | %% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | %% ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 17 | %% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 | %% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19 | %% SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 20 | %% INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 21 | %% CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 22 | %% ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 23 | %% POSSIBILITY OF SUCH DAMAGE. 24 | 25 | -module(eirc). 26 | -include("eirc.hrl"). 27 | 28 | %% Application API 29 | %% -export([connect/4, connect_link/4, disconnect/2]). 30 | -compile(export_all). 31 | 32 | %% ============================================================================= 33 | %% Application API 34 | %% ============================================================================= 35 | start(_, _) -> 36 | eirc_cl_sup:start_link(). 37 | 38 | stop(_) -> 39 | ok. 40 | 41 | %% ============================================================================= 42 | %% Client API 43 | %% ============================================================================= 44 | start_client(ClientId) -> 45 | start_client(ClientId, []). 46 | 47 | start_client(ClientId, Options) -> 48 | eirc_cl_sup:start_client(ClientId, Options). 49 | 50 | install_bot(ClientId, BotId, CBMod, Args) -> 51 | case eirc_cl_sup:get_client(ClientId) of 52 | undefined -> 53 | {error, no_client}; 54 | ClPid -> 55 | eirc_cl:install_bot(ClPid, BotId, CBMod, Args) 56 | end. 57 | 58 | uninstall_bot(ClientId, BotId) -> 59 | case eirc_cl_sup:get_client(ClientId) of 60 | undefined -> 61 | {error, no_client}; 62 | ClPid -> 63 | eirc_cl:uninstall_bot(ClPid, BotId) 64 | end. 65 | 66 | stop_client(ClientId) -> 67 | eirc_cl_sup:stop_client(ClientId). 68 | 69 | get_client(ClientId) -> 70 | eirc_cl_sup:get_client(ClientId). 71 | 72 | connect_and_logon(Client, Server, Port, Nick) -> 73 | connect_and_logon(Client, Server, Port, "nopass", Nick, Nick, "No Name"). 74 | 75 | connect_and_logon(Client, Server, Port, Pass, Nick, User, Name) -> 76 | case connect(Client, Server, Port) of 77 | ok -> logon(Client, Pass, Nick, User, Name); 78 | Error -> Error 79 | end. 80 | 81 | connect(Client, Server, Port) -> 82 | eirc_cl:connect(Client, Server, Port). 83 | 84 | logon(Client, Nick) -> 85 | logon(Client, "nopass", Nick, Nick, "No Name"). 86 | 87 | logon(Client, Pass, Nick, User, Name) -> 88 | eirc_cl:logon(Client, Pass, Nick, User, Name). 89 | 90 | privmsg(Client, Nick, Msg) -> 91 | eirc_cl:msg(Client, privmsg, Nick, Msg). 92 | 93 | notice(Client, Nick, Msg) -> 94 | eirc_cl:msg(Client, notice, Nick, Msg). 95 | 96 | ctcp(Client, Nick, Msg) -> 97 | eirc_cl:msg(Client, ctcp, Nick, Msg). 98 | 99 | nick(Client, NewNick) -> 100 | eirc_cl:nick(Client, NewNick). 101 | 102 | cmd(Client, RawCmd) -> 103 | eirc_cl:cmd(Client, RawCmd). 104 | 105 | join(Client, Channel) -> 106 | join(Client, Channel, ""). 107 | 108 | join(Client, Channel, Key) -> 109 | eirc_cl:join(Client, Channel, Key). 110 | 111 | part(Client, Channel) -> 112 | eirc_cl:part(Client, Channel). 113 | 114 | quit(Client, QuitMsg) -> 115 | eirc_cl:quit(Client, QuitMsg). 116 | 117 | is_logged_on(Client) -> 118 | eirc_cl:is_logged_on(Client). 119 | 120 | channels(Client) -> 121 | eirc_cl:channels(Client). 122 | 123 | chan_users(Client, Channel) -> 124 | eirc_cl:chan_users(Client, Channel). 125 | 126 | chan_topic(Client, Channel) -> 127 | eirc_cl:chan_topic(Client, Channel). 128 | 129 | chan_type(Client, Channel) -> 130 | eirc_cl:chan_type(Client, Channel). 131 | 132 | chan_has_user(Client, Channel, Nick) -> 133 | eirc_cl:chan_has_user(Client, Channel, Nick). 134 | 135 | add_handler(Client, Pid) when is_pid(Pid) -> 136 | eirc_cl:add_handler(Client, Pid). 137 | 138 | remove_handler(Client, Pid) when is_pid(Pid) -> 139 | eirc_cl:remove_handler(Client, Pid). 140 | 141 | state(Client) -> 142 | eirc_cl:state(Client). 143 | 144 | %% ============================================================================= 145 | %% Internal Functions 146 | %% ============================================================================= 147 | -------------------------------------------------------------------------------- /src/eirc_bot_sup.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) 2010, Mazen Harake 2 | %% All rights reserved. 3 | %% 4 | %% Redistribution and use in source and binary forms, with or without 5 | %% modification, are permitted provided that the following conditions are met: 6 | %% 7 | %% * Redistributions of source code must retain the above copyright notice, 8 | %% this list of conditions and the following disclaimer. 9 | %% * Redistributions in binary form must reproduce the above copyright 10 | %% notice, this list of conditions and the following disclaimer in the 11 | %% documentation and/or other materials provided with the distribution. 12 | %% 13 | %% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | %% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | %% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | %% ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 17 | %% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 | %% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19 | %% SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 20 | %% INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 21 | %% CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 22 | %% ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 23 | %% POSSIBILITY OF SUCH DAMAGE. 24 | 25 | -module(eirc_bot_sup). 26 | 27 | %% Supervisor API 28 | -compile(export_all). 29 | 30 | %% Module Interface 31 | 32 | %% ============================================================================= 33 | %% Supervisor API 34 | %% ============================================================================= 35 | start_link() -> 36 | supervisor:start_link(?MODULE, []). 37 | 38 | init(_) -> 39 | {ok, {{one_for_one, 10, 10}, []}}. 40 | 41 | %% ============================================================================= 42 | %% Module Interface 43 | %% ============================================================================= 44 | start_bot(SupPid, BotId, CBMod, Args) -> 45 | Child = {BotId, {gen_eircbot, start_link, [self(), CBMod, Args]}, 46 | transient, 6000, worker, [gen_eircbot]}, 47 | supervisor:start_child(SupPid, Child). 48 | 49 | stop_bot(SupPid, BotId) -> 50 | supervisor:terminate_child(SupPid, BotId), 51 | supervisor:delete_child(SupPid, BotId). 52 | 53 | get_bot(SupPid, BotId) -> 54 | case lists:keyfind(BotId, 1, supervisor:which_children(SupPid)) of 55 | {BotId, Pid, _, _} -> Pid; 56 | false -> undefined 57 | end. 58 | -------------------------------------------------------------------------------- /src/eirc_chan.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) 2010, Mazen Harake 2 | %% All rights reserved. 3 | %% 4 | %% Redistribution and use in source and binary forms, with or without 5 | %% modification, are permitted provided that the following conditions are met: 6 | %% 7 | %% * Redistributions of source code must retain the above copyright notice, 8 | %% this list of conditions and the following disclaimer. 9 | %% * Redistributions in binary form must reproduce the above copyright 10 | %% notice, this list of conditions and the following disclaimer in the 11 | %% documentation and/or other materials provided with the distribution. 12 | %% 13 | %% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | %% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | %% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | %% ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 17 | %% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 | %% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19 | %% SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 20 | %% INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 21 | %% CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 22 | %% ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 23 | %% POSSIBILITY OF SUCH DAMAGE. 24 | 25 | -module(eirc_chan). 26 | -include("eirc.hrl"). 27 | 28 | -record(chan, { name, topic = "", users = [], modes = "", type }). 29 | 30 | -compile(export_all). 31 | 32 | %% ============================================================================= 33 | %% Channel manipulation 34 | %% ============================================================================= 35 | init() -> 36 | gb_trees:empty(). 37 | 38 | %% ============================================================================= 39 | %% Self JOIN/PART 40 | %% ============================================================================= 41 | join(Struct, ChanName) -> 42 | Name = chan2lower(ChanName), 43 | case gb_trees:lookup(Name, Struct) of 44 | {value, _} -> 45 | Struct; 46 | none -> 47 | gb_trees:insert(Name, #chan{ name = Name }, Struct) 48 | end. 49 | 50 | part(Struct, ChanName) -> 51 | Name = chan2lower(ChanName), 52 | gb_trees:delete(Name, Struct). 53 | 54 | %% ============================================================================= 55 | %% Channel Modes/Attributes 56 | %% ============================================================================= 57 | set_topic(Struct, ChanName, Topic) -> 58 | Name = chan2lower(ChanName), 59 | Channel = gb_trees:get(Name, Struct), 60 | gb_trees:enter(Name, Channel#chan{ topic = Topic }, Struct). 61 | 62 | set_type(Struct, ChanName, ChanType) -> 63 | Name = chan2lower(ChanName), 64 | Channel = gb_trees:get(Name, Struct), 65 | Type = case ChanType of 66 | "@" -> secret; "*" -> private; "=" -> public 67 | end, 68 | gb_trees:enter(Name, Channel#chan{ type = Type }, Struct). 69 | 70 | %% ============================================================================= 71 | %% Users JOIN/PART/AKAs(namechange) 72 | %% ============================================================================= 73 | user_join(Struct, ChanName, Nick) -> 74 | users_join(Struct, ChanName,[Nick]). 75 | 76 | users_join(Struct, ChanName, Nicks) -> 77 | PNicks = strip_rank(Nicks), 78 | ManipFun = fun(ChanNicks) -> lists:usort(ChanNicks ++ PNicks) end, 79 | users_manip(Struct, ChanName, ManipFun). 80 | 81 | user_part(Struct, ChanName, Nick) -> 82 | PNick = strip_rank([Nick]), 83 | ManipFun = fun(ChanNicks) -> lists:usort(ChanNicks -- PNick) end, 84 | users_manip(Struct, ChanName, ManipFun). 85 | 86 | user_rename(Struct, Nick, NewNick) -> 87 | ManipFun = fun(ChanNicks) -> 88 | case lists:member(Nick, ChanNicks) of 89 | true -> lists:usort([NewNick|ChanNicks--[Nick]]); 90 | false -> ChanNicks 91 | end 92 | end, 93 | FoldlFun = fun(ChanName, NewStruct) -> 94 | Name = chan2lower(ChanName), 95 | users_manip(NewStruct, Name, ManipFun) 96 | end, 97 | lists:foldl(FoldlFun, Struct, channels(Struct)). 98 | 99 | users_manip(Struct, ChanName, Fun) -> 100 | Name = chan2lower(ChanName), 101 | Channel = gb_trees:get(Name, Struct), 102 | Chanlist = Fun(Channel#chan.users), 103 | gb_trees:enter(ChanName, Channel#chan{ users = Chanlist }, Struct). 104 | 105 | %% ============================================================================= 106 | %% Introspection 107 | %% ============================================================================= 108 | channels(Struct) -> 109 | [ ChanName || {ChanName, _Chan} <- gb_trees:to_list(Struct) ]. 110 | 111 | chan_users(Struct, ChanName) -> 112 | get_attr(Struct, ChanName, fun(#chan{ users = Users }) -> Users end). 113 | 114 | chan_topic(Struct, ChanName) -> 115 | get_attr(Struct, ChanName, fun(#chan{ topic = Topic }) -> Topic end). 116 | 117 | chan_type(Struct, ChanName) -> 118 | get_attr(Struct, ChanName, fun(#chan{ type = Type }) -> Type end). 119 | 120 | chan_has_user(Struct, ChanName, Nick) -> 121 | get_attr(Struct, ChanName, fun(#chan{ users = Users }) -> 122 | lists:member(Nick, Users) 123 | end). 124 | 125 | to_proplist(Struct) -> 126 | [ {ChanName, [{users, Chan#chan.users}, {topic, Chan#chan.topic}, {type, Chan#chan.type}]} 127 | || {ChanName, Chan} <- gb_trees:to_list(Struct) ]. 128 | 129 | %% ============================================================================= 130 | %% Internal functions 131 | %% ============================================================================= 132 | chan2lower(ChanName) -> string:to_lower(ChanName). 133 | 134 | strip_rank(Nicks) -> 135 | lists:map(fun([$@|Nick]) -> Nick; 136 | ([$+|Nick]) -> Nick; 137 | (Nick) -> Nick end, Nicks). 138 | 139 | get_attr(Struct, ChanName, Fun) -> 140 | Name = chan2lower(ChanName), 141 | case gb_trees:lookup(Name, Struct) of 142 | {value, Channel} -> Fun(Channel); 143 | none -> {error, no_such_channel} 144 | end. 145 | 146 | -------------------------------------------------------------------------------- /src/eirc_cl.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) 2010, Mazen Harake 2 | %% All rights reserved. 3 | %% 4 | %% Redistribution and use in source and binary forms, with or without 5 | %% modification, are permitted provided that the following conditions are met: 6 | %% 7 | %% * Redistributions of source code must retain the above copyright notice, 8 | %% this list of conditions and the following disclaimer. 9 | %% * Redistributions in binary form must reproduce the above copyright 10 | %% notice, this list of conditions and the following disclaimer in the 11 | %% documentation and/or other materials provided with the distribution. 12 | %% 13 | %% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | %% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | %% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | %% ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 17 | %% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 | %% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19 | %% SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 20 | %% INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 21 | %% CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 22 | %% ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 23 | %% POSSIBILITY OF SUCH DAMAGE. 24 | 25 | -module(eirc_cl). 26 | -include("eirc.hrl"). 27 | 28 | -compile(export_all). 29 | 30 | %% ============================================================================= 31 | %% Module API 32 | %% ============================================================================= 33 | start(Options) -> 34 | gen_server:start(?MODULE, Options, []). 35 | 36 | start_link(Options) -> 37 | gen_server:start_link(?MODULE, Options, []). 38 | 39 | install_bot(Client, BotId, CBMod, Args) -> 40 | gen_server:call(Client, {install_bot, BotId, CBMod, Args}, infinity). 41 | 42 | uninstall_bot(Client, BotId) -> 43 | gen_server:call(Client, {uninstall_bot, BotId}, infinity). 44 | 45 | stop(Client) -> 46 | gen_server:call(Client, stop). 47 | 48 | connect(Client, Server, Port) -> 49 | gen_server:call(Client, {connect, Server, Port}, infinity). 50 | 51 | logon(Client, Pass, Nick, User, Name) -> 52 | gen_server:call(Client, {logon, Pass, Nick, User, Name}, infinity). 53 | 54 | msg(Client, Type, Nick, Msg) -> 55 | gen_server:call(Client, {msg, Type, Nick, Msg}, infinity). 56 | 57 | nick(Client, NewNick) -> 58 | gen_server:call(Client, {nick, NewNick}, infinity). 59 | 60 | cmd(Client, RawCmd) -> 61 | gen_server:call(Client, {cmd, RawCmd}). 62 | 63 | join(Client, Channel, Key) -> 64 | gen_server:call(Client, {join, Channel, Key}, infinity). 65 | 66 | part(Client, Channel) -> 67 | gen_server:call(Client, {part, Channel}, infinity). 68 | 69 | quit(Client, QuitMsg) -> 70 | gen_server:call(Client, {quit, QuitMsg}, infinity). 71 | 72 | is_logged_on(Client) -> 73 | gen_server:call(Client, is_logged_on). 74 | 75 | channels(Client) -> 76 | gen_server:call(Client, channels). 77 | 78 | chan_users(Client, Channel) -> 79 | gen_server:call(Client, {chan_users, Channel}). 80 | 81 | chan_topic(Client, Channel) -> 82 | gen_server:call(Client, {chan_topic, Channel}). 83 | 84 | chan_type(Client, Channel) -> 85 | gen_server:call(Client, {chan_type, Channel}). 86 | 87 | chan_has_user(Client, Channel, Nick) -> 88 | gen_server:call(Client, {chan_has_user, Channel, Nick}). 89 | 90 | add_handler(Client, Pid) -> 91 | gen_server:call(Client, {add_handler, Pid}). 92 | 93 | remove_handler(Client, Pid) -> 94 | gen_server:call(Client, {remove_handler, Pid}). 95 | 96 | asynch_add_handler(Client, Pid) -> 97 | gen_server:cast(Client, {add_handler, Pid}). 98 | 99 | asynch_remove_handler(Client, Pid) -> 100 | gen_server:cast(Client, {remove_handler, Pid}). 101 | 102 | state(Client) -> 103 | State = gen_server:call(Client, state), 104 | ActiveBots = lists:map(fun({N,P,_,_}) -> {N, P, gen_eircbot:state(P)} end, 105 | supervisor:which_children(State#eirc_state.botsup)), 106 | [{server, State#eirc_state.server}, 107 | {port, State#eirc_state.port}, 108 | {nick, State#eirc_state.nick}, 109 | {pass, State#eirc_state.pass}, 110 | {user, State#eirc_state.user}, 111 | {name, State#eirc_state.name}, 112 | {autoping, State#eirc_state.autoping}, 113 | {chprefix, State#eirc_state.chprefix}, 114 | {channels, eirc_chan:to_proplist(State#eirc_state.channels)}, 115 | {network, State#eirc_state.network}, 116 | {login_time, State#eirc_state.login_time}, 117 | {debug, State#eirc_state.debug}, 118 | {event_handlers, State#eirc_state.event_handlers}, 119 | {botsup, State#eirc_state.botsup}, 120 | {bots, ActiveBots}]. 121 | 122 | 123 | %% ============================================================================= 124 | %% Behaviour callback API 125 | %% ============================================================================= 126 | init(Options) -> 127 | Autoping = proplists:get_value(autoping, Options, true), 128 | Debug = proplists:get_value(debug, Options, false), 129 | Handlers = proplists:get_value(event_handlers, Options, []), 130 | Bots = proplists:get_value(bots, Options, []), 131 | {ok, SupPid} = eirc_bot_sup:start_link(), 132 | lists:foreach(fun(BotSpec) -> start_bot(SupPid, BotSpec) end, Bots), 133 | NHandlers = lists:foldl(fun do_add_handler/2, [], Handlers), 134 | {ok, #eirc_state{ event_handlers = NHandlers, autoping = Autoping, 135 | logged_on = false, debug = Debug, 136 | channels = eirc_chan:init(), botsup = SupPid }}. 137 | 138 | handle_call({install_bot, BotId, CBMod, Args}, _From, State) -> 139 | start_bot(State#eirc_state.botsup, {BotId, CBMod, Args}), 140 | {reply, ok, State}; 141 | 142 | handle_call({uninstall_bot, BotId}, _From, State) -> 143 | stop_bot(State#eirc_state.botsup, BotId), 144 | {reply, ok, State}; 145 | 146 | handle_call({add_handler, Pid}, _From, State) -> 147 | NHandlers = do_add_handler(Pid, State#eirc_state.event_handlers), 148 | {reply, ok, State#eirc_state{ event_handlers = NHandlers }}; 149 | 150 | handle_call({remove_handler, Pid}, _From, State) -> 151 | NHandlers = do_remove_handler(Pid, State#eirc_state.event_handlers), 152 | {reply, ok, State#eirc_state{ event_handlers = NHandlers }}; 153 | 154 | handle_call(state, _From, State) -> 155 | {reply, State, State}; 156 | 157 | handle_call(stop, _From, State) -> 158 | {stop, normal, ok, State}; 159 | 160 | handle_call({connect, Server, Port}, _From, State) -> 161 | case gen_tcp:connect(Server, Port, [list, {packet, line}]) of 162 | {ok, Socket} -> 163 | send_event({connect, Server, Port}, State), 164 | {reply, ok, State#eirc_state{ server = Server, port = Port, 165 | socket = Socket } }; 166 | Error -> 167 | {reply, Error, State} 168 | end; 169 | 170 | handle_call({logon, Pass, Nick, User, Name}, _From, #eirc_state{ logged_on = false } = State) -> 171 | gen_tcp:send(State#eirc_state.socket, ?PASS(Pass)), 172 | gen_tcp:send(State#eirc_state.socket, ?NICK(Nick)), 173 | gen_tcp:send(State#eirc_state.socket, ?USER(User, Name)), 174 | send_event({logon, Pass, Nick, User, Name}, State), 175 | {reply, ok, State#eirc_state{ pass = Pass, nick = Nick, user = User, name = Name }}; 176 | 177 | handle_call(is_logged_on, _From, State) -> 178 | {reply, State#eirc_state.logged_on, State}; 179 | 180 | handle_call(_, _From, #eirc_state{ logged_on = false } = State) -> 181 | {reply, {error, not_connected}, State}; 182 | 183 | handle_call({quit, QuitMsg}, _From, State) -> 184 | gen_tcp:send(State#eirc_state.socket, ?QUIT(QuitMsg)), 185 | {reply, ok, State}; 186 | 187 | handle_call({msg, Type, Nick, Msg}, _From, State) -> 188 | Data = case Type of 189 | privmsg -> ?PRIVMSG(Nick, Msg); 190 | notice -> ?NOTICE(Nick, Msg); 191 | ctcp -> ?NOTICE(Nick, ?CTCP(Msg)) 192 | end, 193 | gen_tcp:send(State#eirc_state.socket, Data), 194 | {reply, ok, State}; 195 | 196 | handle_call({join, Channel, Key}, _From, State) -> 197 | gen_tcp:send(State#eirc_state.socket, ?JOIN(Channel, Key)), 198 | {reply, ok, State}; 199 | 200 | handle_call({part, Channel}, _From, State) -> 201 | gen_tcp:send(State#eirc_state.socket, ?PART(Channel)), 202 | {reply, ok, State}; 203 | 204 | handle_call({nick, NewNick}, _From, State) -> 205 | gen_tcp:send(State#eirc_state.socket, ?NICK(NewNick)), 206 | {reply, ok, State}; 207 | 208 | handle_call({cmd, RawCmd}, _From, State) -> 209 | gen_tcp:send(State#eirc_state.socket, ?CMD(RawCmd)), 210 | {reply, ok, State}; 211 | 212 | handle_call(channels, _From, State) -> 213 | {reply, eirc_chan:channels(State#eirc_state.channels), State}; 214 | 215 | handle_call({chan_users, Channel}, _From, State) -> 216 | {reply, eirc_chan:chan_users(State#eirc_state.channels, Channel), State}; 217 | 218 | handle_call({chan_topic, Channel}, _From, State) -> 219 | {reply, eirc_chan:chan_topic(State#eirc_state.channels, Channel), State}; 220 | 221 | handle_call({chan_type, Channel}, _From, State) -> 222 | {reply, eirc_chan:chan_type(State#eirc_state.channels, Channel), State}; 223 | 224 | handle_call({chan_has_user, Channel, Nick}, _From, State) -> 225 | {reply, eirc_chan:chan_has_user(State#eirc_state.channels, Channel, Nick), State}. 226 | 227 | handle_cast({add_handler, Pid}, State) -> 228 | NHandlers = do_add_handler(Pid, State#eirc_state.event_handlers), 229 | {noreply, State#eirc_state{ event_handlers = NHandlers }}; 230 | 231 | handle_cast({remove_handler, Pid}, State) -> 232 | NHandlers = do_remove_handler(Pid, State#eirc_state.event_handlers), 233 | {noreply, State#eirc_state{ event_handlers = NHandlers }}. 234 | 235 | handle_info({tcp_closed, _Socket}, State) -> 236 | io:format("Connection closed!~n"), 237 | {noreply, #eirc_state{ autoping = State#eirc_state.autoping, 238 | debug = State#eirc_state.debug, 239 | event_handlers = State#eirc_state.event_handlers, 240 | botsup = State#eirc_state.botsup, 241 | channels = eirc_chan:init() }}; 242 | 243 | handle_info({tcp_error, Socket}, State) -> 244 | {stop, {tcp_error, Socket}, State}; 245 | 246 | handle_info({tcp, _, Data}, State) -> 247 | case eirc_lib:parse(Data) of 248 | #ircmsg{ ctcp = true } = Msg -> 249 | send_event(Msg, State), 250 | {noreply, State}; 251 | #ircmsg{ ctcp = false } = Msg -> 252 | send_event(Msg, State), 253 | handle_data(Msg, State); 254 | #ircmsg{ ctcp = invalid } = Msg when State#eirc_state.debug == true -> 255 | send_event(Msg, State), 256 | {noreply, State}; 257 | _ -> 258 | {noreply, State} 259 | end; 260 | 261 | handle_info({'DOWN', _, _, Pid, _}, State) -> 262 | NHandlers = do_remove_handler(Pid, State#eirc_state.event_handlers), 263 | {noreply, State#eirc_state{ event_handlers = NHandlers }}; 264 | handle_info(_, State) -> 265 | {noreply, State}. 266 | 267 | %% TERMINATE 268 | terminate(_Reason, _State) -> 269 | ok. 270 | 271 | %% CODE CHANGE 272 | code_change(_Old, State, _Extra) -> 273 | {ok, State}. 274 | 275 | %% ============================================================================= 276 | %% Data handling logic 277 | %% ============================================================================= 278 | %% Sucessfully logged in 279 | handle_data(#ircmsg{ cmd = ?RPL_WELCOME }, 280 | #eirc_state{ logged_on = false } = State) -> 281 | {noreply, State#eirc_state{ logged_on = true, 282 | login_time = erlang:now() }}; 283 | 284 | %% Server capabilities 285 | handle_data(#ircmsg{ cmd = ?RPL_ISUPPORT } = Msg, State) -> 286 | {noreply, eirc_lib:isup(Msg#ircmsg.args, State)}; 287 | 288 | %% We entered a channel 289 | handle_data(#ircmsg{ nick = Nick, cmd = "JOIN" } = Msg, 290 | #eirc_state{ nick = Nick } = State) -> 291 | Channels = eirc_chan:join(State#eirc_state.channels, hd(Msg#ircmsg.args)), 292 | {noreply, State#eirc_state{ channels = Channels }}; 293 | 294 | %% Someone joined a channel we are in 295 | handle_data(#ircmsg{ nick = UserNick, cmd = "JOIN"} = Msg, State) -> 296 | Channels = eirc_chan:user_join(State#eirc_state.channels, hd(Msg#ircmsg.args), 297 | UserNick), 298 | {noreply, State#eirc_state{ channels = Channels }}; 299 | 300 | %% Topic message on join 301 | %% 3 arguments is not RFC compliant but _very_ common 302 | %% 2 arguments is RFC compliant 303 | handle_data(#ircmsg{ cmd = ?RPL_TOPIC } = Msg, State) -> 304 | case Msg#ircmsg.args of 305 | [_Nick, Channel, Topic] -> ok; 306 | [Channel, Topic] -> ok 307 | end, 308 | Channels = eirc_chan:set_topic(State#eirc_state.channels, Channel, Topic), 309 | {noreply, State#eirc_state{ channels = Channels }}; 310 | 311 | %% Topic message while in channel 312 | handle_data(#ircmsg{ cmd = "TOPIC", args = [Channel, Topic] }, State) -> 313 | Channels = eirc_chan:set_topic(State#eirc_state.channels, Channel, Topic), 314 | {noreply, State#eirc_state{ channels = Channels }}; 315 | 316 | %% NAMES reply 317 | handle_data(#ircmsg{ cmd = ?RPL_NAMREPLY } = Msg, State) -> 318 | case Msg#ircmsg.args of 319 | [_Nick, ChanType, Channel, Names] -> ok; 320 | [ChanType, Channel, Names] -> ok 321 | end, 322 | Channels = eirc_chan:set_type( 323 | eirc_chan:users_join(State#eirc_state.channels, 324 | Channel, string:tokens(Names, " ")), 325 | Channel, ChanType), 326 | {noreply, State#eirc_state{ channels = Channels }}; 327 | 328 | %% We successfully changed name 329 | handle_data(#ircmsg{ cmd = "NICK", nick = Nick, args = [NewNick] }, 330 | #eirc_state{ nick = Nick } = State) -> 331 | {noreply, State#eirc_state{ nick = NewNick }}; 332 | 333 | %% Someone we know (or can see) changed name 334 | handle_data(#ircmsg{ cmd = "NICK", nick = Nick, args = [NewNick] }, State) -> 335 | Channels = eirc_chan:user_rename(State#eirc_state.channels, Nick, NewNick), 336 | {noreply, State#eirc_state{ channels = Channels }}; 337 | 338 | %% We left a channel 339 | handle_data(#ircmsg{ nick = Nick, cmd = "PART" } = Msg, 340 | #eirc_state{ nick = Nick } = State) -> 341 | Channels = eirc_chan:part(State#eirc_state.channels, hd(Msg#ircmsg.args)), 342 | {noreply, State#eirc_state{ channels = Channels }}; 343 | 344 | %% Someone left a channel we are in 345 | handle_data(#ircmsg{ nick = UserNick, cmd = "PART" } = Msg, State) -> 346 | Channels = eirc_chan:user_part(State#eirc_state.channels, hd(Msg#ircmsg.args), 347 | UserNick), 348 | {noreply, State#eirc_state{ channels = Channels }}; 349 | 350 | %% We got a ping, reply if autoping is on. 351 | handle_data(#ircmsg{ cmd = "PING" } = Msg, #eirc_state{ autoping = true } = State) -> 352 | case Msg of 353 | #ircmsg{ args = [From] } -> 354 | gen_tcp:send(State#eirc_state.socket, ?PONG2(State#eirc_state.nick, From)); 355 | _ -> 356 | gen_tcp:send(State#eirc_state.socket, ?PONG1(State#eirc_state.nick)) 357 | end, 358 | {noreply, State}; 359 | 360 | %% "catch-all", period. (DEV) 361 | handle_data(_Msg, State) -> 362 | {noreply, State}. 363 | 364 | %% ============================================================================= 365 | %% Internal functions 366 | %% ============================================================================= 367 | send_event(Msg, #eirc_state{ event_handlers = Handlers }) 368 | when is_list(Handlers) -> 369 | lists:foreach(fun({Pid, _}) -> Pid ! Msg end, Handlers). 370 | 371 | gv(Key, Options) -> proplists:get_value(Key, Options). 372 | gv(Key, Options, Default) -> proplists:get_value(Key, Options, Default). 373 | 374 | start_bot(SupPid, {BotId, CBMod, Args}) -> 375 | {ok, Pid} = eirc_bot_sup:start_bot(SupPid, BotId, CBMod, Args), 376 | Pid. 377 | 378 | stop_bot(SupPid, BotId) -> 379 | eirc_bot_sup:stop_bot(SupPid, BotId). 380 | 381 | do_add_handler(Pid, Handlers) -> 382 | case erlang:is_process_alive(Pid) andalso not lists:member(Pid, Handlers) of 383 | true -> 384 | Ref = erlang:monitor(process, Pid), 385 | [{Pid, Ref}|Handlers]; 386 | false -> 387 | Handlers 388 | end. 389 | 390 | do_remove_handler(Pid, Handlers) -> 391 | case lists:keyfind(Pid, 1, Handlers) of 392 | {Pid, Ref} -> 393 | erlang:demonitor(Ref), 394 | lists:keydelete(Pid, 1, Handlers); 395 | false -> 396 | Handlers 397 | end. 398 | 399 | -------------------------------------------------------------------------------- /src/eirc_cl_sup.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) 2010, Mazen Harake 2 | %% All rights reserved. 3 | %% 4 | %% Redistribution and use in source and binary forms, with or without 5 | %% modification, are permitted provided that the following conditions are met: 6 | %% 7 | %% * Redistributions of source code must retain the above copyright notice, 8 | %% this list of conditions and the following disclaimer. 9 | %% * Redistributions in binary form must reproduce the above copyright 10 | %% notice, this list of conditions and the following disclaimer in the 11 | %% documentation and/or other materials provided with the distribution. 12 | %% 13 | %% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | %% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | %% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | %% ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 17 | %% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 | %% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19 | %% SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 20 | %% INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 21 | %% CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 22 | %% ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 23 | %% POSSIBILITY OF SUCH DAMAGE. 24 | 25 | -module(eirc_cl_sup). 26 | 27 | %% Supervisor API 28 | -compile(export_all). 29 | 30 | %% Module Interface 31 | 32 | %% ============================================================================= 33 | %% Supervisor API 34 | %% ============================================================================= 35 | start_link() -> 36 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 37 | 38 | init(_) -> 39 | Strategy = {one_for_one, 10, 10}, 40 | {ok, {Strategy, []}}. 41 | 42 | %% ============================================================================= 43 | %% Module Interface 44 | %% ============================================================================= 45 | start_client(ClientId, Options) -> 46 | Child = {ClientId, {eirc_cl, start_link, [Options]}, 47 | transient, 6000, worker, [eirc_cl]}, 48 | supervisor:start_child(?MODULE, Child). 49 | 50 | stop_client(ClientId) -> 51 | case get_client(ClientId) of 52 | undefined -> ok; 53 | Pid -> 54 | ok = eirc_cl:stop(Pid), 55 | supervisor:delete_child(?MODULE, ClientId) 56 | end. 57 | 58 | get_client(ClientId) -> 59 | case lists:keyfind(ClientId, 1, supervisor:which_children(?MODULE)) of 60 | {ClientId, Pid, _, _} -> Pid; 61 | false -> undefined 62 | end. 63 | 64 | 65 | -------------------------------------------------------------------------------- /src/eirc_example_bot.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) 2010, Mazen Harake 2 | %% All rights reserved. 3 | %% 4 | %% Redistribution and use in source and binary forms, with or without 5 | %% modification, are permitted provided that the following conditions are met: 6 | %% 7 | %% * Redistributions of source code must retain the above copyright notice, 8 | %% this list of conditions and the following disclaimer. 9 | %% * Redistributions in binary form must reproduce the above copyright 10 | %% notice, this list of conditions and the following disclaimer in the 11 | %% documentation and/or other materials provided with the distribution. 12 | %% 13 | %% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | %% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | %% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | %% ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 17 | %% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 | %% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19 | %% SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 20 | %% INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 21 | %% CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 22 | %% ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 23 | %% POSSIBILITY OF SUCH DAMAGE. 24 | 25 | %% This is an example of a bot using the gen_eircbot. 26 | -module(eirc_example_bot). 27 | 28 | -behaviour(gen_eircbot). 29 | 30 | -include("eirc.hrl"). 31 | 32 | -export([init/2, on_connect/3, on_logon/5, on_logon/1, on_text/4, 33 | on_server_notice/3, on_notice/4, on_join/3, on_part/3, on_ctcp/4, 34 | on_mode/5, on_topic/4, on_ping/1, on_nick/3, on_raw/3, on_kick/5, 35 | on_quit/3, handle_call/3, handle_info/2, terminate/2]). 36 | 37 | -compile(export_all). 38 | 39 | -record(botstate, { cl, nick }). 40 | 41 | -define(VERSION, "EIRC Example Bot 0.2"). 42 | 43 | %% ============================================================================= 44 | %% Module API 45 | %% ============================================================================= 46 | 47 | %% ============================================================================= 48 | %% Callbacks 49 | %% ============================================================================= 50 | %% This is called to initialise a state and is before a connections is opened to 51 | %% the server 52 | init(Client, _Args) -> 53 | process_flag(trap_exit, true), 54 | io:format("Initiating bot...~n"), 55 | {ok, #botstate{ cl = Client }}. 56 | 57 | %% Triggers when the bot successfully established a connection to the IRC server 58 | on_connect(IpHost, Port, State) -> 59 | io:format("Connected to ~p:~p~n", [IpHost, Port]), 60 | {ok, State}. 61 | 62 | %% (note: 5 args) Triggers when the client sends the nick user pass arguments to 63 | %% the server 64 | on_logon(_Pass, Nick, _User, _Name, State) -> 65 | io:format("Logging in as ~p~n", [Nick]), 66 | {ok, State#botstate{ nick = Nick }}. 67 | 68 | %% (note: 1 arg) Triggers when the bot has successfully logged in (when the 69 | %% clients receives the WELCOME message code from the irc server) 70 | on_logon(State) -> 71 | io:format("Login successful~n"), 72 | {ok, State}. 73 | 74 | %% Triggered when someone sends a message to the bot. This can be either a user 75 | %% or a channel sending the message 76 | on_text(_From, _To, "!JOIN "++Channel, State) -> 77 | eirc:join(State#botstate.cl, Channel), 78 | {ok, State}; 79 | on_text(_, _, "!PART "++Channel, State) -> 80 | eirc:part(State#botstate.cl, Channel), 81 | {ok, State}; 82 | on_text(From, To, Text, State) -> 83 | io:format("TEXT: From (~p) To (~p) - ~p ~n", [From, To, Text]), 84 | {ok, State}. 85 | 86 | %% Triggers when the server sends us a message and not another user 87 | on_server_notice(ServerName, Msg, State) -> 88 | io:format("NOTICE (~p): ~1000p~n", [ServerName, Msg]), 89 | {ok, State}. 90 | 91 | %% Like on_text but triggers on NOTICE messages instead, sent by a user 92 | on_notice(From, To, Text, State) -> 93 | io:format("NOTICE: From (~p) To (~p) - ~p ~n", [From, To, Text]), 94 | {ok, State}. 95 | 96 | %% Triggers when someone or the bot joins a channel 97 | on_join(User, Channel, State) -> 98 | io:format("JOIN: ~p joined ~p~n", [User, Channel]), 99 | {ok, State}. 100 | 101 | %% The opposite of on_join. User could be the bot 102 | on_part(User, Channel, State) -> 103 | io:format("PART: (~p) ~p parted ~p~n", [State#botstate.nick, User, Channel]), 104 | {ok, State}. 105 | 106 | %% Triggers when a CTCP request is sent to the bot. If these callbacks are *not* 107 | %% implemented then the behaviour will reply with standard responses for the 108 | %% VERSION, TIME and PING CTCP Requests. 109 | on_ctcp(User, "VERSION", _Args, State) -> 110 | %% The VERSION requests needs a reply thats starts with "VERSION " and then 111 | %% Any string to tell the version. This "overrides" the behaviours response 112 | eirc:ctcp(State#botstate.cl, User, "VERSION "++?VERSION), 113 | {ok, State}; 114 | on_ctcp(User, Cmd, Args, State) -> 115 | io:format("CTCP: ~p:~p - ~p~n", [User, Cmd, Args]), 116 | {ok, State}. 117 | 118 | %% Triggers when the Server or a User sets a mode on either a User or a 119 | %% Channel. It is up to this callback to make sense of the modes. The important 120 | %% modes are +/-b for ban, +/-v for voice and +/-o for operatior. 121 | on_mode(ServerOrNick, TargetChanOrNick, ModeFlags, ModeParameters, State) -> 122 | io:format("MODE: ~p sets ~p on ~p (parameters: ~p)~n", 123 | [ServerOrNick, ModeFlags, TargetChanOrNick, ModeParameters]), 124 | {ok, State}. 125 | 126 | %% Triggers when a topic is set in a channel that the bot is in 127 | on_topic(Nick, Channel, Topic, State) -> 128 | io:format("TOPIC: (~p) ~p set topic to: ~p~n", [Channel, Nick, Topic]), 129 | {ok, State}. 130 | 131 | %% Triggers when the servers sends a PING. If the autoping has been set to off 132 | %% then the bot has to reply to the ping here otherwise the client will be 133 | %% kicked out 134 | on_ping(State) -> 135 | io:format("PING? PONG!~n"), 136 | {ok, State}. 137 | 138 | %% Triggers when a user is kicked from a channel, User could be the bot 139 | on_kick(User, Channel, TargetUser, Reason, State) -> 140 | io:format("KICK: ~p kicked ~p from ~p, reason: ~p~n", [User, TargetUser, Channel, Reason]), 141 | {ok, State}. 142 | 143 | %% Triggers when someone (that the bot can "see" in a channel) changes nick, The 144 | %% user changing nick could be the bot 145 | on_nick(OldNick, NewNick, State) -> 146 | io:format("NICK: ~p is now known as ~p~n",[OldNick, NewNick]), 147 | {ok, State}. 148 | 149 | %% Triggers when someone (that the bot can "see" in a channel) quits from the 150 | %% server. The user quitting could be the bot 151 | on_quit(Nick, QuitMsg, State) -> 152 | io:format("QUIT: ~p quit (~1000p)~n",[Nick, QuitMsg]), 153 | {ok, State}. 154 | 155 | %% Any command that is not picked up by the behaviour can be received using this 156 | %% callback. This callback will be triggered for every event the server sends 157 | on_raw(Cmd, Args, State) -> 158 | io:format("RAW: ~p; ~1000p~n",[Cmd, Args]), 159 | {ok, State}. 160 | 161 | handle_call(Call, _From, State) -> 162 | io:format("Unknown call: ~p ~n", [Call]), 163 | {reply, ok, State}. 164 | 165 | handle_info(Msg, State) -> 166 | io:format("Unknown message: ~p ~n", [Msg]), 167 | {ok, State}. 168 | 169 | terminate(Reason, _State) -> 170 | io:format("Bot terminating ~p...~n", [Reason]), 171 | ok. 172 | -------------------------------------------------------------------------------- /src/eirc_lib.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) 2010, Mazen Harake 2 | %% All rights reserved. 3 | %% 4 | %% Redistribution and use in source and binary forms, with or without 5 | %% modification, are permitted provided that the following conditions are met: 6 | %% 7 | %% * Redistributions of source code must retain the above copyright notice, 8 | %% this list of conditions and the following disclaimer. 9 | %% * Redistributions in binary form must reproduce the above copyright 10 | %% notice, this list of conditions and the following disclaimer in the 11 | %% documentation and/or other materials provided with the distribution. 12 | %% 13 | %% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | %% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | %% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | %% ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 17 | %% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 | %% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19 | %% SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 20 | %% INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 21 | %% CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 22 | %% ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 23 | %% POSSIBILITY OF SUCH DAMAGE. 24 | 25 | -module(eirc_lib). 26 | -include("eirc.hrl"). 27 | 28 | -compile(export_all). 29 | 30 | %% ============================================================================= 31 | %% Generic IRC message parse 32 | %% ============================================================================= 33 | parse(UnstrippedData) -> 34 | Data = string:substr(UnstrippedData,1,length(UnstrippedData)-2), 35 | case Data of 36 | [$:|_] -> 37 | [[$:|From]|RestData] = string:tokens(Data," "), 38 | getcmd(RestData, parsefrom(From, #ircmsg{ ctcp = false })); 39 | Data -> 40 | getcmd(string:tokens(Data," "), #ircmsg{ ctcp = false }) 41 | end. 42 | 43 | parsefrom(FromStr, Msg) -> 44 | case re:split(FromStr, "(!|@|\\.)",[{return, list}]) of 45 | [Nick, "!", User, "@", Host|HostRest] -> 46 | Msg#ircmsg{ nick = Nick, user = User, host = Host++HostRest }; 47 | [Nick, "@", Host|HostRest] -> 48 | Msg#ircmsg{ nick = Nick, host = Host++HostRest }; 49 | [_,"."|_] -> 50 | %% FromStr is _most_likely_ a server name 51 | Msg#ircmsg{ server = FromStr }; 52 | [Nick] -> 53 | Msg#ircmsg{ nick = Nick } 54 | end. 55 | 56 | getcmd([Cmd,Arg1,[$:,1|CTCPTrail]|RestArgs], Msg) when Cmd == "PRIVMSG"; 57 | Cmd == "NOTICE" -> 58 | getcmd([Cmd,Arg1,[1|CTCPTrail]|RestArgs], Msg); 59 | getcmd([Cmd,_Arg1,[1|CTCPTrail]|RestArgs], Msg) when Cmd == "PRIVMSG"; 60 | Cmd == "NOTICE" -> 61 | case lists:reverse(lists:flatten(CTCPTrail++[" "++Arg||Arg<-RestArgs])) of 62 | [1|CTCPRev] -> 63 | [CTCPCmd|Args] = string:tokens(lists:reverse(CTCPRev)," "), 64 | Msg#ircmsg{ cmd = CTCPCmd, args = Args, ctcp = true }; 65 | _ -> 66 | Msg#ircmsg{ cmd = Cmd, ctcp = invalid } 67 | end; 68 | getcmd([Cmd|RestData], Msg) -> 69 | getargs(RestData, Msg#ircmsg{ cmd = Cmd }). 70 | 71 | getargs([], Msg) -> 72 | Msg#ircmsg{ args = lists:reverse(Msg#ircmsg.args) }; 73 | getargs([[$:|FirstArg]|RestArgs], Msg) -> 74 | case lists:flatten([" "++Arg||Arg<-[FirstArg|RestArgs]]) of 75 | [_|[]] -> 76 | getargs([], Msg#ircmsg{ args = [""|Msg#ircmsg.args] }); 77 | [_|FullTrail] -> 78 | getargs([], Msg#ircmsg{ args = [FullTrail|Msg#ircmsg.args] }) 79 | end; 80 | getargs([Arg|[]], Msg) -> 81 | getargs([], Msg#ircmsg{ args = ["",Arg|Msg#ircmsg.args] }); 82 | getargs([Arg|RestData], Msg) -> 83 | getargs(RestData, Msg#ircmsg{ args = [Arg|Msg#ircmsg.args] }). 84 | 85 | %% ============================================================================= 86 | %% RPL_ISUPPORT (005) parse 87 | %% ============================================================================= 88 | isup([], State) -> State; 89 | isup([Param|Rest], State) -> 90 | try isup(Rest, isup_param(Param, State)) 91 | catch _:_ -> isup(Rest, State) end. 92 | 93 | isup_param("CHANTYPES="++ChanPrefixes, State) -> 94 | State#eirc_state{ chprefix = ChanPrefixes }; 95 | isup_param("NETWORK="++Network, State) -> 96 | State#eirc_state{ network = Network }; 97 | isup_param("PREFIX="++UserPrefixes, State) -> 98 | {match,[{P1,L1},{P2,L2}]} = 99 | re:run(UserPrefixes, "\\((.*)\\)(.*)", [{capture, all_but_first}]), 100 | State#eirc_state{ usrprefix = lists:zip(string:substr(UserPrefixes,P1+1,L1), 101 | string:substr(UserPrefixes,P2+1,L2)) }; 102 | isup_param(_, State) -> 103 | State. 104 | 105 | %% ============================================================================= 106 | %% Helper functions 107 | %% ============================================================================= 108 | ctcp_time({{Y,M,D},{H,N,S}}) -> 109 | [lists:nth(calendar:day_of_the_week(Y,M,D), 110 | ["Mon","Tue","Wed","Thu","Fri","Sat","Sun"])," ", 111 | lists:nth(M, ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug", 112 | "Sep","Oct","Nov","Dec"])," ", 113 | io_lib:format("~2..0s",[integer_to_list(D)])," ", 114 | io_lib:format("~2..0s",[integer_to_list(H)]),":", 115 | io_lib:format("~2..0s",[integer_to_list(N)]),":", 116 | io_lib:format("~2..0s",[integer_to_list(S)])," ", 117 | integer_to_list(Y)]. 118 | -------------------------------------------------------------------------------- /src/gen_eircbot.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) 2010, Mazen Harake 2 | %% All rights reserved. 3 | %% 4 | %% Redistribution and use in source and binary forms, with or without 5 | %% modification, are permitted provided that the following conditions are met: 6 | %% 7 | %% * Redistributions of source code must retain the above copyright notice, 8 | %% this list of conditions and the following disclaimer. 9 | %% * Redistributions in binary form must reproduce the above copyright 10 | %% notice, this list of conditions and the following disclaimer in the 11 | %% documentation and/or other materials provided with the distribution. 12 | %% 13 | %% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | %% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | %% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | %% ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 17 | %% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 | %% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19 | %% SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 20 | %% INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 21 | %% CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 22 | %% ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 23 | %% POSSIBILITY OF SUCH DAMAGE. 24 | 25 | -module(gen_eircbot). 26 | -export([behaviour_info/1]). 27 | 28 | -include("eirc.hrl"). 29 | 30 | %% Application API 31 | %% -export([connect/4, connect_link/4, disconnect/2]). 32 | -compile(export_all). 33 | 34 | -record(st, { cbmod, cbstate, iphost, port, nick, clpid, exiting, logged_on }). 35 | 36 | -define(CTCP_VERSION, "VERSION EIRC-BOT 0.0.1"). 37 | -define(CTCP_TIME, "TIME "++eirc_lib:ctcp_time(calendar:local_time())). 38 | -define(CTCP_PING(TS), "PING "++TS). 39 | 40 | -define(MISSING_CALLBACKS, Cb == on_connect; Cb == on_text; 41 | Cb == on_server_notice; Cb == on_notice; Cb == on_join; 42 | Cb == on_part; Cb == on_mode; Cb == on_topic; Cb == on_ping; 43 | Cb == on_kick; Cb == on_nick; Cb == on_raw; Cb == on_quit; 44 | Cb == handle_call; Cb == terminate). 45 | 46 | %% ============================================================================= 47 | %% Application API 48 | %% ============================================================================= 49 | start_link(ClPid, Callback, InitArgs) -> 50 | gen_server:start_link(?MODULE, {ClPid, Callback, InitArgs}, []). 51 | 52 | state(Server) -> 53 | State = call(Server, {'$gen_eircbot', state}), 54 | [{cbmod, State#st.cbmod}, 55 | {cbstate, State#st.cbstate}]. 56 | 57 | call(Server, Msg) -> 58 | gen_server:call(Server, Msg). 59 | 60 | call(Server, Msg, Timeout) -> 61 | gen_server:call(Server, Msg, Timeout). 62 | 63 | init({ClPid, Callback, InitArgs}) -> 64 | eirc_cl:asynch_add_handler(ClPid, self()), 65 | {ok, _State} = safe_callback({init, Callback, [ClPid, InitArgs]}, undefined). 66 | 67 | handle_call({'$gen_eircbot', state}, _From, State) -> 68 | {reply, State, State}; 69 | handle_call(Call, From, State) -> 70 | safe_callback({handle_call, [Call, From]}, State). 71 | 72 | handle_cast(_Cast, State) -> 73 | {no_reply, State}. 74 | 75 | handle_info(Message, State) -> 76 | safe_callback(Message, State). 77 | 78 | code_change(_OldVsn, State, _Extra) -> 79 | {ok, State}. 80 | 81 | terminate(Reason, State) -> 82 | safe_callback({terminate, [Reason]}, State). 83 | 84 | safe_callback(Args, State) -> 85 | try 86 | callback(get_call_tuple(Args, State)) 87 | catch 88 | throw:{stop, normal, NState} -> 89 | {stop, normal, NState}; 90 | throw:{stop, Reason, NState} -> 91 | {stop, Reason, NState}; 92 | error:undef -> 93 | case erlang:get_stacktrace() of 94 | [{_, on_ctcp, _}|_] -> 95 | handle_default_ctcp(Args, State), 96 | {noreply, State}; 97 | [{Mod, Cb, _}|_] when ?MISSING_CALLBACKS -> 98 | error_logger:warning_report([{unexported_callback, {Mod, Cb}}]), 99 | {noreply, State}; 100 | StackTrace -> 101 | erlang:error(undef, StackTrace) 102 | end 103 | end. 104 | 105 | get_call_tuple({init, CBMod, Args}, undefined) -> 106 | {CBMod, init, Args}; 107 | get_call_tuple({connect, Server, Port}, State) -> 108 | {State, on_connect, [Server, Port]}; 109 | get_call_tuple({logon, Pass, Nick, User, Name}, State) -> 110 | {State, on_logon, [Pass, Nick, User, Name]}; 111 | get_call_tuple({handle_call, Args}, State) -> 112 | {State, handle_call, Args}; 113 | get_call_tuple({terminate, Args}, State) -> 114 | {State, terminate, Args}; 115 | get_call_tuple(#ircmsg{ cmd = ?RPL_WELCOME } = IrcMsg, State) -> 116 | {State, on_logon, IrcMsg}; 117 | get_call_tuple(#ircmsg{ ctcp = true } = IrcMsg, State) -> 118 | {State, on_ctcp, [IrcMsg#ircmsg.nick, IrcMsg#ircmsg.cmd, IrcMsg#ircmsg.args]}; 119 | get_call_tuple(#ircmsg{ cmd = "PRIVMSG" } = IrcMsg, State) -> 120 | FromNick = IrcMsg#ircmsg.nick, 121 | [ToNick|Msg] = IrcMsg#ircmsg.args, 122 | {State, on_text, [FromNick, ToNick, lists:flatten(Msg)]}; 123 | get_call_tuple(#ircmsg{ cmd = "NOTICE", nick = undefined } = IrcMsg, State) -> 124 | Server = IrcMsg#ircmsg.server, 125 | [_|Msg] = IrcMsg#ircmsg.args, 126 | {State, on_server_notice, [Server, Msg]}; 127 | get_call_tuple(#ircmsg{ cmd = "NOTICE" } = IrcMsg, State) -> 128 | FromNick = IrcMsg#ircmsg.nick, 129 | [ToNick|Msg] = IrcMsg#ircmsg.args, 130 | {State, on_notice, [FromNick, ToNick, lists:flatten(Msg)]}; 131 | get_call_tuple(#ircmsg{ cmd = "JOIN" } = IrcMsg, State) -> 132 | Nick = IrcMsg#ircmsg.nick, 133 | Channel = hd(IrcMsg#ircmsg.args), 134 | {State, on_join, [Nick, Channel]}; 135 | get_call_tuple(#ircmsg{ cmd = "PART" } = IrcMsg, State) -> 136 | Nick = IrcMsg#ircmsg.nick, 137 | Channel = hd(IrcMsg#ircmsg.args), 138 | {State, on_part, [Nick, Channel]}; 139 | get_call_tuple(#ircmsg{ cmd = "MODE" } = IrcMsg, State) -> 140 | ServerOrNick = IrcMsg#ircmsg.nick, 141 | [ChanOrNick,Flags|Parameters] = IrcMsg#ircmsg.args, 142 | {State, on_mode, [ServerOrNick, ChanOrNick, Flags, Parameters]}; 143 | get_call_tuple(#ircmsg{ cmd = "TOPIC" } = IrcMsg, State) -> 144 | Nick = IrcMsg#ircmsg.nick, 145 | [Channel|Topic] = IrcMsg#ircmsg.args, 146 | {State, on_topic, [Nick, Channel, hd(Topic)]}; 147 | get_call_tuple(#ircmsg{ cmd = "PING" }, State) -> 148 | {State, on_ping, []}; 149 | get_call_tuple(#ircmsg{ cmd = "KICK" } = IrcMsg, State) -> 150 | Nick = IrcMsg#ircmsg.nick, 151 | [Channel, TargetUser, Reason|_] = IrcMsg#ircmsg.args, 152 | {State, on_kick, [Nick, Channel, TargetUser, Reason]}; 153 | get_call_tuple(#ircmsg{ cmd = "NICK" } = IrcMsg, State) -> 154 | Nick = IrcMsg#ircmsg.nick, 155 | [NewNick|_] = IrcMsg#ircmsg.args, 156 | {State, on_nick, [Nick, NewNick]}; 157 | get_call_tuple(#ircmsg{ cmd = "QUIT" } = IrcMsg, State) -> 158 | Nick = IrcMsg#ircmsg.nick, 159 | QuitMsg = hd(IrcMsg#ircmsg.args), 160 | {State, on_quit, [Nick, QuitMsg]}; 161 | get_call_tuple(#ircmsg{} = IrcMsg, State) -> 162 | {State, on_raw, IrcMsg}; 163 | get_call_tuple(Message, State) -> 164 | {State, handle_info, [Message]}. 165 | 166 | handle_default_ctcp(#ircmsg{ cmd = "VERSION" } = IrcMsg, State) -> 167 | eirc_cl:msg(State#st.clpid, ctcp, IrcMsg#ircmsg.nick, ?CTCP_VERSION); 168 | handle_default_ctcp(#ircmsg{ cmd = "TIME" } = IrcMsg, State) -> 169 | eirc_cl:msg(State#st.clpid, ctcp, IrcMsg#ircmsg.nick, ?CTCP_TIME); 170 | handle_default_ctcp(#ircmsg{ cmd = "PING", args = [Timestamp] } = IrcMsg, State) -> 171 | eirc_cl:msg(State#st.clpid, ctcp, IrcMsg#ircmsg.nick, ?CTCP_PING(Timestamp)); 172 | handle_default_ctcp(#ircmsg{ cmd = Cmd } = IrcMsg, State) -> 173 | eirc_cl:msg(State#st.clpid, ctcp, IrcMsg#ircmsg.nick, Cmd++" N/A"). 174 | 175 | callback({CBMod, init, [ClPid, Args]}) -> 176 | case erlang:apply(CBMod, init, [ClPid, Args]) of 177 | {ok, CBState} -> 178 | {ok, #st{ clpid = ClPid, cbmod = CBMod, cbstate = CBState }}; 179 | {stop, Reason} -> 180 | throw({stop, Reason, undefined}) 181 | end; 182 | callback({State, on_connect, [IpHost, Port] = Args}) -> 183 | case erlang:apply(State#st.cbmod, on_connect, Args++[State#st.cbstate]) of 184 | {ok, CBState} -> 185 | {noreply, State#st{ cbstate = CBState, iphost = IpHost, port = Port }}; 186 | {stop, Reason, CBState} -> 187 | throw({stop, Reason, State#st{ cbstate = CBState }}) 188 | end; 189 | callback({State, on_logon, Args}) when length(Args) == 4 -> 190 | case erlang:apply(State#st.cbmod, on_logon, Args++[State#st.cbstate]) of 191 | {ok, CBState} -> 192 | {noreply, State#st{ cbstate = CBState }}; 193 | {stop, Reason, CBState} -> 194 | throw({stop, Reason, State#st{ cbstate = CBState }}) 195 | end; 196 | callback({State, handle_call, Args}) -> 197 | case erlang:apply(State#st.cbmod, handle_call, Args++[State#st.cbstate]) of 198 | {reply, Reply, CBState} -> 199 | {reply, Reply, State#st{ cbstate = CBState }}; 200 | {stop, Reason, Reply, CBState} -> 201 | {stop, Reason, Reply, State#st{ cbstate = CBState }} 202 | end; 203 | callback({State, terminate, Args}) -> 204 | (catch erlang:apply(State#st.cbmod, terminate, Args++[State#st.cbstate])), 205 | ok; 206 | callback({State, on_logon, IrcMsg}) -> 207 | case erlang:apply(State#st.cbmod, on_logon, [State#st.cbstate]) of 208 | {ok, CBState} -> 209 | callback({State#st{ cbstate = CBState }, on_raw, IrcMsg}); 210 | {stop, Reason, CBState} -> 211 | throw({stop, Reason, State#st{ cbstate = CBState }}) 212 | end; 213 | callback({State, on_raw, IrcMsg}) -> 214 | case erlang:apply(State#st.cbmod, on_raw, [IrcMsg#ircmsg.cmd, IrcMsg#ircmsg.args, 215 | State#st.cbstate]) of 216 | {ok, CBState} -> 217 | {noreply, State#st{ cbstate = CBState }}; 218 | {stop, Reason, CBState} -> 219 | throw({stop, Reason, State#st{ cbstate = CBState }}) 220 | end; 221 | callback({State, CBFunction, Args}) -> 222 | case erlang:apply(State#st.cbmod, CBFunction, Args++[State#st.cbstate]) of 223 | {ok, CBState} -> 224 | {noreply, State#st{ cbstate = CBState }}; 225 | {stop, Reason, CBState} -> 226 | throw({stop, Reason, State#st{ cbstate = CBState }}) 227 | end. 228 | 229 | %% ============================================================================= 230 | %% Behaviour API 231 | %% ============================================================================= 232 | behaviour_info(callbacks) -> 233 | [{init, 2}, {on_connect, 3}, {on_logon, 5}, {on_logon, 1}, {on_text, 4}, 234 | {on_server_notice, 3}, {on_notice, 4}, {on_join, 3}, {on_part, 3}, 235 | {on_ctcp, 4}, {on_mode, 5}, {on_topic, 4}, {on_ping, 1}, {on_kick, 5}, 236 | {on_nick, 3}, {on_quit, 3}, {on_ctcp, 4}, {on_raw, 3}, {handle_call, 3}, 237 | {handle_info, 2}, {terminate, 2}]. 238 | --------------------------------------------------------------------------------