SRL Archive - Archive

Emarian - SRL Member Application


Emarian

2021-01-18 10:44:18

How Long have You Played Runescape?
10 Year(s)


Are you affilated in anyway with Jagex LTD?
No

Were you Referred to SRL?



What is your general programming knowledge?
Not much most everything I know, I learned here on villavu


Are you a member of any other communities?
No. This is the only forum I've posted to in 10 years


Tell us about yourself…
From: Emarian
I don't play often because I don't have much time in real life. I also don't work with computers in real life so this stuff is hard for me.

I quit for about 10 years but I've come back recently and I've been trying to learn. SRL is the only community that's open source and public. Kinda dead nowadays, but maybe we can change that?

I don't know much, but I'm trying. I just want to help make this site great again



Please show us one (or multiple) of your scripts.
I posted up most of what I have. I didn't post this one because it got me banned but this is the first complete one I wrote recently. I've learned alot more since, so I've been meaning to rewrite it and try again, but this one got to over 90 magic and smithing from a lvl3 before it got the hammer.


Below that is a basic NMZ script I wrote. I thought about submitting the mouseoverprayer function to the github. I asked about that in the discord!!

That's someone else's drink potion procedure. I was gonna write my own but I found that snippet that I had saved from somewhere and it was just too perfect. I also use slacky's progress report because his scripts are what made things finally start clicking for me. So for me seeing his progress report makes me happy


[SIMBA]program new;
{$define SMART} // need to adjust rsw and fill dtm
{$I SRL/OSR.simba}
{$I RSWalker/Walker.simba}

const
LOGIN_NAME = '';
LOGIN_PASS = '';
RS_WORLD = -1;
IS_MEMBER = true;

var
HeatSlot: Integer;
TimesDone: Integer;
TimesHeated: Integer;
MagicExp: Integer;
MiningExp: Integer;
SmithExp: Extended;
OreCounter: Integer;
OreCountExp: Integer;
RSW: TRSWalker;

var
GoldOre := DTMFromString('');

procedure declarePlayers();
begin
with Players.New()^ do
begin
LoginName := LOGIN_NAME;
Password := LOGIN_PASS;
IsActive := True;
IsMember := IS_MEMBER;
World := RS_WORLD;
end;
Players.SetCurrent(0);
end;

procedure goldWaitLoop();
var
InvCount, InvCount2 : Integer;
T: TTimeMarker;
begin
T.Start;
InvCount := Inventory.Count(GoldOre);
repeat
WaitEx(120,25);
InvCount2 := Inventory.Count(GoldOre);
if chatbox.FindTextOnLines(['here is currently'], [7]) then
Break;
until((InvCount2 > InvCount) or (T.GetTime > RandomRange(50000,100000)));
end;

procedure spellWaitLoop();
var
T: TTimeMarker;
begin
T.Start;
repeat
WaitEx(120,25);
until((Spells.IsOpen = true)) or (T.GetTime > RandomRange(5000,8000));
if not Spells.IsOpen then
Spells.Open;
end;

procedure invWaitLoop();
var
T: TTimeMarker;
begin
T.Start;
repeat
WaitEx(120,25);
until((Inventory.IsOpen = true)) or (T.GetTime > RandomRange(5000,8000));
if not Inventory.IsOpen then
Inventory.Open;
end;

procedure checkSpell();
var
T: TTimeMarker;
begin
T.Start;
if not MainScreen.IsUpText('uperheat') then
repeat
if not Spells.IsOpen then
Spells.Open;
Mouse.Move(IntToBox(620, 330, 720, 230));
WaitEx(120,25);
Spells.Cast(CAST_SUPERHEAT_ITEM);
until((MainScreen.IsUpText('uperheat') = true)) or (T.GetTime > RandomRange(20000,25000));
if not MainScreen.IsUpText('uperheat') then
begin
if not Spells.IsOpen then
Spells.Open;
WaitEx(120,25);
Spells.Cast(CAST_SUPERHEAT_ITEM);
end;
end;

procedure findGold;
var
TPA, Gold: TPointArray;
ATPA: T2DPointArray;
begin
SRL.DismissRandom();
if (SRL.FindColors(TPA, CTS2(3446440, 13, 0.02, 0.47), MainScreen.GetBounds) > 0) then
begin
ATPA := TPA.Cluster(3);
ATPA.FilterSize(550, 2100);
ATPA.SortByIndex(MainScreen.GetMiddle);
//Smart.Image.DebugATPA(ATPA);
for Gold in ATPA do
begin
WaitEx(300,150);
mouse.Move(TPA.Bounds);
//Mouse.Move(Gold[Random(Length(Gold))]);
if not MainScreen.IsUpText(['ocks']) then
Continue;
if MainScreen.IsUpText('ocks') then
begin
WaitEx(300,150);
if Mouse.Click(ctRed) then
begin
//Smart.Image.Clear;
Minimap.WaitPlayerMoving();
goldWaitLoop();
if chatbox.FindTextOnLines(['ou manage to'], [7]) then
inc(OreCounter);
Break;
end;
end;
end;
end;
//Smart.Image.Clear;
end;

procedure superheatGold;
begin
Spells.Open();
writeln('About to try and heat '+IntToStr(OreCounter)+' ores');
repeat
if TimesHeated = OreCounter then
Break;
SRL.DismissRandom();
spellWaitLoop();
Spells.Cast(CAST_SUPERHEAT_ITEM);
checkSpell();
invWaitLoop();
Inventory.MouseSlot(HeatSlot);
WaitEx(200,50);
if not MainScreen.IsUpText('old') then
begin
repeat
WaitEx(800,250);
HeatSlot := HeatSlot - 1;
Inventory.MouseSlot(HeatSlot);
WaitEx(800,250);
until (MainScreen.IsUpText('old') = true);
end;
if MainScreen.IsUpText('bar') then
begin
Mouse.Click(mouse_Left);
spellWaitLoop();
Break;
end;
if MainScreen.IsUpText(['uperheat', 'ore']) then
begin
Mouse.Click(mouse_Left);
spellWaitLoop();
Inc(TimesHeated);
end;
until(TimesHeated = 40);
Inventory.Open();
invWaitLoop();
end;

begin
//Smart.EnableDrawing := True;
SRL.Setup([]);
clearDebug();
declarePlayers();
RSW.Init('world.png');
Inventory.ShiftDrop := True;
WaitEx(750,200);
writeln('Starting GoldMHD!');
repeat
if (not SRL.isLoggedIn) then
begin
Players.LoginCurrent();
WaitEx(750,200);
MainScreen.setAngle(True);
WaitEx(750,200);
end;
if RSW.GetMyPos.DistanceTo([1802, 2416]) > 10 then
RSW.WebWalk(Point(1802, 2416),5)
repeat
if Inventory.IsFull() then
Break;
findGold(); // Look for gold rocks
until(Inventory.isFull());
HeatSlot := 24;
superheatGold(); // Start superheating process
TimesHeated := 0;
OreCountExp:= OreCountExp + OreCounter;
OreCounter := 0;
WaitEx(350,100);
Inventory.DropItemsExcept([25..27], Inventory.ErrorPattern(DROP_PATTERN_SNAKE));
Inc(TimesDone);
writeln('We have finished '+IntToStr(TimesDone)+' load(s) in '+SRL.TimeRunning());
writeln('Out of a total of '+IntToStr(OreCountExp)+' gold, we have gained:');
MagicExp := OreCountExp*53;
MiningExp := OreCountExp*65;
SmithExp := OreCountExp*56.2;
writeln(IntToStr(MagicExp)+' Magic experience');
writeln(IntToStr(MiningExp)+' Mining experience');
writeln(FloatToStr(Round(SmithExp))+' Smithing experience');
until(TimesDone = 98); // Roughly 5 hours
RSW.Free();
FreeDTMs([GoldOre]);
end.

program new;
{$I SRL/OSR.simba}
{$H-}

var
ms, StatsDebugTick: Int64;
clicks: Integer;

var
DTM_OVERLOAD := DTMFromString('mWAAAAHicY2FgYNjPxMBwEIi3AfEpID7ByMCwDoiPQbEQHx9QFSMSJgwAsvsGRA==');
DTM_ROCKCAKE := DTMFromString('mggAAAHicY2NgYHBiYmCwBWI/IPYAYgsgNgXiF0C5O0D8BIg/APFbIH4ExMWZvkCSEQNzMZADAAZMCDQ=');
DTM_ABSORPTION := DTMFromString('mWAAAAHicY2FgYNjPxMBwEIi3AfEpID7ByMCwDoiPQfG2fUeAqhjhmIuBMAAAHF4IWA==');

procedure TMouse.Click(Middle: TPoint; Radius, Button: Int32); override;
begin
if Mainscreen.IsUpText('Quick-prayers') then
Mouse.Click(mouse_Left) else
Self.Click(srl.RandomPoint(Middle, Radius), Button);
end;

function TRSMinimap.TogglePrayer(const Enable: Boolean): Boolean; override;
var t: TCountDown;
begin
if (Enable = Self.isPrayerEnabled()) then Exit(True);
with (Self.OrbInfo[Ord(MMOrbPrayer)]) do
begin
Mouse.Click(Middle, Radius, mouse_Left);
t.Init(Round(srl.TruncatedGauss(300, 1500)));
while (not t.IsFinished()) and (Self.isPrayerEnabled() <> Enable) do
WaitEx(50,10);
Result := Self.isPrayerEnabled() = Enable;
end;
end;

procedure MouseOverPrayer;
var
T: TTimeMarker;
begin
T.Start;
Mouse.Move(Point(555, 91), 13);
repeat
WaitEx(50,10);
if (T.GetTime > RandomRange(50000,60000)) then TerminateScript('Error while moving mouse to minimap prayer orb');
until (Mainscreen.IsUpText('Quick-prayers'));
end;

procedure Process();
begin

if GetTickCount() - StatsDebugTick > 1000 then
begin
ClearDebug();
ms:= GetTimeRunning;
ClearDebug();
WriteLn('+---| STATS |----------------------------------------');
WriteLn('|- Script Runtime : ', SRL.MsToTime(GetTimeRunning, Time_Short));
WriteLn('|- Clicks Performed : ', IntToStr(clicks));
WriteLn('+----------------------------------------------------');
StatsDebugTick := GetTickCount();
end;

end;

function DrinkPotion(DTM, Doses: Integer): boolean;
var
Slots: TIntegerArray;
i, c: Integer = 0;
T: TTimeMarker;
begin
Slots := Inventory.FindDTM(DTM);
if Slots.Len = 0 then
Exit(False);
for i := 0 to Doses - 1 do
begin
if Inventory.IsSlotUsed(Slots[c]) then
begin
T.Start();
repeat
Inventory.MouseSlot(Slots[c], mouse_left);
Wait(150,300, wdLeft);
until (T.GetTime > 50000) or Mainscreen.IsUpText(['Drink']);
mouse.Click(mouse_left);

Wait(150,1000, wdRight);
end
else
c += 1;
if (c - 1) > Slots.Len then
Exit(False);
end;
Result := True;
MouseOverPrayer;
end;

procedure Guzzle();
var
findcake: TIntArray;
cakeslot: Integer;
T: TTimeMarker;
begin
Wait(150,1000, wdLeft);
findcake := Inventory.FindDTM(DTM_ROCKCAKE);
if (findcake = []) then TerminateScript('Rock cake not detected');
cakeslot := findcake[0];

while (Minimap.GetHPLevel > 1) do
begin
T.Start();
Inventory.MouseSlot(cakeslot);
if Mainscreen.IsUpText('Dwarven', 1000) then
ChooseOption.Select('Guzzle') else
TerminateScript('That was not a rockcake we just found');
Wait(150,1000, wdLeft);
if (T.GetTime > 60000) then TerminateScript('Unknown error while guzzling rock cake');
end;
MouseOverPrayer;
end;

procedure ClickPrayer;
var
x, y: Integer;
begin
Minimap.TogglePrayer(True);
Minimap.TogglePrayer(False);
while findColorTolerance(x, y, 14745568, 382, 277, 510, 332, 35) do // runelite
begin
inc(clicks);
Process();
Wait(8000,54000, wdRight);
Minimap.TogglePrayer(True);
Minimap.TogglePrayer(False);

//if Minimap.GetHPLevel > 50 then DrinkPotion(DTM_OVERLOAD, 1); // overload mode
if (Minimap.GetHPLevel > 1) then Guzzle;
if findColorTolerance(x, y, 2228192, 9, 298, 43, 333, 35) then DrinkPotion(DTM_ABSORPTION, Round(srl.NormalRange(10, 14)))

end;
writeln('Runelite menu not detected');
end;

begin
srl.Setup([]);
ClearDebug;

if not Inventory.IsOpen then
Inventory.Open;

ClickPrayer;

FreeDTM(DTM_OVERLOAD);
FreeDTM(DTM_ROCKCAKE);
FreeDTM(DTM_ABSORPTION);
end.

footballjds

2021-01-18 12:04:05

standards are poor and looking at how "static" the script is i'm not surprised you were banned.

I voted yes just because…

Emarian

2021-01-18 20:15:41

Hey at least I got one vote :) already doing better than I expected. Thanks dude. I didn't expect to get accepted I just wanted to try. I wasn't even sure if anyone was gonna vote. I wish this place had a quarter of the members it used to

I just realized today that instead of a long, almost minute wait function like that, I could use a procedure that checked for overloads and absorptions every few seconds and timed out after the minute instead.

Something like this:

procedure PrayerWait;
var
T: TTimeMarker;
begin
T.Start;
repeat
WaitEx(200,50);
if Minimap.GetHPLevel > 50 then DrinkPotion(DTM_OVERLOAD, 1) else // overload mode
if (Minimap.GetHPLevel > 1) then Guzzle;
if findColorTolerance(x, y, 2228192, 9, 298, 43, 333, 35) then DrinkPotion(DTM_ABSORPTION, Round(srl.NormalRange(10, 14)));

until (T.GetTime > RandomRange(10000,60000));
end;

I'm not good at this stuff but I'm learning. The t timer isn't even right on that I just typed it up to show I can figure stuff out

Hoodz

2021-01-19 16:57:44

I voted yes, good luck with the rest of the votes :)

Emarian

2021-01-20 19:28:41

Thanks hoodz <3 I'm working on some more scripts to show soon. Much better ones using everything I've learned

KeepBotting

2021-01-25 08:23:38

Best of luck, nice to see applications getting submitted still :)

Emarian

2021-01-25 20:25:43

Man you have to got to be the LAST dude I expected to show up in here. I can hardly believe I got a yes vote from you :) thanks!! I was posting about your libframebuffer the other day

EDIT:
Whoa I see simba 1.4 in your signature. What's up with that? I need to update right?

Clarity

2021-01-26 06:46:51

Hi Emarian,
Overall, I'm happy to see you apply and show genuine interest in SRL. However, your input behavior is rather robotic and I'd expect a detectable pattern to quickly form. Static mouse movements and inconsistent standards not only come across as lazy, but also are just bad practices. Especially in an application script. This sticks out as a bad idea:

//MouseOverPrayer
mouse.move(point(555, 91), 13);
I assume you are preemptively hovering the mouse over prayer in order to decrease mouse travel time to toggle. That's a good approach, but you really should be doing something like:

//MouseOverPrayer
mouse.move(minimap.getPrayerCircle());
Now, the mouse will move to a random point in the circle.

Edit: I'm an idiot, your code here is fine although my code might be 'cleaner' (13 sets the radius for the randomized circle click point, I forgot)

It might seem like I'm nitpicking, but these behaviors can expose you. Consider as well:

//Your version: same drop pattern every time
inventory.dropItemsExcept([25..27], inventory.errorPattern(DROP_PATTERN_SNAKE));

//Better version: randomize drop pattern every time
var
i: integer;
chosenPattern: tIntegerArray;
{...}
i := random(1, 100);
case i of
1..25: chosenPattern := DROP_PATTERN_SNAKE;
26..50: chosenPattern := DROP_PATTERN_SNAKE_R;
51..75: chosenPattern := DROP_PATTERN_SPIRAL;
76..88: chosenPattern := DROP_PATTERN_TOPDOWN;
89..100: chosenPattern := DROP_PATTERN_TOPDOWN_R;
end;
inventory.dropItemsExcept([25..27], inventory.errorPattern(chosenPattern));
Your submitted scripts don't give me enough information about your scripting ability to vote, but I'd like to vote yes following your reply. Some questions:

  • Do you have any ideas for improving your script's variance in behavior, similar to the above suggestions? What might you change or add?
  • Or, if you deliberately chose to do something statically, feel free to explain why!
  • Could you explain the reasoning for your mouse clicking overrides in the NMZ script?
Hope to hear more from you and see you around! Best of luck with your application :)

Emarian

2021-01-26 20:59:06

I take my last post back. I thought you were long gone. You are definitely a true surprise to see on my thread. My story is that I quit playing over 10 years ago, but have always loved the game. I tried to come back more than once, but it never worked out. About 5 years ago, I did do it for like a month though. I never bot on that account anymore just because it's my OG from 2004, but in that month you got me a few 99s. Clarity is a name I couldn't forget. I never even knew about ironman mode till probably 3 or 4 months ago, and I've been hooked hard so I'm definitely not going anywhere this time though. I always wanted to learn SRL but never had the motivation, I'm not good at applying myself. Had to start learning for this ironman though

From: Clarity
[*]Do you have any ideas for improving your script's variance in behavior, similar to the above suggestions? What might you change or add?


I had no idea about minimap.getPrayerCircle. This was the first time I looked for something in the include and couldn't find it. Glad to know it was there all along, otherwise I wouldn't have used those static coordinates. However, I do address why I did that mouse.click override in my next two answers

As for improving it, the main way would be to clean up what I did. I'll be honest, I didn't expect my application to get a single vote much all this; I thought all you guys were long gone. If I had known you and KeepBotting would see this, I definitely wouldn't have posted it in this messy state. I've been incredibly busy at work for the past week too or I would have already cleaned it. I for real used inc(clicks) as part of my progress report

From: Clarity
[*]Or, if you deliberately chose to do something statically, feel free to explain why!


RL. Instead of trying to auto detect runelite menus, I just set that up static. For example, I wasn't too sure on the best way to detect if I was in an instance or not, so one part constantly checks for runelite's point menu and terminates if it doesn't see it. Or after you drop below the absorption threshold you set in the NMZ plugin, RL turns that icon red. That was super helpful to decide when to drink potions

As for (Point(555, 91), 13), I actually took that out of the include (SRL/OSR/interfaces/minimap.simba). In this file those coordinates are set to MMOrbPrayer and then used in Minimap.TogglePrayer like so:

    with OrbInfo[MMOrbPrayer] do
begin
Middle := [555, 91];
Radius := 13;
TextBox := [520, 89, 541, 102];
end;

  with (Self.OrbInfo[Ord(MMOrbPrayer)]) do
begin
Mouse.Click(Middle, Radius, mouse_Left);

Problem I ended up having just using this as stock is below

From: Clarity
[*]Could you explain the reasoning for your mouse clicking overrides in the NMZ script?


I set the script up to keep the mouse over the prayer icon. It only moves the mouse to drink a potion and that's all. I know if I was playing legit, I would do this with a trackball. To prayer flick in NMZ to reset auto healing or whatever you call that, you toggle rapid heal on and off. So basically I needed to use Minimap.TogglePrayer twice, but the way this function is set up doing that moves the mouse to a new coordinate inside of the circle. I just didn't see the mouse moving all over the place every single click looking human at all. I also hear of alot of people using mousekeys as well for NMZ. Most people would just double tap the left mouse button and not move it. So I wanted to be able to use Minimap.TogglePrayer as intended, but if the mouse was already over the orb then I didn't want it to move the mouse again, only left click. That's why I overrode that part of mouse.click, because I didn't want to break my include.


You guys know more about this than I do so if moving the mouse within that prayer orb on each left click wouldn't get you banned, then that mouse.click override was a waste of time. Mouseoverprayer 100% was though, I should have known there was a way to return the coordinates within the include I just couldn't figure it out that night. Script only has to move mouse every 5 minutes, so saving time doing that was not a concern. I wasn't sure if that stuff would get you banned or not, I just tried to replicate how I did it legit


I rarely have time for RS so my progress has always been incredibly slow, but I'm inspired knowing you guys are still around. I wish there were more modern scripts that are up to date that I could learn off of. That minimap.getPrayerCircle is a good example. If I had seen that I could have remembered it. I'm not too fond of how other bot platforms send fake data to the server. SRL is the only way for OSRS so I'm still praying it takes off again :)

Thanks guys. I'm very hard of hearing so I don't mess with youtube and I don't like discord. Forums were my thing and the world's moved on now, but when I think of famous OSRS names, I actually think of the big guys on here :) I never expected Clarity and KeepBotting, thats certain. I wouldn't be surprised if YoHoLo made a post now. At this point, even if my app got denied I'd still feel like a winner

Clarity

2021-01-26 23:26:44

From: Emarian
I never even knew about ironman mode till probably 3 or 4 months ago, and I've been hooked hard so I'm definitely not going anywhere this time though. I always wanted to learn SRL but never had the motivation, I'm not good at applying myself. Had to start learning for this ironman though.

Nice! Ironman brought me back to scripting RS3. Leagues brought me to scripting OSRS for the first time. Was a lot of fun, and now I have a huge OSRS script library to use on the main.

From: Emarian
RL. Instead of trying to auto detect runelite menus, I just set that up static.

Makes sense for a private script. Something we like to see in application scripts is compatibility with any user's setup, since published scripts need to account for potential differences (else it makes the user experience annoying by demanding a certain layout). My NMZ script also started out with a lot of static RL detection, but because I use different RL plugins and change them around constantly, I needed to switch to dynamic detection.

From: Emarian
As for (Point(555, 91), 13), I actually took that out of the include (SRL/OSR/interfaces/minimap.simba).

I've edited my post, my brain wasn't working. Your code is fine as you say, it essentially is a more literal version of my code but still performs randomized hovering.

From: Emarian
I just didn't see the mouse moving all over the place every single click looking human at all. I also hear of alot of people using mousekeys as well for NMZ. Most people would just double tap the left mouse button and not move it.

Thanks for explaining. If you ever have doubts on this aspect of your script, you can run a tool like Antiban Diagnostics to see whether your clicks are too robotic or obvious in some way.

Overall, I think the submitted scripts are not SRL Member quality, and years ago I would've suggested you reapply with fixed-up scripts. But I appreciate you answering my questions and explaining your thought process. I'd say someone with knowledge and a strong interest in contributing takes priority given the current state of the forum.

Voted yes :)

Hoodz

2021-01-27 14:29:19

From: Clarity




//Better version: randomize drop pattern every time
var
i: integer;
chosenPattern: tIntegerArray;
{…}
i := random(1, 100);
case i of
1..25: chosenPattern := DROP_PATTERN_SNAKE;
26..50: chosenPattern := DROP_PATTERN_SNAKE_R;
51..75: chosenPattern := DROP_PATTERN_SPIRAL;
76..88: chosenPattern := DROP_PATTERN_TOPDOWN;
89..100: chosenPattern := DROP_PATTERN_TOPDOWN_R;
end;
inventory.dropItemsExcept([25..27], inventory.errorPattern(chosenPattern));



I disagree, this is not something a real player would do (a real human is not 100% random). It would be better to change it over some long periods of time or per account.

Emarian

2021-01-27 21:15:41

I am humbled I got 6 yes votes. Thank you guys, honestly. Clarity is right those scripts should have been fixed up. Thank you guys for believing in me and voting yes anyway. I promise you I'm going to learn and post all of it publicly. SRL is the only open source community on the net for OSRS. Free as in freedom

Ashaman88

2021-01-28 08:52:06

I voted yes

Clarity

2021-01-28 10:27:21

From: Hoodz
I disagree, this is not something a real player would do (a real human is not 100% random). It would be better to change it over some long periods of time or per account.
I think there's a few schools of thought to this. Personally I long abandoned the pursuit of seeming as human as possible, in favor of reasonably breaking any form of pattern detection. Along your lines of thinking, I do, for instance, have varied mouse speeds and miss percents set only once per script run. Truthfully I find their anticheat is so terrible lately that it's all made us pretty lazy with "human-like" antiban.

Emarian

2021-01-29 23:11:38

I have to agree with @Clarity on this one just because I modify my scripts constantly and it's amazing what crap I've gotten away with. Botting two accounts at once, both ironmen, and I only quest on one. I only bot the other one in case something happened to my main ironman, and so far it seems like he's golden

slacky

2021-02-06 18:34:16

Now all that remains is to get you into members, an admin will probably drop by soon enough. I was about to vote yes, but the poll is closed… thought that counts. Hope to see you release some scripts. As for simba 1.4 youd probably want ollys in dev srl-osrs with it: https://github.com/ollydev/SRL-Development … wich will probably end up in the official repo in time.

Emarian

2021-02-07 15:21:52

Hello slacky :) I will. Thank you. With tax time coming up work has gotten crazy but it'll be slow again before too long

Justin

2021-08-03 18:12:33

Accepted, welcome!