Search code examples
delphiconsole-applicationtelnetindy10delphi-10.2-tokyo

How can I get my Delphi TS3 Serverquery IdTelnet to run as a console application?


I need to run my Delphi application in console mode so that I can run it on my VPS as a Wine emulated application on my Linux server so that it communicates via telnet to my Teamspeak server as serverquery. It needs to stay constantly stay connected and move players out of one channel into another channel, so passively using PHP is not an option. I know TS3 bots already exist, but none are programmed in Delphi for Teamspeak 3. The program works flawlessly in the Windows application, but just hangs in the console version.

I have indy setup IdTelnet1.ThreadedEvent := true in the datamodule which only seemed to somewhat help. I'm guessing somehow I need to talk with that thread, but not sure how.

I have tried to do it this way, but the program just hangs:

program TS3bot;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils,
  Unit1 in 'Unit1.pas' {DataModule1: TDataModule};

begin
  try
    DataModule1 := TDataModule1.Create(nil);
    try
      { TODO -oUser -cConsole Main : Insert code here }
      DataModule1.IdTelnet1.Connect;
      DataModule1.IdTelnet1.TelnetThread.Start;
      repeat
        //
      until (DataModule1.IdTelnet1.Connected = false);
      DataModule1.IdTelnet1.TelnetThread.Stop;
    except
      on E: Exception do
        Writeln(E.ClassName, ': ', E.Message);
    end;
  finally
    writeln('Program ended.');
    DataModule1.Free;
  end;
end.

UNIT1

unit Unit1;

interface

uses
  System.SysUtils, System.Classes, IdBaseComponent, IdComponent,
  IdTCPConnection, IdTCPClient, IdTelnet, IdGlobal;

type
  TDataModule1 = class(TDataModule)
    IdTelnet1: TIdTelnet;
    procedure IdTelnet1Connected(Sender: TObject);
    procedure IdTelnet1DataAvailable(Sender: TIdTelnet; const Buffer: TIdBytes);
    procedure IdTelnet1Disconnected(Sender: TObject);
    procedure DataModuleDestroy(Sender: TObject);
  private
    { Private declarations }
    procedure processCommand(Command : string);
    procedure processCommands;
    procedure InterpetBuffer(Buffer: string);
  public
    { Public declarations }
  end;

Const
  Elements = (3); //(Elements - 1)
  ListOfOnConnectCommands : array [0..Elements] of string =
  ('login serverquery password',
  'use 1',
  'clientupdate client_nickname=NickNameServer',
  'servernotifyregister event=server');

var
  DataModule1: TDataModule1;
  BufferNumber: integer = 0;
  CommandSent : boolean = false;
  CommandOK : boolean = false;
  CommandNumber : integer = 0;

implementation

{%CLASSGROUP 'System.Classes.TPersistent'}

{$R *.dfm}

procedure pSplitIT(BreakString, BaseString: string; StringList: TStrings);
var
  EndOfCurrentString: byte;
begin
  StringList.Clear;

  repeat
    EndOfCurrentString := Pos(BreakString, BaseString);

    if EndOfCurrentString = 0 then
      StringList.add(BaseString)
    else
      StringList.add(Copy(BaseString, 1, EndOfCurrentString - 1));
    BaseString := Copy(BaseString, EndOfCurrentString + length(BreakString), length(BaseString) - EndOfCurrentString);

  until EndOfCurrentString = 0;
end;

procedure TDataModule1.processCommand(Command : string);
begin
  writeln('processCommand: ' + Command);
  IdTelnet1.SendString(Command);
  IdTelnet1.SendCh(#10);
  IdTelnet1.SendCh(#13);
end;

procedure TDataModule1.processCommands;
var
  MyString: string;
begin
  if CommandNumber <= Elements then
  begin
    MyString := ListOfOnConnectCommands[CommandNumber];
    writeln('processCommands: ' + MyString);
    IdTelnet1.SendString(MyString);
    IdTelnet1.SendCh(#10);
    IdTelnet1.SendCh(#13);
    inc(CommandNumber);
    //exit;
  end;
end;

procedure TDataModule1.InterpetBuffer(Buffer: string);
var
  MyTstringlist: Tstringlist;
  MyBuffer: string;
  I: integer;
  clid: integer;
  member, legionnaire, enteredGuestChannel: boolean;
begin
  enteredGuestChannel := false;
  member := false;
  legionnaire := false;

  inc(BufferNumber);
  writeln('---------------------------------------------------------');
  writeln('IdTelnet1DataAvailable BufferNumber: ' + BufferNumber.ToString);
  writeln('---------------------------------------------------------');

  if Pos('notifycliententerview',Buffer)>0 then
    begin
      writeln('----------------------');
      writeln('EXIT notifycliententerview:');
      writeln(Buffer);

      MyTstringlist := Tstringlist.Create;
      MyBuffer := Buffer;

      pSplitIT(' ',MyBuffer,MyTstringlist);
      writeln('COUNT: ' + MyTstringlist.Count.ToString);
      for I := 0 to MyTstringlist.Count - 1 do
      begin
        writeln(MyTstringlist.Strings[I]);
        if MyTstringlist.Strings[I] = 'ctid=45' then
        begin
          // client entered GUESTS CHANNEL, see if we can move them.
          enteredGuestChannel := true;
        end;
        if Pos('client_servergroups=',MyTstringlist.Strings[I]) > 0 then
        begin
          if Pos('18',MyTstringlist.Strings[I]) > 0 then
          begin
            member := true;
          end;
          if Pos('19',MyTstringlist.Strings[I]) > 0 then
          begin
            legionnaire := true;
          end;
          if Pos('28',MyTstringlist.Strings[I]) > 0 then
          begin
            legionnaire := true;
          end;
        end;
        //clid
        if Pos('clid=',MyTstringlist.Strings[I]) > 0 then
        begin
          clid := StrToInt(copy(MyTstringlist.Strings[I],6,high(MyTstringlist.Strings[I])));
        end;

      end;

      writeln('----------------------');

      MyTstringlist.Free;

      if ( (enteredGuestChannel = true)
        and (member = true) ) then
        begin
          processCommand('clientmove clid=' + clid.ToString + ' cid=47');
        end
      else if ( (enteredGuestChannel = true)
        and (legionnaire = true) ) then
        begin
          processCommand('clientmove clid=' + clid.ToString + ' cid=47');
        end;

      exit;
    end;

  // Create Returns in terminal
  if Pos(#10#13,Buffer)>0 then
    begin
      MyTstringlist := Tstringlist.Create;
      pSplitIT(#10#13,Buffer,MyTstringlist);
      writeln('COUNT: ' + MyTstringlist.Count.ToString);
      for I := 0 to MyTstringlist.Count - 1 do
      begin
        writeln(MyTstringlist.Strings[I]);
      end;
      MyTstringlist.Free;
    end
    else
    begin
      writeln(Buffer);
    end;

  if Pos('error id=0 msg=ok',Buffer)>0 then
    begin
      processCommands;
    end;

  writeln('');
end;

procedure TDataModule1.DataModuleDestroy(Sender: TObject);
begin
  processCommand('logout');
  processCommand('quit');
  IdTelnet1.Disconnect(true);
end;

procedure TDataModule1.IdTelnet1Connected(Sender: TObject);
begin
  writeln('IdTelnet1Connected');
//  sleep(5000);
  //writeln('processCommands:');
//  processCommands;
end;

procedure TDataModule1.IdTelnet1DataAvailable(Sender: TIdTelnet;
  const Buffer: TIdBytes);
begin
  InterpetBuffer(bytestostring(Buffer));
end;

procedure TDataModule1.IdTelnet1Disconnected(Sender: TObject);
begin
  writeln('IdTelnet1Disconnected');
end;

end.

Here is my original code from TForm:

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, IdBaseComponent,
  IdComponent, IdTCPConnection, IdTCPClient, IdTelnet, IdGlobal, Vcl.ExtCtrls;

type
  TForm1 = class(TForm)
    Memo1: TMemo;
    Edit1: TEdit;
    IdTelnet1: TIdTelnet;
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    Memo2: TMemo;
    Timer1: TTimer;
    Button4: TButton;
    IdSchedulerOfThreadDefault1: TIdSchedulerOfThreadDefault;
    procedure Button1Click(Sender: TObject);
    procedure IdTelnet1Disconnected(Sender: TObject);
    procedure IdTelnet1DataAvailable(Sender: TIdTelnet; const Buffer: TIdBytes);
    procedure Button3Click(Sender: TObject);
    procedure IdTelnet1Status(ASender: TObject; const AStatus: TIdStatus;
      const AStatusText: string);
    procedure IdTelnet1Connected(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
    procedure Button4Click(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
    procedure InterpetBuffer(buffer: string);
    procedure processCommands;
    procedure processCommand(Command : string);
  public
    { Public declarations }
  end;

Const
  Elements = (3); //(Elements - 1)
  ListOfOnConnectCommands : array [0..Elements] of string =
  ('login severquery password',
  'use 1',
  'clientupdate client_nickname=NickNameServer',
  'servernotifyregister event=server');

var
  Form1: TForm1;
  BufferNumber: integer = 0;
  CommandSent : boolean = false;
  CommandOK : boolean = false;
  CommandNumber : integer = 0;

implementation

{$R *.dfm}


procedure pSplitIT(BreakString, BaseString: string; StringList: TStrings);
var
  EndOfCurrentString: byte;
begin
  StringList.Clear;

  repeat
    EndOfCurrentString := Pos(BreakString, BaseString);

    if EndOfCurrentString = 0 then
      StringList.add(BaseString)
    else
      StringList.add(Copy(BaseString, 1, EndOfCurrentString - 1));
    BaseString := Copy(BaseString, EndOfCurrentString + length(BreakString), length(BaseString) - EndOfCurrentString);

  until EndOfCurrentString = 0;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  BufferNumber := 0;
  IdTelnet1.Connect;
end;

procedure TForm1.Button2Click(Sender: TObject);
var
  MyString: string;
  I: integer;
begin
  MyString := edit1.Text;
  for I := Low(MyString) to High(MyString) do
  begin
    IdTelnet1.SendCh(MyString[I]);
  end;
  IdTelnet1.SendCh(#10);
  IdTelnet1.SendCh(#13);
  edit1.Text := '';
end;

procedure TForm1.Button3Click(Sender: TObject);
begin
  processCommand('logout');
  processCommand('quit');
  IdTelnet1.Disconnect(true);
end;

procedure TForm1.Button4Click(Sender: TObject);
begin
  processCommands;
end;

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  processCommand('logout');
  processCommand('quit');

  IdTelnet1.Disconnect(true);
end;

procedure TForm1.IdTelnet1Connected(Sender: TObject);
begin
  memo1.Lines.Add('IdTelnet1Connected');
  // Wait 1 second after connected to send commands.
  Timer1.Enabled := true;
end;

procedure TForm1.InterpetBuffer(Buffer: string);
var
  MyTstringlist: Tstringlist;
  MyBuffer: string;
  I: integer;
  //ctid: integer;
  clid: integer;
  member, legionnaire, enteredGuestChannel: boolean;
begin
  inc(BufferNumber);
  memo1.Lines.Add('---------------------------------------------------------');
  memo1.Lines.Add('IdTelnet1DataAvailable BufferNumber: ' + BufferNumber.ToString);
  memo1.Lines.Add('---------------------------------------------------------');

  OutputDebugString(PChar('IdTelnet1DataAvailable BufferNumber: ' + BufferNumber.ToString));

  if Pos('notifycliententerview',Buffer)>0 then
    begin
      memo1.Lines.Add('----------------------');
      memo1.Lines.Add('EXIT notifycliententerview:');
      memo1.Lines.Add(Buffer);

      MyTstringlist := Tstringlist.Create;
      MyBuffer := Buffer;

      pSplitIT(' ',MyBuffer,MyTstringlist);
      memo1.Lines.Add('COUNT: ' + MyTstringlist.Count.ToString);
      for I := 0 to MyTstringlist.Count - 1 do
      begin
        memo1.Lines.Add(MyTstringlist.Strings[I]);
        if MyTstringlist.Strings[I] = 'ctid=45' then
        begin
          // client entered GUESTS CHANNEL, see if we can move them.
          enteredGuestChannel := true;
        end;
        if Pos('client_servergroups=',MyTstringlist.Strings[I]) > 0 then
        begin
          if Pos('18',MyTstringlist.Strings[I]) > 0 then
          begin
            member := true;
          end;
          if Pos('19',MyTstringlist.Strings[I]) > 0 then
          begin
            legionnaire := true;
          end;
          if Pos('28',MyTstringlist.Strings[I]) > 0 then
          begin
            legionnaire := true;
          end;
        end;
        //clid
        if Pos('clid=',MyTstringlist.Strings[I]) > 0 then
        begin
          clid := StrToInt(copy(MyTstringlist.Strings[I],6,high(MyTstringlist.Strings[I])));
        end;

      end;

      memo1.Lines.Add('----------------------');

      MyTstringlist.Free;

      if ( (enteredGuestChannel = true)
        and (member = true) ) then
        begin
          processCommand('clientmove clid=' + clid.ToString + ' cid=47');
        end
      else if ( (enteredGuestChannel = true)
        and (legionnaire = true) ) then
        begin
          processCommand('clientmove clid=' + clid.ToString + ' cid=47');
        end;

      exit;
    end;

  // Create Returns in terminal
  if Pos(#10#13,Buffer)>0 then
    begin
      MyTstringlist := Tstringlist.Create;
      pSplitIT(#10#13,Buffer,MyTstringlist);
      memo1.Lines.Add('COUNT: ' + MyTstringlist.Count.ToString);
      for I := 0 to MyTstringlist.Count - 1 do
      begin
        memo1.Lines.Add(MyTstringlist.Strings[I]);
      end;
      MyTstringlist.Free;
    end
    else
    begin
      memo1.Lines.Add(Buffer);
    end;

  if Pos('error id=0 msg=ok',Buffer)>0 then
    begin
      processCommands;
    end;

  memo1.Lines.Add('');
end;

procedure TForm1.processCommand(Command : string);
begin
  memo2.Lines.Add('processCommand: ' + Command);
  IdTelnet1.SendString(Command);
  IdTelnet1.SendCh(#10);
  IdTelnet1.SendCh(#13);
end;

procedure TForm1.processCommands;
var
  MyString: string;
begin
  if CommandNumber <= Elements then
  begin
    MyString := ListOfOnConnectCommands[CommandNumber];
    memo2.Lines.Add('processCommands: ' + MyString);
    IdTelnet1.SendString(MyString);
    IdTelnet1.SendCh(#10);
    IdTelnet1.SendCh(#13);
    inc(CommandNumber);
  end;
end;

procedure TForm1.Timer1Timer(Sender: TObject);
begin
  if IdTelnet1.Connected = false then
  begin
    memo2.Lines.Add('NOT CONNECTED YET...WAITING TO SEND COMMANDS.');
    exit;
  end;

  processCommands;
  Timer1.Enabled := false;
end;

procedure TForm1.IdTelnet1DataAvailable(Sender: TIdTelnet;
  const Buffer: TIdBytes);
begin
  InterpetBuffer(bytestostring(Buffer));
end;

procedure TForm1.IdTelnet1Disconnected(Sender: TObject);
begin
  memo1.Lines.Add('IdTelnet1Disconnected');
end;

procedure TForm1.IdTelnet1Status(ASender: TObject; const AStatus: TIdStatus;
  const AStatusText: string);
begin
  memo1.Lines.Add('AStatusText: ' + AStatusText);
end;

end.

I expect the program to run in console just like the GUI windows version of itself, but I'm not exactly sure how to go about this with indy idTelnet. I have mainly converted it over to the best of my knowledge and what I could find on the internet (which isn't much). Somehow I need to figure out what is causing it to hang instead of processing telnet messages?


Solution

  • Okay, I did much research and found some code to help me with the console application.

    I know Remy Lebeau said that Teamspeak doesn't use telnet protocol, but I used it and it works just fine.

    The credit has to go to Tony Caduto on this webpage: http://www.44342.com/delphi-f1279-t5081-p1.htm follow the link and go to the bottom of the page. Search for the last reply by Tony Caduto.

    This code works now!

    Now I can run this on my VPS running linux via WINE! I just don't have the money to buy the more expensive version of Delphi that would run it on linux. I am going to wait until they release a version for small developers like myself that will allow us to run delphi applications on linux. Until then, WINE is the way.

    edit: make sure you put IdTelnet1.TelnetThread.Loop := true; after IdTelnet1.Connect; or you get an error.

    Here is the code:

    program TS3bot;
    
    {$APPTYPE CONSOLE}
    
    {$R *.res}
    
    uses
      System.SysUtils,
      Unit1 in 'Unit1.pas' {DataModule1: TDataModule};
    
    begin
      try
        DataModule1 := TDataModule1.Create(nil);
        with DataModule1 do
        try
          { TODO -oUser -cConsole Main : Insert code here }
          IdTelnet1.ThreadedEvent := true;
          IdTelnet1.Connect;
          IdTelnet1.TelnetThread.Loop := true;
          while (IdTelnet1.Connected = true) do
          begin
            Sleep(60);
          end;
        except
          on E: Exception do
            Writeln(E.ClassName, ': ', E.Message);
        end;
      finally
        writeln('Program ended.');
        freeandnil(DataModule1);
      end;
    end.
    

    Here is the other part just in case it changed:

    unit Unit1;
    
    interface
    
    uses
      System.SysUtils, System.Classes, IdBaseComponent, IdComponent,
      IdTCPConnection, IdTCPClient, IdTelnet, IdGlobal;
    
    type
      TDataModule1 = class(TDataModule)
        IdTelnet1: TIdTelnet;
        procedure IdTelnet1Connected(Sender: TObject);
        procedure IdTelnet1DataAvailable(Sender: TIdTelnet; const Buffer: TIdBytes);
        procedure IdTelnet1Disconnected(Sender: TObject);
        procedure DataModuleDestroy(Sender: TObject);
      private
        { Private declarations }
        procedure processCommand(Command : string);
        procedure processCommands;
        procedure InterpetBuffer(Buffer: string);
      public
        { Public declarations }
      end;
    
    Const
      Elements = (3); //(Elements - 1)
      ListOfOnConnectCommands : array [0..Elements] of string =
      ('login serverquery mypassword',
      'use 1',
      'clientupdate client_nickname=ServerNickName',
      'servernotifyregister event=server');
    
    var
      DataModule1: TDataModule1;
      BufferNumber: integer = 0;
      CommandSent : boolean = false;
      CommandOK : boolean = false;
      CommandNumber : integer = 0;
    
    implementation
    
    {%CLASSGROUP 'System.Classes.TPersistent'}
    
    {$R *.dfm}
    
    procedure pSplitIT(BreakString, BaseString: string; StringList: TStrings);
    var
      EndOfCurrentString: byte;
    begin
      StringList.Clear;
    
      repeat
        EndOfCurrentString := Pos(BreakString, BaseString);
    
        if EndOfCurrentString = 0 then
          StringList.add(BaseString)
        else
          StringList.add(Copy(BaseString, 1, EndOfCurrentString - 1));
        BaseString := Copy(BaseString, EndOfCurrentString + length(BreakString), length(BaseString) - EndOfCurrentString);
    
      until EndOfCurrentString = 0;
    end;
    
    procedure TDataModule1.processCommand(Command : string);
    begin
      writeln('processCommand: ' + Command);
      IdTelnet1.SendString(Command);
      IdTelnet1.SendCh(#10);
      IdTelnet1.SendCh(#13);
    end;
    
    procedure TDataModule1.processCommands;
    var
      MyString: string;
    begin
      if CommandNumber <= Elements then
      begin
        MyString := ListOfOnConnectCommands[CommandNumber];
        writeln('processCommands: ' + MyString);
        IdTelnet1.SendString(MyString);
        IdTelnet1.SendCh(#10);
        IdTelnet1.SendCh(#13);
        inc(CommandNumber);
        //exit;
      end;
    end;
    
    procedure TDataModule1.InterpetBuffer(Buffer: string);
    var
      MyTstringlist: Tstringlist;
      MyBuffer: string;
      I: integer;
      clid: integer;
      member, legionnaire, enteredGuestChannel: boolean;
    begin
      enteredGuestChannel := false;
      member := false;
      legionnaire := false;
    
      inc(BufferNumber);
      writeln('---------------------------------------------------------');
      writeln('IdTelnet1DataAvailable BufferNumber: ' + BufferNumber.ToString);
      writeln('---------------------------------------------------------');
    
      if Pos('notifycliententerview',Buffer)>0 then
        begin
          writeln('----------------------');
          writeln('EXIT notifycliententerview:');
          writeln(Buffer);
    
          MyTstringlist := Tstringlist.Create;
          MyBuffer := Buffer;
    
          pSplitIT(' ',MyBuffer,MyTstringlist);
          writeln('COUNT: ' + MyTstringlist.Count.ToString);
          for I := 0 to MyTstringlist.Count - 1 do
          begin
            writeln(MyTstringlist.Strings[I]);
            if MyTstringlist.Strings[I] = 'ctid=45' then
            begin
              // client entered GUESTS CHANNEL, see if we can move them.
              enteredGuestChannel := true;
            end;
            if Pos('client_servergroups=',MyTstringlist.Strings[I]) > 0 then
            begin
              if Pos('18',MyTstringlist.Strings[I]) > 0 then
              begin
                member := true;
              end;
              if Pos('19',MyTstringlist.Strings[I]) > 0 then
              begin
                legionnaire := true;
              end;
              if Pos('28',MyTstringlist.Strings[I]) > 0 then
              begin
                legionnaire := true;
              end;
            end;
            //clid
            if Pos('clid=',MyTstringlist.Strings[I]) > 0 then
            begin
              clid := StrToInt(copy(MyTstringlist.Strings[I],6,high(MyTstringlist.Strings[I])));
            end;
    
          end;
    
          writeln('----------------------');
    
          MyTstringlist.Free;
    
          if ( (enteredGuestChannel = true)
            and (member = true) ) then
            begin
              processCommand('clientmove clid=' + clid.ToString + ' cid=47');
            end
          else if ( (enteredGuestChannel = true)
            and (legionnaire = true) ) then
            begin
              processCommand('clientmove clid=' + clid.ToString + ' cid=47');
            end;
    
          exit;
        end;
    
      // Create Returns in terminal
      if Pos(#10#13,Buffer)>0 then
        begin
          MyTstringlist := Tstringlist.Create;
          pSplitIT(#10#13,Buffer,MyTstringlist);
          writeln('COUNT: ' + MyTstringlist.Count.ToString);
          for I := 0 to MyTstringlist.Count - 1 do
          begin
            writeln(MyTstringlist.Strings[I]);
          end;
          MyTstringlist.Free;
        end
        else
        begin
          writeln(Buffer);
        end;
    
      if Pos('error id=0 msg=ok',Buffer)>0 then
        begin
          processCommands;
        end;
    
      writeln('');
    end;
    
    procedure TDataModule1.DataModuleDestroy(Sender: TObject);
    begin
      processCommand('logout');
      processCommand('quit');
      IdTelnet1.Disconnect(true);
    end;
    
    procedure TDataModule1.IdTelnet1Connected(Sender: TObject);
    begin
      writeln('IdTelnet1Connected');
      sleep(5000);
      writeln('processCommands:');
      processCommands;
    end;
    
    procedure TDataModule1.IdTelnet1DataAvailable(Sender: TIdTelnet;
      const Buffer: TIdBytes);
    begin
      InterpetBuffer(bytestostring(Buffer));
    end;
    
    procedure TDataModule1.IdTelnet1Disconnected(Sender: TObject);
    begin
      writeln('IdTelnet1Disconnected');
    end;
    
    end.