Tracking the opers
Scary! Tracking down the opers
Why?
Telehack most active opers, underwood and forbin seems to always active on the server. However we don’t have enough time to wait and measure their downtime, that’s why today, we are making a Python script that measures their downtime and put them onto a graph.
How?
Firstly, to determine whether an user is active or not, there’s a key named “last” in telehack/u/[user].json which indicates the last time ( in UNIX timestamp format ) that they interacted with telehack.
For example, looking at user stargraph at telehack.com/u/stargraph.json
Meaning the last time he interacted with the terminal is 1736291506 which is Tuesday, January 7, 2025 11:11:46 PM GMT+0
We first initalize our project with this simple GET request
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from datetime import datetime
import time, sys, os, json, requests
def get_last(username):
url = "http://telehack.com/u/" + username + ".json"
response = requests.get(url)
if response.status_code != 200:
err = response.status_code
if err == 404:
print(f"{err}: User not found.")
sys.exit(1)
elif err:
print(f"{err}: An error occurred.")
sys.exit(1)
data = json.loads(response.text)
return data.get('last')
With print(get_last("stargraph")), we will know that he last active at 1736302718.
Next, we will need a function to check the duration of their inactivity by subtracting the last active timestamp from the current Unix timestamp ( which we can get from time.time() )
1
2
3
4
5
6
def check_downtime(username):
last = get_last(username)
now = time.time()
downtime = now - last
print(f"Last seen: {datetime.fromtimestamp(last)}")
return downtime
Now, we need to create a loop to track them every 5 minutes
1
2
3
while True:
downtime = check_downtime("stargraph")
time.sleep(300)
However, we are not going to monitor stargraph’s actions but from underwood and forbin.
1
2
3
4
5
users = ["underwood", "forbin", "shibu"]
while True:
for user in users:
downtime = check_downtime(user)
time.sleep(300)
Now we need something to store those accquired information. Start by creating data.json and intialize it with 3 objects.
1
2
3
4
5
{
"underwood":{},
"forbin": {},
"shibu": {}
}
Finally, modify the loop to store the data to data.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
with open('data.json', 'r') as file:
data = json.load(file)
users = ["underwood", "forbin", "shibu"]
while True:
for user in users:
downtime = check_downtime(user)
current_time = int(time.time())
if user not in data:
data[user] = {}
data[user][current_time] = downtime
with open('data.json', 'w') as file:
json.dump(data, file, indent=4)
time.sleep(300)
After running for around 3 days
After running the script for more than 3 days, I downloaded the data.json. Here is the preview of the first 10 lines:
1
2
3
4
5
6
7
8
9
10
{
"underwood": {
"1736306698": 11919.034086704254,
"1736307001": 12222.364164352417,
"1736307304": 12525.448112487793,
"1736307607": 12828.796787977219,
"1736307910": 13131.876776695251,
"1736308214": 13435.224066734314,
"1736308517": 13738.308649778366,
"1736308820": 14041.673213243484,
Creating a new Python file named process.py, we import some basic packages
1
2
3
4
5
6
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import json
from datetime import datetime
from collections import defaultdict
from pytz import timezone
For simplicity, we will first start with shibu. Start by reading data.json and extracting only shibu object.
1
2
3
with open('data.json', 'r') as file:
data = json.load(file)
data_shibu = dict(data["shibu"])
Logically speaking, the X-axis should be the time and Y-axis is the value. Let’s save those values into list x and y to plot them. We have to apply int on the key because it was saved as a string in data.json
1
2
x = [datetime.fromtimestamp(int(ts)) for ts in data_shibu.keys()]
y = list(data_shibu.values())
Then, plot them onto our graph and display it.
1
2
3
4
5
6
7
8
9
plt.plot(x, y)
plt.xlabel('Time')
plt.ylabel('Last interaction')
plt.title('Shibu View')
plt.gca().xaxis.set_major_locator(mdates.HourLocator(interval=1))
plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%H:%M'))
plt.show()
We can see this nicely formatted graph being displayed.
The first thing we notice that is the labels are so densely packed together that we could not make out any time. Start by resizing the figure width into 24 inches by inserting this line into the plotting part.
1
2
plt.figure(figsize=(24, 6))
plt.plot(x, y)
The next thing we can do is instead of displaying the timeframes of all 4 days, we should split then into 4 days and save into pictures.
Go back to data_shibu = dict(data["shibu"]) and split them into days.
1
2
3
4
for ts, val in data_shibu.items():
dt = datetime.fromtimestamp(int(ts))
day = datetime(dt)
group[day]["shibu"].append((dt, val))
Now let’s modify the plotting loop
1
2
3
4
5
6
7
8
9
10
11
12
13
14
for i, (day,val) in enumerate(group.items(), start=1):
plt.figure(figsize=(24, 6))
x = [ts for ts, _ in val["shibu"]]
y = [val for _, val in val["shibu"]]
plt.plot(x, y)
plt.xlabel('Time')
plt.ylabel('Last interaction')
plt.title(f'Shibu View - Day {i}')
plt.gca().xaxis.set_major_locator(mdates.HourLocator(interval=1))
plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%H:%M'))
plt.savefig(f'Shibu_View_Day_{i}.png')
plt.close()
Let’s rerun our script!
We can immediately notice that it had created 4 different files for us, although Day1.png and Day4.png aren’t whole records due to our script weren’t started at 0:00 UTC+0 sharp.
Woohoo! Our graph is now very comprehensible and readable!
Let’s analyze the first day we recorded. Notice those peaks and valleys that I marked for you? Since we are plotting the “Last interaction” duration on the Y-axis, that means the higher the value is, the longer they afk. When they type anything to the terminal, the value immediately dropped to 0, that’s the reason behind those sharp and sudden drops.
(1), (2), (3) are the valleys of the 3 most significant peaks, which marks the end of their AFK duration.
For the sakes of simplicity, if the user stop interacting with the terminal for more than 33 minutes, they will be considered AFK.
We can easily conclude that, shibu’s AFK span on Day 1 was 12:00 - 13:00, 15:15 - 16:15 and 17:00 - 18:30
Conclusion
Opers are scary. Don’t mess with the best.




