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

 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,
  StringResources;

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, RS_Channels_CHMOTD0);
  StrHash_AddEx(Motd, Format(RS_Channels_CHMOTD1, [Channel]));
  StrHash_AddEx(Motd, RS_Channels_CHMOTD2);
  StrHash_AddEx(Motd, RS_Channels_CHMOTD3);
  StrHash_AddEx(Motd, RS_Channels_CHMOTD4);
  StrHash_AddEx(Motd, Format(RS_Channels_CHMOTD5, [Channel]));
  StrHash_AddEx(Motd, RS_Channels_CHMOTD6);
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: 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, RS_Channels_FileDescription0);
  List.Insert(1, RS_Channels_FileDescription1);
  List.Insert(2, RS_Channels_FileDescription2);
  List.Insert(3, RS_Channels_FileDescription3);
  List.Insert(4, RS_Channels_FileDescription4);
  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.
