{*********************************************************

 SlavaNap source code.

 Copyright 2001,2002 by SlavaNap development team
 Released under GNU General Public License

 Latest version is available at
 http://www.slavanap.org

**********************************************************

 Unit: channels

 Class for channel

*********************************************************}
unit channels;

interface

uses
 Windows, Classes2, SysUtils, constants, stypes, blcksock, synsock, winsock, share,
 servers, users, localusers, registered, slavastrings, class_cmdexlist;

type
 TChannelState = set of (chRegistered, chPrivate, chModerated, chTopic);
 TChannel = class(TObject)
  channel            : String;
  topic              : String;
  limit              : Integer;
  level              : TNapUserLevel;
  users              : TMyList;
  ops                : TStringHash;
  voices             : TStringHash;
  bans               : TStringHash;
  motd               : TStringHash;
  history            : TNapCmdExList;
  state              : TChannelState;
  constructor Create(name: String);
  destructor Destroy; override;
  procedure Join(user: POnlineUser);
  procedure Part(user: POnlineUser);
  procedure SetTopic(str: String);
  function FindUser(user: POnlineUser): Integer;
  function Visible(user: POnlineUser): Boolean;
  function Operator(user: POnlineUser): Boolean;
  procedure WriteChannelServers(id: Integer; cmd: String);
  procedure Wallop(str: String);
  procedure DefaultMotd;
  function Banned(user,ip: String): Boolean;
 end;

procedure LoadChannels(filename: String);
procedure SaveChannels(filename: String);
function FindChannel(channel: String): TChannel;
procedure CheckChannels(user: POnlineUser);

implementation

{$I defines.pas}

uses
 lang, vars, handler, bans, memory_manager;

constructor TChannel.Create(name: String);
begin
 inherited Create;
 channel:=name;
 topic:=Format(STR_DEFAULTTOPIC,[name]);
 limit:=DEF_CHANNEL_LIMIT;
 level:=napUseruser;
 users:=CreateList;
 StrHash_Reset(ops);
 StrHash_Reset(voices);
 StrHash_Reset(bans);
 StrHash_Reset(motd);
 DefaultMotd;
 history:=CreateCmdExList;
 state:=[];
end;

destructor TChannel.Destroy;
begin
 FreeList(users);
 StrHash_Clear(ops);
 StrHash_Clear(voices);
 StrHash_Clear(bans);
 StrHash_Clear(motd);
 FreeCmdExList(history);
 inherited Destroy;
end;

procedure TChannel.Join(user: POnlineUser);
var
 i: Integer;
 usr: POnlineUser;
 all: Boolean;
 p: PStringHashItem;
begin
 if user=nil then exit;
 if FindUser(user)<>-1 then exit; // already in
 if user^.server=nil then
  WriteAllServers(MSG_CLIENT_JOIN,user^.username,channel);
 all:=Visible(user);
 for i:=0 to users.count-1 do
 begin
  usr:=users.Items[i];
  if usr^.server=nil then
   if all or (usr^.level>napUserUser) then
    Exec(usr,MSG_SERVER_JOIN,channel+' '+user^.username+' '+IntToStr(user^.shared)+' '+IntToStr(Ord(user^.speed)));
 end;
 users.Add(user);
 if user^.server=nil then
 begin
   Exec(user,MSG_SERVER_JOIN_ACK,channel);
   for i:=0 to users.count-1 do
   begin
     usr:=users.Items[i];
     if (user^.level>napUserUser) or Visible(usr) then
      Exec(user,MSG_SERVER_CHANNEL_USER_LIST,channel+' '+usr^.username+' '+IntToStr(usr^.shared)+' '+IntToStr(Ord(usr^.speed)));
   end;
   Exec(user,MSG_SERVER_CHANNEL_USER_LIST_END,channel);
   Exec(user,MSG_SERVER_TOPIC,channel+' '+topic);
   p:=motd.first;
   while p<>nil do
   begin
     if Length(p^.data)<1 then
      Exec(user,MSG_SERVER_EMOTE,channel+' Server ""')
     else
      if p^.data[1]<>';' then
       Exec(user,MSG_SERVER_EMOTE,channel+' Server "'+FormatString(user,p^.data,true)+'"');
     p:=p^.next;  
   end;
   if chModerated in state then // channel is moderated
     Exec(user,MSG_SERVER_PUBLIC,channel+' Server '+GetLangT(LNG_CHANNELMODERATED,channel));
   if Operator(user) then // inform that he is operator
    if (user^.level<napUserModerator) or show_operators then
     Exec(user,MSG_SERVER_PUBLIC,channel+' Server '+GetLangT(LNG_CHANNELOPERATOR,serveralias,channel));
 end;
 if Operator(user) then // tell everyone that user is operator
  if (user^.level<napUserModerator) or show_operators then
  for i:=0 to users.count-1 do
  begin
    usr:=users.Items[i];
    if usr<>user then
    if usr^.server=nil then
//     if (usr^.level>napUserUser) or all then
      if Operator(usr) then
       Exec(usr,MSG_SERVER_PUBLIC,channel+' Server '+GetLangT(LNG_CHANNELOPERATOR2,GetServerAlias(user^.server),user^.username,channel));
  end;
 if user^.level=napUserUser then
 begin
  if Operator(user) then
  begin
    if user^.server=nil then WriteAllServers(MSG_SRV_OP,'',channel+' '+user^.username+' '+IntToStr(myserverhandle)+' 1 0')
    else user^.server.Exec(MSG_SRV_OP,channel+' '+user^.username+' '+IntToStr(myserverhandle)+' 1 1');
  end;
  if chModerated in state then
   if StrHash_FindString(voices,user^.username,true) then
   begin
     if user^.server=nil then WriteAllServers(MSG_SRV_VOICE,'',channel+' '+user^.username+' '+IntToStr(myserverhandle)+' 1 0')
     else user^.server.Exec(MSG_SRV_VOICE,channel+' '+user^.username+' '+IntToStr(myserverhandle)+' 1 1');
   end;
 end;
 CheckChannels(user);
end;

procedure TChannel.Part(user: POnlineUser);
var
 i: Integer;
 all: Boolean;
 usr: POnlineUser;
begin
 if user=nil then exit;
 i:=FindUser(user);
 if i=-1 then exit; // not in channel
 users.Delete(i);
 all:=Visible(user);
 if user^.server=nil then Exec(user,MSG_CLIENT_PART,channel);
 for i:=0 to users.count-1 do
 begin
  usr:=users.Items[i];
  if usr^.server=nil then
   if all or (usr^.level>napUserUser) then
    Exec(usr,MSG_SERVER_PART,channel+' '+user^.username+' '+IntToStr(user^.shared)+' '+IntToStr(Ord(user^.speed)));
 end;
 if user^.server=nil then
  WriteAllServers(MSG_CLIENT_PART,user^.username,channel);
 CheckChannels(user); 
end;

procedure TChannel.WriteChannelServers(id: Integer; cmd: String);
var
 list: TMyList;
 i: Integer;
 usr: POnlineUser;
begin // send message only to servers that active in this channel
 list:=CreateList;
 for i:=0 to users.count-1 do
 begin
  usr:=users.Items[i];
  if usr^.server<>nil then
   if list.IndexOf(usr^.server)=-1 then
    list.Add(usr^.server);
 end;
 if list.count>1 then
  WriteAllServers(id,'',cmd)
 else
  for i:=0 to list.count-1 do
   TServer(list.Items[i]).Exec(id,cmd);
 FreeList(list);
end;

procedure TChannel.SetTopic(str: String);
var
 i: Integer;
 user: POnlineUser;
begin
 if str='' then exit;
 if topic=str then exit;
 if not allow_rtf_code and (Pos('{\rtf', str) > 0) then Exit;
 topic:=str;
 for i:=0 to users.count-1 do
 begin
   user:=users.Items[i];
   if user^.server=nil then
    Exec(user,MSG_SERVER_TOPIC,channel+' '+topic);
 end;
// if all_servers then
//  WriteAllServers(MSG_SERVER_TOPIC,'',channel+' '+topic);
end;

function TChannel.Operator(user: POnlineUser): Boolean;
begin
 if user^.level>napUserUser then
 begin
  if user^.level<level then Result:=false
  else Result:=true;
 end
 else
  Result:=StrHash_FindString(ops,user^.username,true);
end;

function TChannel.Visible(user: POnlineUser): Boolean;
begin
 Result:=not (userCloaked in user^.state); // and (user^.level>napUserUser));
end;

function TChannel.FindUser(user: POnlineUser): Integer;
var
 i: Integer;
begin
 for i:=0 to users.count-1 do
  if users.Items[i]=user then
  begin
    Result:=i;
    exit;
  end;
 Result:=-1; 
end;

procedure TChannel.Wallop(str: String);
var
 i: Integer;
 user: POnlineUser;
begin
 for i:=0 to users.count-1 do
 begin
   user:=users.Items[i];
   if Operator(user) then
    Exec(user,MSG_SERVER_PUBLIC,channel+' Server '+str);
 end;
end;

procedure TChannel.DefaultMotd;
begin
 StrHash_Clear(motd);
 StrHash_AddEx(motd,'; ');
 StrHash_AddEx(motd,'; これは'+channel+'チャンネルのmotdです。');
 StrHash_AddEx(motd,'; 形式はmotdファイルと同じです。');
 StrHash_AddEx(motd,'; 二重引用符は使えません。');
 StrHash_AddEx(motd,'; ');
 StrHash_AddEx(motd,channel+'チャンネルへようこそ！');
 StrHash_AddEx(motd,'">help"で内部コマンドの一覧が見れます。');
end;

function TChannel.Banned(user,ip: String): Boolean;
var
 item: PStringHashItem;
 str,str1,str2: String;
begin
 item:=bans.first;
 user:=AnsiLowerCase(user);
 ip:=lowercase(ip);
 while item<>nil do
 begin
   str:=item^.data;
   item:=item^.next;
   SplitBan(str,str1,str2);
   if (str1<>'*') and (user<>'*') then
    if MatchesMaskEx(user,str1) then
    begin
      Result:=true;
      exit;
    end;
   if (str2<>'*') and (ip<>'*') then
    if MatchesMaskEx(ip,str2) then
    begin
      Result:=true;
      exit;
    end;
 end;
 Result:=false;
end;

procedure LoadChannels(filename: String);
var
 ch: TChannel;
 i,j: Integer;
 st: TChannelState;
 list,lst,lst2: TMyStringList;
begin
 list:=CreateStringList;
 try
  list.LoadFromFile(filename);
  except
   FreeStringList(list);
   exit;
 end;
 lst:=CreateStringList;
 lst2:=CreateStringList;
 for i:=0 to list.count-1 do
  begin
   SplitStringOld(list.Strings[i],lst);
   if lst.Count>6 then
   if lst.Strings[0]<>';' then // 1.0.0, 2.0.0 - ...
   begin
     ch:=TChannel.Create(channelname(lst.Strings[0]));
     if not StrHash_LoadFromFile(ch.motd,ApplicationDir+'chmotd.'+ch.channel) then
      ch.DefaultMotd;
     ch.topic:=lst.Strings[1];
     ch.limit:=StrToIntDef(lst.Strings[2],DEF_CHANNEL_LIMIT);
     if StrToIntDef(lst.Strings[3],-1)<>-1 then
       ch.level:=TNapUserLevel(StrToIntDef(lst.Strings[3],0))
     else
       ch.level:=Str2Level(lst.Strings[3]);
     st:=[chRegistered];
     if lst.Strings[4]='1' then st:=st+[chModerated];
     if lst.Strings[5]='1' then st:=st+[chPrivate];
     if lst.Strings[6]='1' then st:=st+[chTopic];
     ch.state:=st;
     StrHash_Clear(ch.bans);
     if lst.Count>7 then
     begin
       SplitString(lst.Strings[7],lst2);
       for j:=0 to lst2.Count-1 do
        StrHash_AddEx(ch.bans,CheckBan(lst2.Strings[j]));
     end;
     if lst.Count>8 then
     begin
       SplitString(lst.Strings[8],lst2);
       for j:=0 to lst2.Count-1 do
        StrHash_AddEx(ch.ops,AnsiLowerCase(lst2.Strings[j]));
     end;
     if lst.Count>9 then
     begin
       SplitString(lst.Strings[9],lst2);
       for j:=0 to lst2.Count-1 do
        StrHash_AddEx(ch.voices,AnsiLowerCase(lst2.Strings[j]));
     end;
     db_channels.Add(ch);
   end;
  end;
 FreeStringList(lst);
 FreeStringList(lst2);
 FreeStringList(list);
end;

procedure SaveChannels(filename: String);
var
 str: String;
 list: TMyStringList;
 i,j: Integer;
 ch: TChannel;
 p: PStringHashItem;
begin
 list:=CreateStringList;
 for i:=0 to db_channels.Count-1 do
 begin
   ch:=db_channels.Items[i];
   if chRegistered in ch.state then
   begin
     str:=ch.channel+' "'+ch.topic+'" '+IntToStr(ch.limit)+' '+IntToStr(Ord(ch.level))+' '+IntToStr(Ord(chModerated in ch.state))+' '+IntToStr(Ord(chPrivate in ch.state))+' '+IntToStr(Ord(chTopic in ch.state));
     // bans
     str:=str+' "';
     p:=ch.bans.first;
     while p<>nil do
     begin
       str:=str+p^.data+' ';
       p:=p^.next;
     end;
     str:=str+'"';
     // ops
     str:=str+' "';
     p:=ch.ops.first;
     while p<>nil do
     begin
       str:=str+p^.data+' ';
       p:=p^.next;
     end;
     str:=str+'"';
     // voices
     str:=str+' "';
     p:=ch.voices.first;
     while p<>nil do
     begin
       str:=str+p^.data;
       p:=p^.next;
     end;
     str:=str+'"';
     list.Add(str);
     StrHash_SaveToFile(ch.motd,ApplicationDir+'chmotd.'+ch.channel)
   end;
 end;
 list.Insert(0,'; ! このファイルは次回サーバーがシャットダウンしたとき上書きされます。');
 list.Insert(1,'; 編集するときは、サーバーが稼動していないか確認してください。');
 list.Insert(2,';');
 list.Insert(3,'; 形式: name "topic" limit level moderated private topic_changeable "bans_list" "ops_list" "voices_list"');
 list.Insert(4,';');
 try
  list.SaveToFile(filename);
  except
 end;
 FreeStringList(list);
end;

function FindChannel(channel: String): TChannel;
var
 i: Integer;
 ch: TChannel;
begin
 Result:=nil;
 if db_channels=nil then exit;
 channel:=AnsiLowerCase(channel);
 for i:=0 to db_channels.count-1 do
 begin
   ch:=db_channels.Items[i];
   if AnsiLowerCase(ch.channel)=channel then
   begin
     Result:=ch;
     exit;
   end;
 end;
end;

procedure CheckChannels(user: POnlineUser);
var
 b: Boolean;
 ch: TChannel;
 i: Integer;
begin
 b:=false;
 for i:=0 to db_channels.Count-1 do
 begin
   ch:=db_channels.Items[i];
   if ch.FindUser(user)<>-1 then b:=true;
 end;
 if b then user^.state:=user^.state+[userChatting]
 else user^.state:=user^.state-[userChatting];
end;


end.
