From 6cfaa6fdd04e04b626a4880a9eb2af0cfe8bb2d8 Mon Sep 17 00:00:00 2001
From: "Mirar (Pontus Hagland)" <pike@sort.mirar.org>
Date: Wed, 27 Oct 1999 20:01:16 +0200
Subject: [PATCH] Protocols.IRC :)

Rev: lib/modules/Protocols.pmod/IRC.pmod/Client.pike:1.1
Rev: lib/modules/Protocols.pmod/IRC.pmod/Error.pmod:1.1
Rev: lib/modules/Protocols.pmod/IRC.pmod/Raw.pike:1.1
Rev: lib/modules/Protocols.pmod/IRC.pmod/Requests.pmod:1.1
---
 .gitattributes                                |   1 +
 .../Protocols.pmod/IRC.pmod/Client.pike       | 393 ++++++++++++++++++
 .../Protocols.pmod/IRC.pmod/Error.pmod        |  47 +++
 lib/modules/Protocols.pmod/IRC.pmod/Raw.pike  | 146 +++++++
 .../Protocols.pmod/IRC.pmod/Requests.pmod     | 114 +++++
 5 files changed, 701 insertions(+)
 create mode 100644 lib/modules/Protocols.pmod/IRC.pmod/Client.pike
 create mode 100644 lib/modules/Protocols.pmod/IRC.pmod/Error.pmod
 create mode 100644 lib/modules/Protocols.pmod/IRC.pmod/Raw.pike
 create mode 100644 lib/modules/Protocols.pmod/IRC.pmod/Requests.pmod

diff --git a/.gitattributes b/.gitattributes
index 9b807419db..82a1b7eb0e 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -54,6 +54,7 @@ testfont binary
 /lib/modules/MIME.pmod foreign_ident
 /lib/modules/Protocols.pmod/IMAP.pmod/imap_server.pike foreign_ident
 /lib/modules/Protocols.pmod/IMAP.pmod/requests.pmod foreign_ident
+/lib/modules/Protocols.pmod/IRC.pmod/Requests.pmod foreign_ident
 /lib/modules/Protocols.pmod/Ident.pmod foreign_ident
 /lib/modules/Protocols.pmod/LDAP.pmod/client.pike foreign_ident
 /lib/modules/Protocols.pmod/LDAP.pmod/ldap_errors.h foreign_ident
diff --git a/lib/modules/Protocols.pmod/IRC.pmod/Client.pike b/lib/modules/Protocols.pmod/IRC.pmod/Client.pike
new file mode 100644
index 0000000000..00f2c6233b
--- /dev/null
+++ b/lib/modules/Protocols.pmod/IRC.pmod/Client.pike
@@ -0,0 +1,393 @@
+import ".";
+
+object raw;
+string pass=MIME.encode_base64(Crypto.randomness.reasonably_random()->read(6));
+
+mapping options;
+
+mapping channels=([]);
+
+void create(string _server,void|mapping _options)
+{
+   options=
+      ([
+	 "port":6667,
+#if constant(getpwuid) && constant(getuid)
+	 "user":(getpwuid(getuid())||({"-"}))[0],
+#else
+	 "user":"unknown",
+#endif
+#if constant(getpwuid) && constant(getuid)
+	 "nick":String.capitalize((getpwuid(getuid())||({"-"}))[0]),
+#else
+	 "nick":"Unknown"+random(10000),
+#endif
+#if constant(getpwuid) && constant(getuid)
+	 "realname":(getpwuid(getuid())||({0,0,0,0,"Mr. Nobody"}))[4],
+#else
+	 "realname":"Mr. Anonymous",
+#endif
+#if constant(uname)
+	 "host":uname()->nodename||"localhost",
+#else
+	 "host":"localhost",
+#endif
+      ])|(_options||([]));
+
+   options->server=_server;
+
+   me=person(options->nick,options->user+"@"+options->host);
+
+   raw=Raw(options->server,options->port,got_command,got_notify,
+	   connection_lost);
+
+   cmd->create(raw);
+
+   cmd->pass(pass); 
+   cmd->nick(options->nick);
+   cmd->user(options->user,options->host,options->server,options->realname);
+
+   call_out(da_ping,30);
+}
+
+string expecting_pong;
+
+void da_ping()
+{
+   call_out(da_ping,30);
+   call_out(no_ping_reply,30); // timeout
+   cmd->ping(expecting_pong=
+	     options->host+" "+Array.shuffle("pike""IRC""client"/"")*"");
+}
+
+void no_ping_reply()
+{
+   remove_call_out(da_ping);
+   werror("no ping reply\n");
+   connection_lost();
+}
+
+void connection_lost()
+{
+   if (options->connection_lost)
+      options->connection_lost();
+   else
+   {
+      werror("destructing self\n");
+      catch { destruct(raw->con); };
+      destruct(raw);
+      destruct(this_object());
+   }
+}
+
+void got_command(string what,string ... args)
+{
+   // werror("got command: %O, %O\n",what,args);
+   // most commands can be handled immediately
+   switch (what)
+   {
+      case "PING":
+	 cmd->pong(args[0]);
+	 return;
+      case "KILL":
+      case "ERROR":
+	 if (options->error_notify)
+	    options->error_notify(@args);
+	 return;
+   }
+   werror("got command: %O, %O\n",what,args);
+}
+
+void got_notify(string from,string type,
+		void|string to,void|string message,
+		void|string extra,void|string extra2)
+{
+   object c;
+   if (options->system_notify && glob("2??",type))
+   {
+      options->system_notify(type,message);
+      return;
+   }
+   if (options->motd_notify && (<"372","375","376">)[type])
+   {
+      options->motd_notify((["372":"cont",
+			     "375":"start",
+			     "376":"end"])[type],message);
+      return;
+   }
+
+   object originator=person(@(from/"!"));
+
+   switch (type)
+   {
+      case "433": // nick in use
+	 if (options->error_nickinuse)
+	    options->error_nickinuse(message);
+	 else if (options->generic_notify) break;
+	 cmd->nick("unknown"+random(10000));
+	 return;
+
+      case "473": // failed to join
+	 if ((c=channels[lower_case(message||"")]))
+	 {
+	    c->not_failed_to_join();
+	    return;
+	 }
+	 break;
+
+      case "353": // names list
+	 if (extra && (c=channels[lower_case(extra||"")]))
+	 {
+	    c->not_names(map(extra2/" "-({""}),
+			     lambda(string name)
+			     {
+				string a,b,c;
+				sscanf(name,"%[@]%s%[+]",
+				       a,b,c);
+				return ({person(b),a+c});
+			     }));
+	    return;
+	 }
+	 break;
+
+	 /* --- */
+
+      case "PONG":
+	 if (message==expecting_pong)
+	 {
+	    remove_call_out(no_ping_reply);
+	    return;
+	 }
+	 break;
+
+      case "MODE":
+	 if ((c=channels[lower_case(to||"")]))
+	 {
+	    // who, mode, by
+	    c->not_mode(extra?person(extra):originator,message,originator);
+	    return;
+	 }
+	 break;
+
+      case "JOIN":
+	 if ((c=channels[lower_case(to||"")]))
+	 {
+	    c->not_join(originator);
+	    return;
+	 }
+	 break;
+
+      case "PART":
+	 if ((c=channels[lower_case(to||"")]))
+	 {
+	    // who, why, by
+	    c->not_part(originator,message,originator);
+	    return;
+	 }
+	 break;
+
+      case "KICK":
+	 if ((c=channels[lower_case(to||"")]))
+	 {
+	    // who, why, by
+	    c->not_part(person(message),extra,originator);
+	    return;
+	 }
+	 break;
+
+      case "QUIT":
+	 forget_person(originator);
+	 foreach (values(channels),c)
+	    c->not_part(originator,message,originator);
+	 if (options->quit_notify)
+	 {
+	    // who, why
+	    options->quit_notify(originator,to);
+	    return;
+	 }
+	 break;
+
+      case "PRIVMSG":
+	 if ((c=channels[lower_case(to||"")]))
+	 {
+	    c->not_message(originator,message);
+	    return;
+	 }
+	 if (!options->privmsg_notify) break;
+	 options->privmsg_notify(originator,message,to);
+	 return;
+
+      case "NICK":
+// 	 werror("%s is known as %s (aka %s)\n",
+// 		originator->nick,
+// 		to,
+// 		((array)originator->aka)*",");
+	 mixed err=0;
+	 originator->aka[originator->nick]=1;
+	 originator->aka[to]=1;
+	 if (options->nick_notify)
+	    err=catch { options->nick_notify(originator,to); };
+	 originator->nick=to;
+	 nick2person[to]=originator;
+	 if (err) throw(err);
+	 return;
+   }
+//    werror("got notify: %O, %O, %O, %O\n",from,type,to,message);
+   if (options->generic_notify)
+      options->generic_notify(from,type,to,message,extra);
+}
+
+object cmd=class
+{
+   object raw;
+   public void create(object _raw) { raw=_raw; }
+
+   class SyncRequest
+   {
+      program prog;
+      object ret;
+   	    
+      void create(program p,object _ret)
+      {
+   	 prog=p;
+   	 ret=_ret;
+      }
+   
+      mixed `()(mixed ...args)
+      {
+   	 mixed m;
+   	 object req=prog();
+   	 m=req->sync(raw,@args);
+   	 if (!m) return ret;
+   	 else return m;
+      }
+   }
+   	 
+   class AsyncRequest
+   {
+      program prog;
+   	    
+      void create(program p)
+      {
+   	 prog=p;
+      }
+   
+      mixed `()(mixed ...args)
+      {
+   	 object req=prog();
+   	 req->async(this_object(),@args);
+   	 return req;
+      }
+   }
+   
+   class AsyncCBRequest
+   {
+      program prog;
+   
+      void create(program p)
+      {
+   	 prog=p;
+      }
+   
+      mixed `()(function callback,mixed ...args)
+      {
+   	 object req=prog();
+   	 req->callback=callback;
+   	 req->async(this_object(),@args);
+   	 return req;
+      }
+   }
+   
+   mixed `->(string request)
+   {
+      mixed|program p;
+
+
+      if ( (p=Requests[request]) )
+   	 return SyncRequest(p,this_object());
+      else if ( request[..5]=="async_" &&
+   		(p=Requests[request[6..]]) )
+   	 return AsyncRequest(p);
+      else if ( request[..8]=="async_cb_" &&
+   		(p=Requests[request[9..]]) )
+   	 return AsyncCBRequest(p);
+      else
+      {
+	 p=::`[](request);
+	 if (!p)
+	 {
+	    werror("FOO! FOO! FOO! %O %O\n",request,p);
+	    Error.internal("unknown command %O",request);
+	    return 0;
+	 }
+	 return p;
+      }
+   }
+} (raw);
+
+// ----- commands
+
+void send_message(string|array to,string msg)
+{
+   cmd->privmsg( (arrayp(to)?to*",":to), msg);
+}
+
+// ----- channel
+
+class Channel
+{
+   string name;
+
+   void	not_message(string who,string message);
+   void	not_join(string who);
+   void	not_part(string who,string message,string executor);
+   void	not_mode(string who,string mode);
+   void	not_failed_to_join();
+}
+
+// ----- persons
+
+class Person
+{
+   string nick; // Mirar
+   string user; // mirar
+   string ip;   // mistel.idonex.se
+   int last_action; // time_t
+   multiset aka=(<>);
+
+   void say(string message)
+   {
+      cmd->privmsg(nick,message);
+   }
+
+   void me(string what)
+   {
+      say("\1ACTION "+what+"\1");
+   }
+}
+
+mapping nick2person=([]);
+object me;
+
+Person person(string who,void|string ip)
+{
+   Person p;
+
+   if ( ! (p=nick2person[who]) )
+   {
+      p=Person();
+      p->nick=who;
+      nick2person[who]=p;
+   }
+   if (ip && !p->ip)
+      sscanf(ip,"%*[~]%s@%s",p->user,p->ip);
+
+//    werror("%O is %O %O %O\n",who,p->nick,p->user,p->ip);
+
+   return p;
+}
+
+void forget_person(object p)
+{
+   m_delete(nick2person,p->nick);
+}
diff --git a/lib/modules/Protocols.pmod/IRC.pmod/Error.pmod b/lib/modules/Protocols.pmod/IRC.pmod/Error.pmod
new file mode 100644
index 0000000000..c0e6a4a1b2
--- /dev/null
+++ b/lib/modules/Protocols.pmod/IRC.pmod/Error.pmod
@@ -0,0 +1,47 @@
+
+class StdErr
+{
+   constant is_generic_error=1;
+   constant is_irc_error=1;
+
+   string name;
+   array(mixed) __backtrace;
+
+   void create(string _name)
+   {
+      name=_name;
+      __backtrace=backtrace();
+      while (!__backtrace[-1][0]||
+	     __backtrace[-1][0][strlen(__backtrace[-1][0])-11..]
+	     == "/Error.pmod")
+	 __backtrace=__backtrace[0..sizeof(__backtrace)-2];
+   }
+
+   mixed `[](mixed z)
+   {
+      switch (z)
+      {
+	 case 0: return "Protocols.IRC:  "+name+"\n";
+	 case 1: return __backtrace;
+      }
+      return ::`[](z);
+   }
+}
+
+class Connection
+{
+   inherit StdErr;
+
+   constant is_irc_connection_error=1;
+
+   int errno;
+
+   void create(string desc,int _errno)
+   {
+      ::create(desc+" ("+strerror(errno=_errno)+")");
+   }
+}
+
+void connection(mixed ...args) { throw(Connection(@args)); }
+void internal(string format,mixed ...args) 
+   { throw(StdErr(sprintf(format+" (internal error)",@args))); }
diff --git a/lib/modules/Protocols.pmod/IRC.pmod/Raw.pike b/lib/modules/Protocols.pmod/IRC.pmod/Raw.pike
new file mode 100644
index 0000000000..9ce8706014
--- /dev/null
+++ b/lib/modules/Protocols.pmod/IRC.pmod/Raw.pike
@@ -0,0 +1,146 @@
+import ".";
+
+object con;
+
+// #define IRC_DEBUG
+
+function(string,string ...:void) command_callback;
+function(string,string,...:void) notify_callback;
+function(:void) close_callback;
+
+// command_callback(string cmd,string ... parameters)
+// notify_callback(string from,string type,string from,string message)
+
+void create(string server,int port,
+	    function(string,string ...:void) _command_callback,
+	    void|function(string,string...:void) _notify_callback,
+	    void|function(:void) _close_callback)
+{
+   array aserver=gethostbyname(server);
+   if (!aserver || !sizeof(aserver[1]))
+      Error.connection("Failed to lookup host %O",server);
+   server=aserver[1][random(sizeof(aserver[1]))];
+
+   con=Stdio.File();
+   if (!con->connect(server,port))
+      Error.connection("Failed to connect",con->errno());
+
+   con->set_nonblocking(con_read,0,con_close);
+
+   command_callback=_command_callback;
+   notify_callback=_notify_callback;
+   close_callback=_close_callback;
+}
+
+void con_close()
+{
+#ifdef IRC_DEBUG
+   werror("-> (connection closed)\n");
+#endif
+   con->close();
+   con=0;
+   if (close_callback) close_callback();
+}
+
+string buf="";
+
+void con_read(mixed dummy,string what)
+{
+#ifdef IRC_DEBUG
+   werror("-> %O\n",what);
+#endif
+   buf+=what;
+   array lines=(buf-"\r")/"\n";
+   buf=lines[-1];
+   foreach (lines[0..sizeof(lines)-2],string row)
+   {
+      mixed err=catch 
+      {
+	 handle_command(row);
+      };
+      if (err)
+	 werror(master()->describe_backtrace(err));
+   }
+}
+
+array write_buf=({});
+
+void con_write_callback()
+{
+   while (sizeof(write_buf))
+   {
+      int j=con->write(write_buf[0]);
+      if (j!=strlen(write_buf[0]))
+      {
+	 if (j==-1) 
+	 {
+#ifdef IRC_DEBUG
+	    werror("<- (write error)\n");
+#endif
+	    break; // connection error?
+	 }
+#ifdef IRC_DEBUG
+	 werror("<- %O (buffer full)\n",write_buf[0][0..j-1]);
+#endif
+	 write_buf[0]=write_buf[0][j..];
+      }
+#ifdef IRC_DEBUG
+      werror("<- %O (buffer finished)\n",write_buf[0]);
+#endif
+      write_buf=write_buf[1..];
+   }
+   con->set_nonblocking(con_read,0,con_close);
+}
+
+void con_write(string s)
+{
+   if (sizeof(write_buf))
+   {
+      write_buf+=({s});
+      return;
+   }
+   int j=con->write(s);
+   if (j!=sizeof(s))
+   {
+      if (j==-1) 
+      {
+#ifdef IRC_DEBUG
+	 werror("<- (write error; %O)\n",strerror(con->errno()));
+#endif
+	 return; // connection broken?
+      }
+#ifdef IRC_DEBUG
+      werror("<- %O [%d] (buffer full)\n",s[0..j-1],j);
+#endif
+      write_buf=({s[j..]});
+      con->set_nonblocking(con_read,con_write_callback,con_close);
+   }
+#ifdef IRC_DEBUG
+   else
+      werror("<- %O [%d]\n",s,j);
+#endif
+}
+
+void transmit_noreply(string cmd,string args)
+{
+   con_write(cmd+" "+args+"\r\n");
+}
+
+void handle_command(string cmd)
+{
+   string a,b;
+   if (cmd=="") return;
+   if (cmd[0]==':') 
+   {
+      if (!notify_callback) return;
+      if (sscanf(cmd,":%s :%s",a,b)==2)
+	 notify_callback(@(a/" "),b);
+      else
+	 notify_callback(@(cmd[1..]/" "));
+      return;
+   }
+   if (sscanf(cmd,"%s :%s",a,b)==2)
+      command_callback(@(a/" "),b);
+   else
+      command_callback(@(cmd/" "));
+}
diff --git a/lib/modules/Protocols.pmod/IRC.pmod/Requests.pmod b/lib/modules/Protocols.pmod/IRC.pmod/Requests.pmod
new file mode 100644
index 0000000000..46aae2e20d
--- /dev/null
+++ b/lib/modules/Protocols.pmod/IRC.pmod/Requests.pmod
@@ -0,0 +1,114 @@
+
+/*
+PASS gurka
+NICK Mirar^
+USER mirar mistel.idonex.se irc.starchat.net :Mirar is testing
+*/
+
+string __cvs_id="$Id: Requests.pmod,v 1.1 1999/10/27 18:01:16 mirar Exp $";
+
+import ".";
+
+class Request
+{
+   function callback;
+   string cmd=0;
+   
+   void async(object con,mixed ...args)
+   {
+      con->transmit_async(cmd,encode(args),got_answer);
+   }
+
+   mixed sync(object con,mixed ...args)
+   {
+      return decode_answer(con->transmit(cmd,encode(args)));
+   }
+
+   void got_answer(mixed s)
+   {
+      if (callback) callback(decode_answer(s));
+   }
+
+   void got_error(object error)
+   {
+      if (callback) callback(error);
+   }
+
+   string encode(mixed ...);
+   mixed decode_answer(string s);
+}
+
+class NoReply
+{
+   string source=
+"#"+__LINE__+" \""+__FILE__+#" (NoReply.%cmd%)\"
+inherit Protocols.IRC.Requests.Request;
+
+string cmd=\"%cmd%\";
+
+void async(object con,mixed ...args)
+{
+    con->transmit_noreply(cmd,encode(args));
+    if (callback) call_out(callback,0,1);
+}
+
+int(1..1) sync(object con,mixed ...args)
+{ 
+   con->transmit_noreply(cmd,encode(args));
+   return 1;
+}
+
+string encode(array args)
+{
+    %encode%
+}
+
+mixed decode_answer(string s)
+{
+    return 1;
+}
+";
+
+   string cmd;
+   program p;
+   
+   void create(string _cmd,string ...args)
+   {
+      source=replace(source,"%cmd%",cmd=_cmd);
+      array format=({});
+      int i=0;
+      foreach (args,string type)
+      {
+	 switch (type)
+	 {
+	    case "string": format+=({"%s"}); break;
+	    case "text":   format+=({":%s"}); break;
+	    default: Error.internal("didn't expect type %O",type);
+	 }
+      }
+      source=replace(source,"%encode%",
+		     "return sprintf(\""
+		     +format*" "+"\",@args);");
+   }
+
+   object `()(mixed ... args)
+   {
+      if (!p) 
+      { 
+	 p=compile_string(source,"NoReply."+cmd); 
+	 source=0; 
+      }
+      return p(@args);
+   }
+}
+
+
+object pass=NoReply("PASS","string");
+object nick=NoReply("NICK","string");
+object user=NoReply("USER","string","string","string","text");
+object pong=NoReply("PONG","text");
+object ping=NoReply("PING","text");
+object privmsg=NoReply("PRIVMSG","string","text");
+object join=NoReply("JOIN","string");
+object names=NoReply("NAMES","string");
+
-- 
GitLab