198 lines
5.7 KiB
ObjectPascal
198 lines
5.7 KiB
ObjectPascal
program DiscordCreateThread;
|
|
{$MODE OBJFPC}
|
|
{$H+}
|
|
uses
|
|
SysUtils, Classes, fphttpclient, opensslsockets, DateUtils;
|
|
|
|
const
|
|
DISCORD_API = 'https://discord.com/api/v10';
|
|
|
|
function GetEnvDef(const Name, Def: string): string;
|
|
begin
|
|
if GetEnvironmentVariable(Name) = '' then
|
|
GetEnvDef := Def
|
|
else
|
|
GetEnvDef := GetEnvironmentVariable(Name);
|
|
end;
|
|
|
|
function GetArchiveDuration: Integer;
|
|
begin
|
|
GetArchiveDuration := StrToIntDef(GetEnvironmentVariable('ARCHIVE_DURATION'), 10080);
|
|
end;
|
|
|
|
function GetThreadName: string;
|
|
var
|
|
Base: string;
|
|
begin
|
|
Base := GetEnvironmentVariable('THREAD_NAME');
|
|
GetThreadName := Base + ' (' + FormatDateTime('yyyy-mm-dd', Now) + ')';
|
|
end;
|
|
|
|
function HttpPost(const URL, Body, Token: string): string;
|
|
var
|
|
Client: TFPHttpClient;
|
|
BodyStream, ResponseStream: TStringStream;
|
|
begin
|
|
Client := TFPHttpClient.Create(nil);
|
|
BodyStream := TStringStream.Create(Body);
|
|
ResponseStream := TStringStream.Create('');
|
|
try
|
|
Client.AddHeader('Authorization', 'Bot ' + Token);
|
|
Client.AddHeader('Content-Type', 'application/json');
|
|
Client.RequestBody := BodyStream;
|
|
Client.Post(URL, ResponseStream);
|
|
HttpPost := ResponseStream.DataString;
|
|
finally
|
|
ResponseStream.Free;
|
|
BodyStream.Free;
|
|
Client.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure RunCore;
|
|
var
|
|
Token, ChannelID, ThreadMessage: string;
|
|
ThreadBody, MessageBody, Response: string;
|
|
ThreadIDStart, ThreadIDEnd: Integer;
|
|
ThreadID: string;
|
|
begin
|
|
Token := GetEnvironmentVariable('DISCORD_BOT_TOKEN');
|
|
ChannelID := GetEnvironmentVariable('DISCORD_CHANNEL_ID');
|
|
ThreadMessage := GetEnvironmentVariable('THREAD_MESSAGE');
|
|
|
|
ThreadBody := Format(
|
|
'{"name":"%s","auto_archive_duration":%d}',
|
|
[StringReplace(GetThreadName, '"', '\"', [rfReplaceAll]), GetArchiveDuration]
|
|
);
|
|
|
|
Response := HttpPost(DISCORD_API + '/channels/' + ChannelID + '/threads', ThreadBody, Token);
|
|
|
|
ThreadIDStart := Pos('"id":"', Response);
|
|
if ThreadIDStart > 0 then
|
|
begin
|
|
Inc(ThreadIDStart, 6);
|
|
ThreadIDEnd := Pos('"', Response, ThreadIDStart);
|
|
ThreadID := Copy(Response, ThreadIDStart, ThreadIDEnd - ThreadIDStart);
|
|
|
|
if ThreadID <> '' then
|
|
begin
|
|
MessageBody := Format('{"content":"%s"}', [StringReplace(ThreadMessage, '"', '\"', [rfReplaceAll])]);
|
|
HttpPost(DISCORD_API + '/channels/' + ThreadID + '/messages', MessageBody, Token);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
// RUN_AT format: "<day_of_week>:<hour>:<minute>"
|
|
// day_of_week: 1=Sunday, 2=Monday, 3=Tuesday, 4=Wednesday, 5=Thursday, 6=Friday, 7=Saturday
|
|
function ParseRunAt(out TargetDow, TargetHour, TargetMinute: Integer): Boolean;
|
|
var
|
|
RunAt: string;
|
|
Parts: TStringList;
|
|
begin
|
|
ParseRunAt := False;
|
|
RunAt := GetEnvironmentVariable('RUN_AT');
|
|
if RunAt = '' then Exit;
|
|
|
|
Parts := TStringList.Create;
|
|
try
|
|
Parts.Delimiter := ':';
|
|
Parts.StrictDelimiter := True;
|
|
Parts.DelimitedText := RunAt;
|
|
|
|
if Parts.Count < 3 then Exit;
|
|
|
|
TargetDow := StrToIntDef(Parts[0], -1);
|
|
TargetHour := StrToIntDef(Parts[1], -1);
|
|
TargetMinute := StrToIntDef(Parts[2], -1);
|
|
|
|
if (TargetDow < 1) or (TargetDow > 7) then Exit;
|
|
if (TargetHour < 0) or (TargetHour > 23) then Exit;
|
|
if (TargetMinute < 0) or (TargetMinute > 59) then Exit;
|
|
|
|
ParseRunAt := True;
|
|
finally
|
|
Parts.Free;
|
|
end;
|
|
end;
|
|
|
|
function ShouldFire(TargetDow, TargetHour, TargetMinute: Integer): Boolean;
|
|
var
|
|
H, M, S, MS: Word;
|
|
begin
|
|
DecodeTime(SysUtils.Now, H, M, S, MS);
|
|
ShouldFire := (DayOfWeek(SysUtils.Now) = TargetDow) and
|
|
(Integer(H) = TargetHour) and
|
|
(Integer(M) = TargetMinute);
|
|
end;
|
|
|
|
const
|
|
DAY_NAMES: array[1..7] of string = (
|
|
'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'
|
|
);
|
|
|
|
var
|
|
Fired: Boolean;
|
|
LastCheckedMinute: Integer;
|
|
CurrentMinute: Integer;
|
|
H, M, S, MS: Word;
|
|
TargetDow, TargetHour, TargetMinute: Integer;
|
|
|
|
begin
|
|
if not ParseRunAt(TargetDow, TargetHour, TargetMinute) then
|
|
begin
|
|
WriteLn('Error: RUN_AT environment variable is not set or invalid.');
|
|
WriteLn('Format: RUN_AT=<day_of_week>:<hour>:<minute>');
|
|
WriteLn('Example (Tuesday 9:00): RUN_AT=3:9:0');
|
|
Halt(1);
|
|
end;
|
|
|
|
WriteLn('===========================================');
|
|
WriteLn(' Discord Thread Scheduler');
|
|
WriteLn('===========================================');
|
|
WriteLn(Format(' Started at : %s', [FormatDateTime('yyyy-mm-dd hh:nn:ss', SysUtils.Now)]));
|
|
WriteLn(Format(' Channel ID : %s', [GetEnvironmentVariable('DISCORD_CHANNEL_ID')]));
|
|
WriteLn(Format(' Thread name: %s', [GetEnvironmentVariable('THREAD_NAME')]));
|
|
WriteLn(Format(' Fires every: %s at %02d:%02d', [DAY_NAMES[TargetDow], TargetHour, TargetMinute]));
|
|
WriteLn('===========================================');
|
|
|
|
Fired := False;
|
|
LastCheckedMinute := -1;
|
|
|
|
while True do
|
|
begin
|
|
DecodeTime(SysUtils.Now, H, M, S, MS);
|
|
CurrentMinute := Integer(H) * 60 + Integer(M);
|
|
|
|
if CurrentMinute <> LastCheckedMinute then
|
|
begin
|
|
LastCheckedMinute := CurrentMinute;
|
|
|
|
WriteLn(Format('[%s] Current time: %s | Waiting for: %s %02d:%02d',
|
|
[FormatDateTime('yyyy-mm-dd hh:nn', SysUtils.Now),
|
|
FormatDateTime('ddd hh:nn', SysUtils.Now),
|
|
DAY_NAMES[TargetDow], TargetHour, TargetMinute]));
|
|
|
|
if ShouldFire(TargetDow, TargetHour, TargetMinute) then
|
|
begin
|
|
if not Fired then
|
|
begin
|
|
WriteLn(Format('[%s] Trigger matched, running core...',
|
|
[FormatDateTime('yyyy-mm-dd hh:nn', SysUtils.Now)]));
|
|
try
|
|
RunCore;
|
|
WriteLn('Core executed successfully.');
|
|
except
|
|
on E: Exception do
|
|
WriteLn('Error: ', E.Message);
|
|
end;
|
|
Fired := True;
|
|
end;
|
|
end
|
|
else
|
|
Fired := False;
|
|
end;
|
|
|
|
Sleep(10000);
|
|
end;
|
|
end.
|