Skip to content

Commit

Permalink
Initial
Browse files Browse the repository at this point in the history
  • Loading branch information
Al-Muhandis committed Dec 14, 2018
1 parent cfa11e9 commit 5305e3f
Show file tree
Hide file tree
Showing 9 changed files with 930 additions and 0 deletions.
37 changes: 37 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
*.log
*.ini
~*
*.zip
*.dat
*.csv
# Lazarus compiler-generated binaries (safe to delete)
*.exe
*.dll
*.so
*.dylib
*.res
*.compiled
*.dbg
*.ppu
*.o
*.or
*.a

tgshd

# Lazarus autogenerated files (duplicated info)
*.rst
*.rsj
*.lrt

# Lazarus local files (user-specific info)
*.lps

# Lazarus backups and unit output folders.
# These can be changed by user in Lazarus/project options.
backup/
*.bak
lib/

# Application bundle for Mac OS
*.app/
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,27 @@ Helps you work with your server (or remote computer) via telegram bot emulating
It is possible to work in the terminal from under any user in OS. You can configure access to several telegram users or only for You.

The program can run as a regular console program or run as a daemon/service.

Tested in Linux but You can try to use in other OS also. Developed with FreePascal. It depends on fp-telegram lib .

1. Create bot https://core.telegram.org/bots/#3-how-do-i-create-a-bot . You must select longpolling method for getting updates.
2. Create config ini file `telegram.ini` in app folder:

``` INI
[Bot]
;; Specify Your token
Token=

[API]
;; Actual if telegram server is blocked in server/computer region
Endpoint=https://api.telegram.org/bot
;; Timeout for lognpolling requests while getting updates
Timeout=20

[Users]
;; Specify all admin users (Telegram UserID [Integer]). For example:
;;123456789=a
YOUR_User_ID=a

This comment has been minimized.

Copy link
@ashumkin

ashumkin Dec 14, 2018

Why cryptic roles (a, b, s)? Is not it more intuitive to use admin, ban/banned, user?

This comment has been minimized.

Copy link
@Al-Muhandis

Al-Muhandis Dec 14, 2018

Author Owner

Maybe... I'm not sure the easier case was to do. :) Maybe I'll complicate it later.

```

3. Run as a console program or daemon/service.
108 changes: 108 additions & 0 deletions configuration.pas
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
unit configuration;

{$mode objfpc}{$H+}

interface

uses
Classes, SysUtils, IniFiles;

type
TUserStatus = (usAdmin, usSimple, usBanned); // in future it can increase enums

{ TConfig }

TConfig = class
private
FIni: TMemIniFile;
FUsers: TStringList;
function GetAPIEndPoint: String;
function GetAPITimeout: Integer;
function GetBotTooken: String;
function GetUsers(UserID: Int64): TUserStatus;
function GetUserList: TStrings;
public
constructor Create(const AConfFile: String);
destructor Destroy; override;
property APIEndPoint: String read GetAPIEndPoint;
property BotTooken: String read GetBotTooken;
property Users[UserID: Int64]: TUserStatus read GetUsers;
property APITimeout: Integer read GetAPITimeout; // longpolling timeout
end;

var
Cnfg: TConfig;

implementation

uses
tgsendertypes;

var
CnfDir: String;

{ TConfig }

function TConfig.GetAPIEndPoint: String;
begin
Result:=FIni.ReadString('API', 'Endpoint', TelegramAPI_URL);
end;

function TConfig.GetAPITimeout: Integer;
begin
Result:=FIni.ReadInteger('API', 'Timeout', 50); // 50 sec for longpolling request ??
end;

function TConfig.GetBotTooken: String;
begin
Result:=FIni.ReadString('Bot', 'Token', EmptyStr)
end;

function TConfig.GetUsers(UserID: Int64): TUserStatus;
var
s: String;
begin
s:=GetUserList.Values[IntToStr(UserID)];
if s=EmptyStr then
Exit(usSimple);
case s[1] of
'a': Result:=usAdmin;
'b': Result:=usBanned;
's': Result:=usSimple;
else
Result:=usSimple;
end;
end;

function TConfig.GetUserList: TStrings;
begin
if not Assigned(FUsers) then
begin
FUsers:=TStringList.Create;
FIni.ReadSectionRaw('users', FUsers);
FUsers.Sorted:=True;
end;
Result:=FUsers;
end;

constructor TConfig.Create(const AConfFile: String);
begin
FIni:=TMemIniFile.Create(AConfFile);
end;

destructor TConfig.Destroy;
begin
FUsers.Free;
FIni.Free;
inherited Destroy;
end;

initialization
CnfDir:=IncludeTrailingPathDelimiter(ExtractFileDir(ParamStr(0)));
Cnfg:=TConfig.Create(CnfDir+'telegram.ini');

This comment has been minimized.

Copy link
@ashumkin

ashumkin Dec 14, 2018

This prevents using several bots with a single copy of executable. I'd prefer to be able run several bots (and pass a config with a parameter, defaulted to user's config). It's a Linux way.

This comment has been minimized.

Copy link
@Al-Muhandis

Al-Muhandis Dec 14, 2018

Author Owner

I understand. Moreover, my other daemon works with 3 bots in parallel. But here I decided not to complicate. This is a demo program to start working with fp-telegram in longpolling mode


finalization
FreeAndNil(Cnfg);

end.

156 changes: 156 additions & 0 deletions shellthread.pas
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
unit shellthread;

{$mode objfpc}{$H+}

interface

uses
Classes, SysUtils, tgtypes, tgsendertypes, process, eventlog
{$IFDEF MSWINDOWS}, Windows{$ENDIF}
;

type

{ TShellThread }

TShellThread=class(TThread)
private
FLogger: TEventLog;
FBot: TTelegramSender;
FProc: TProcess;
FTerminated: Boolean;
FLPTimeout: Integer;
procedure BotReceiveMessage({%H-}ASender: TObject; AMessage: TTelegramMessageObj);
procedure BotLogMessage({%H-}ASender: TObject; EventType: TEventType; const Msg: String);
function GetLogger: TEventLog;
procedure OutputStd;
procedure SetLogger(AValue: TEventLog);
property Logger: TEventLog read GetLogger write SetLogger;
public
constructor Create;
destructor Destroy; override;
procedure Execute; override;
end;

implementation

uses
configuration, tgshbot
;

{ TShellThread }

procedure TShellThread.SetLogger(AValue: TEventLog);
begin
if FLogger=AValue then Exit;
FLogger.Free;
FLogger:=AValue;
end;

function TShellThread.GetLogger: TEventLog;
begin
if not Assigned(FLogger) then
begin
FLogger:=TEventLog.Create(nil);
FLogger.Identification:='Shell thread';
end;
Result:=FLogger;
end;

procedure TShellThread.BotReceiveMessage(ASender: TObject;
AMessage: TTelegramMessageObj);
var
InputString: String;
begin
if FBot.CurrentIsSimpleUser then
begin
FBot.sendMessage('You cannot access to this bot!');
Exit;
end;
InputString:=AMessage.Text;
if InputString=EmptyStr then
Exit;
InputString+=LineEnding;
if FProc.Running then
begin
OutputStd;
FProc.Input.Write(InputString[1], Length(InputString));
sleep(50);
OutputStd;
end;
end;

procedure TShellThread.BotLogMessage(ASender: TObject; EventType: TEventType;
const Msg: String);
begin
Logger.Log(EventType, Msg);
end;

procedure TShellThread.OutputStd;
var
CharBuffer: array [0..511] of char;
ReadCount: integer;
OutputString: String;
begin
OutputString:=EmptyStr;
while FProc.Output.NumBytesAvailable > 0 do
begin
ReadCount := FProc.Output.NumBytesAvailable;
if ReadCount>Length(CharBuffer) then
ReadCount:=Length(CharBuffer);
FProc.Output.Read(CharBuffer{%H-}, ReadCount);
OutputString+=Copy(CharBuffer, 0, ReadCount);
// Write(StdOut, OutputString); // You can uncomment for debug
end;
if OutputString<>EmptyStr then
FBot.sendMessage('```bash'+LineEnding+OutputString+LineEnding+'```', pmMarkdown);
// read stderr and write to our stderr ... crashing :((
{ while FProc.Stderr.NumBytesAvailable > 0 do
begin
ReadCount := Min(512, FProc.Stderr.NumBytesAvailable);
FProc.Stderr.Read(CharBuffer, ReadCount);
FBot.sendMessage(Copy(CharBuffer, 0, ReadCount));
// Write(StdErr, Copy(CharBuffer, 0, ReadCount));
end; }
end;

constructor TShellThread.Create;
begin
inherited Create(True);
FreeOnTerminate:=False;
TelegramAPI_URL:=Cnfg.APIEndPoint; // For Russian specific case
FBot:=TTgShBot.Create(Cnfg.BotTooken);
if FBot.Token=EmptyStr then
begin
Logger.Error('Please, specify bot token in telegram.ini!');
Exit;
end;
FBot.OnReceiveMessage:=@BotReceiveMessage;
FBot.OnLogMessage:=@BotLogMessage;
{$IFDEF MSWINDOWS}
SetConsoleOutputCP(CP_UTF8);{$ENDIF}
FProc:=TProcess.Create(nil);
FProc.Options := [poUsePipes, poStderrToOutPut];
FProc.Executable:={$IFDEF MSWINDOWS}'cmd'{$ELSE}'sh'{$ENDIF};
FProc.Execute;
sleep(50);
OutputStd;
FTerminated:=False;
FLPTimeout:=Cnfg.APITimeout;
end;

destructor TShellThread.Destroy;
begin
FreeAndNil(FProc);
FreeAndNil(FBot);
inherited Destroy;
end;

procedure TShellThread.Execute;
begin
while not Terminated do
FBot.getUpdatesEx(0, FLPTimeout);
end;

end.

Loading

0 comments on commit 5305e3f

Please sign in to comment.