mirror of
https://github.com/nexy7574/LCC-bot.git
synced 2024-09-19 18:16:34 +01:00
Uptime monitoring for jimmy
This commit is contained in:
parent
8f4f616258
commit
bb927f4d7b
6 changed files with 436 additions and 215 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -4,3 +4,4 @@ __pycache__
|
||||||
venv
|
venv
|
||||||
domains.txt
|
domains.txt
|
||||||
geckodriver.log
|
geckodriver.log
|
||||||
|
targets.json
|
||||||
|
|
|
@ -1321,254 +1321,298 @@
|
||||||
</table>
|
</table>
|
||||||
<table id="486" parent="166" name="starboard"/>
|
<table id="486" parent="166" name="starboard"/>
|
||||||
<table id="487" parent="166" name="students"/>
|
<table id="487" parent="166" name="students"/>
|
||||||
<column id="488" parent="482" name="entry_id">
|
<table id="488" parent="166" name="uptime"/>
|
||||||
|
<column id="489" parent="482" name="entry_id">
|
||||||
<DasType>INTEGER|0s</DasType>
|
<DasType>INTEGER|0s</DasType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
<Position>1</Position>
|
<Position>1</Position>
|
||||||
</column>
|
</column>
|
||||||
<column id="489" parent="482" name="created_by">
|
<column id="490" parent="482" name="created_by">
|
||||||
<DasType>CHAR(32)|0s</DasType>
|
<DasType>CHAR(32)|0s</DasType>
|
||||||
<Position>2</Position>
|
<Position>2</Position>
|
||||||
</column>
|
</column>
|
||||||
<column id="490" parent="482" name="title">
|
<column id="491" parent="482" name="title">
|
||||||
<DasType>VARCHAR(2000)|0s</DasType>
|
<DasType>VARCHAR(2000)|0s</DasType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
<Position>3</Position>
|
<Position>3</Position>
|
||||||
</column>
|
</column>
|
||||||
<column id="491" parent="482" name="classroom">
|
<column id="492" parent="482" name="classroom">
|
||||||
<DasType>VARCHAR(4096)|0s</DasType>
|
<DasType>VARCHAR(4096)|0s</DasType>
|
||||||
<Position>4</Position>
|
<Position>4</Position>
|
||||||
</column>
|
</column>
|
||||||
<column id="492" parent="482" name="shared_doc">
|
<column id="493" parent="482" name="shared_doc">
|
||||||
<DasType>VARCHAR(4096)|0s</DasType>
|
<DasType>VARCHAR(4096)|0s</DasType>
|
||||||
<Position>5</Position>
|
<Position>5</Position>
|
||||||
</column>
|
</column>
|
||||||
<column id="493" parent="482" name="created_at">
|
<column id="494" parent="482" name="created_at">
|
||||||
<DasType>FLOAT|0s</DasType>
|
<DasType>FLOAT|0s</DasType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
<Position>6</Position>
|
<Position>6</Position>
|
||||||
</column>
|
</column>
|
||||||
<column id="494" parent="482" name="due_by">
|
<column id="495" parent="482" name="due_by">
|
||||||
<DasType>FLOAT|0s</DasType>
|
<DasType>FLOAT|0s</DasType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
<Position>7</Position>
|
<Position>7</Position>
|
||||||
</column>
|
</column>
|
||||||
<column id="495" parent="482" name="tutor">
|
<column id="496" parent="482" name="tutor">
|
||||||
<DasType>VARCHAR(7)|0s</DasType>
|
<DasType>VARCHAR(7)|0s</DasType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
<Position>8</Position>
|
<Position>8</Position>
|
||||||
</column>
|
</column>
|
||||||
<column id="496" parent="482" name="reminders">
|
<column id="497" parent="482" name="reminders">
|
||||||
<DasType>JSON|0s</DasType>
|
<DasType>JSON|0s</DasType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
<Position>9</Position>
|
<Position>9</Position>
|
||||||
</column>
|
</column>
|
||||||
<column id="497" parent="482" name="finished">
|
<column id="498" parent="482" name="finished">
|
||||||
<DasType>BOOLEAN|0s</DasType>
|
<DasType>BOOLEAN|0s</DasType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
<Position>10</Position>
|
<Position>10</Position>
|
||||||
</column>
|
</column>
|
||||||
<column id="498" parent="482" name="submitted">
|
<column id="499" parent="482" name="submitted">
|
||||||
<DasType>BOOLEAN|0s</DasType>
|
<DasType>BOOLEAN|0s</DasType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
<Position>11</Position>
|
<Position>11</Position>
|
||||||
</column>
|
</column>
|
||||||
<column id="499" parent="482" name="assignees">
|
<column id="500" parent="482" name="assignees">
|
||||||
<DasType>JSON|0s</DasType>
|
<DasType>JSON|0s</DasType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
<Position>12</Position>
|
<Position>12</Position>
|
||||||
</column>
|
</column>
|
||||||
<foreign-key id="500" parent="482">
|
<foreign-key id="501" parent="482">
|
||||||
<ColNames>created_by</ColNames>
|
<ColNames>created_by</ColNames>
|
||||||
<RefColNames>entry_id</RefColNames>
|
<RefColNames>entry_id</RefColNames>
|
||||||
<RefTableName>students</RefTableName>
|
<RefTableName>students</RefTableName>
|
||||||
</foreign-key>
|
</foreign-key>
|
||||||
<key id="501" parent="482">
|
<key id="502" parent="482">
|
||||||
<ColNames>entry_id</ColNames>
|
<ColNames>entry_id</ColNames>
|
||||||
<Primary>1</Primary>
|
<Primary>1</Primary>
|
||||||
</key>
|
</key>
|
||||||
<column id="502" parent="483" name="entry_id">
|
<column id="503" parent="483" name="entry_id">
|
||||||
<DasType>CHAR(32)|0s</DasType>
|
<DasType>CHAR(32)|0s</DasType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
<Position>1</Position>
|
<Position>1</Position>
|
||||||
</column>
|
</column>
|
||||||
<column id="503" parent="483" name="student_id">
|
<column id="504" parent="483" name="student_id">
|
||||||
<DasType>VARCHAR(7)|0s</DasType>
|
<DasType>VARCHAR(7)|0s</DasType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
<Position>2</Position>
|
<Position>2</Position>
|
||||||
</column>
|
</column>
|
||||||
<column id="504" parent="483" name="associated_account">
|
<column id="505" parent="483" name="associated_account">
|
||||||
<DasType>BIGINT|0s</DasType>
|
<DasType>BIGINT|0s</DasType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
<Position>3</Position>
|
<Position>3</Position>
|
||||||
</column>
|
</column>
|
||||||
<column id="505" parent="483" name="banned_at_timestamp">
|
<column id="506" parent="483" name="banned_at_timestamp">
|
||||||
<DasType>FLOAT|0s</DasType>
|
<DasType>FLOAT|0s</DasType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
<Position>4</Position>
|
<Position>4</Position>
|
||||||
</column>
|
</column>
|
||||||
<index id="506" parent="483" name="sqlite_autoindex_banned_1">
|
<index id="507" parent="483" name="sqlite_autoindex_banned_1">
|
||||||
<ColNames>entry_id</ColNames>
|
<ColNames>entry_id</ColNames>
|
||||||
<NameSurrogate>1</NameSurrogate>
|
<NameSurrogate>1</NameSurrogate>
|
||||||
<Unique>1</Unique>
|
<Unique>1</Unique>
|
||||||
</index>
|
</index>
|
||||||
<index id="507" parent="483" name="sqlite_autoindex_banned_2">
|
<index id="508" parent="483" name="sqlite_autoindex_banned_2">
|
||||||
<ColNames>student_id</ColNames>
|
<ColNames>student_id</ColNames>
|
||||||
<NameSurrogate>1</NameSurrogate>
|
<NameSurrogate>1</NameSurrogate>
|
||||||
<Unique>1</Unique>
|
<Unique>1</Unique>
|
||||||
</index>
|
</index>
|
||||||
<key id="508" parent="483">
|
<key id="509" parent="483">
|
||||||
<ColNames>entry_id</ColNames>
|
<ColNames>entry_id</ColNames>
|
||||||
<Primary>1</Primary>
|
<Primary>1</Primary>
|
||||||
<UnderlyingIndexName>sqlite_autoindex_banned_1</UnderlyingIndexName>
|
<UnderlyingIndexName>sqlite_autoindex_banned_1</UnderlyingIndexName>
|
||||||
</key>
|
</key>
|
||||||
<key id="509" parent="483">
|
<key id="510" parent="483">
|
||||||
<ColNames>student_id</ColNames>
|
<ColNames>student_id</ColNames>
|
||||||
<UnderlyingIndexName>sqlite_autoindex_banned_2</UnderlyingIndexName>
|
<UnderlyingIndexName>sqlite_autoindex_banned_2</UnderlyingIndexName>
|
||||||
</key>
|
</key>
|
||||||
<column id="510" parent="484" name="id">
|
<column id="511" parent="484" name="id">
|
||||||
<DasType>INTEGER|0s</DasType>
|
<DasType>INTEGER|0s</DasType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
<Position>1</Position>
|
<Position>1</Position>
|
||||||
</column>
|
</column>
|
||||||
<column id="511" parent="484" name="code">
|
<column id="512" parent="484" name="code">
|
||||||
<DasType>VARCHAR(64)|0s</DasType>
|
<DasType>VARCHAR(64)|0s</DasType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
<Position>2</Position>
|
<Position>2</Position>
|
||||||
</column>
|
</column>
|
||||||
<column id="512" parent="484" name="bind">
|
<column id="513" parent="484" name="bind">
|
||||||
<DasType>BIGINT|0s</DasType>
|
<DasType>BIGINT|0s</DasType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
<Position>3</Position>
|
<Position>3</Position>
|
||||||
</column>
|
</column>
|
||||||
<column id="513" parent="484" name="student_id">
|
<column id="514" parent="484" name="student_id">
|
||||||
<DasType>VARCHAR(7)|0s</DasType>
|
<DasType>VARCHAR(7)|0s</DasType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
<Position>4</Position>
|
<Position>4</Position>
|
||||||
</column>
|
</column>
|
||||||
<column id="514" parent="484" name="name">
|
<column id="515" parent="484" name="name">
|
||||||
<DasType>VARCHAR(32)|0s</DasType>
|
<DasType>VARCHAR(32)|0s</DasType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
<Position>5</Position>
|
<Position>5</Position>
|
||||||
</column>
|
</column>
|
||||||
<index id="515" parent="484" name="sqlite_autoindex_codes_1">
|
<index id="516" parent="484" name="sqlite_autoindex_codes_1">
|
||||||
<ColNames>code</ColNames>
|
<ColNames>code</ColNames>
|
||||||
<NameSurrogate>1</NameSurrogate>
|
<NameSurrogate>1</NameSurrogate>
|
||||||
<Unique>1</Unique>
|
<Unique>1</Unique>
|
||||||
</index>
|
</index>
|
||||||
<key id="516" parent="484">
|
<key id="517" parent="484">
|
||||||
<ColNames>id</ColNames>
|
<ColNames>id</ColNames>
|
||||||
<Primary>1</Primary>
|
<Primary>1</Primary>
|
||||||
</key>
|
</key>
|
||||||
<key id="517" parent="484">
|
<key id="518" parent="484">
|
||||||
<ColNames>code</ColNames>
|
<ColNames>code</ColNames>
|
||||||
<UnderlyingIndexName>sqlite_autoindex_codes_1</UnderlyingIndexName>
|
<UnderlyingIndexName>sqlite_autoindex_codes_1</UnderlyingIndexName>
|
||||||
</key>
|
</key>
|
||||||
<column id="518" parent="485" name="type">
|
<column id="519" parent="485" name="type">
|
||||||
<DasType>TEXT|0s</DasType>
|
<DasType>TEXT|0s</DasType>
|
||||||
<Position>1</Position>
|
<Position>1</Position>
|
||||||
</column>
|
</column>
|
||||||
<column id="519" parent="485" name="name">
|
<column id="520" parent="485" name="name">
|
||||||
<DasType>TEXT|0s</DasType>
|
<DasType>TEXT|0s</DasType>
|
||||||
<Position>2</Position>
|
<Position>2</Position>
|
||||||
</column>
|
</column>
|
||||||
<column id="520" parent="485" name="tbl_name">
|
<column id="521" parent="485" name="tbl_name">
|
||||||
<DasType>TEXT|0s</DasType>
|
<DasType>TEXT|0s</DasType>
|
||||||
<Position>3</Position>
|
<Position>3</Position>
|
||||||
</column>
|
</column>
|
||||||
<column id="521" parent="485" name="rootpage">
|
<column id="522" parent="485" name="rootpage">
|
||||||
<DasType>INT|0s</DasType>
|
<DasType>INT|0s</DasType>
|
||||||
<Position>4</Position>
|
<Position>4</Position>
|
||||||
</column>
|
</column>
|
||||||
<column id="522" parent="485" name="sql">
|
<column id="523" parent="485" name="sql">
|
||||||
<DasType>TEXT|0s</DasType>
|
<DasType>TEXT|0s</DasType>
|
||||||
<Position>5</Position>
|
<Position>5</Position>
|
||||||
</column>
|
</column>
|
||||||
<column id="523" parent="486" name="entry_id">
|
<column id="524" parent="486" name="entry_id">
|
||||||
<DasType>CHAR(32)|0s</DasType>
|
<DasType>CHAR(32)|0s</DasType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
<Position>1</Position>
|
<Position>1</Position>
|
||||||
</column>
|
</column>
|
||||||
<column id="524" parent="486" name="id">
|
<column id="525" parent="486" name="id">
|
||||||
<DasType>BIGINT|0s</DasType>
|
<DasType>BIGINT|0s</DasType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
<Position>2</Position>
|
<Position>2</Position>
|
||||||
</column>
|
</column>
|
||||||
<column id="525" parent="486" name="channel">
|
<column id="526" parent="486" name="channel">
|
||||||
<DasType>BIGINT|0s</DasType>
|
<DasType>BIGINT|0s</DasType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
<Position>3</Position>
|
<Position>3</Position>
|
||||||
</column>
|
</column>
|
||||||
<column id="526" parent="486" name="starboard_message">
|
<column id="527" parent="486" name="starboard_message">
|
||||||
<DasType>BIGINT|0s</DasType>
|
<DasType>BIGINT|0s</DasType>
|
||||||
<Position>4</Position>
|
<Position>4</Position>
|
||||||
</column>
|
</column>
|
||||||
<index id="527" parent="486" name="sqlite_autoindex_starboard_1">
|
<index id="528" parent="486" name="sqlite_autoindex_starboard_1">
|
||||||
<ColNames>entry_id</ColNames>
|
<ColNames>entry_id</ColNames>
|
||||||
<NameSurrogate>1</NameSurrogate>
|
<NameSurrogate>1</NameSurrogate>
|
||||||
<Unique>1</Unique>
|
<Unique>1</Unique>
|
||||||
</index>
|
</index>
|
||||||
<index id="528" parent="486" name="sqlite_autoindex_starboard_2">
|
<index id="529" parent="486" name="sqlite_autoindex_starboard_2">
|
||||||
<ColNames>id</ColNames>
|
<ColNames>id</ColNames>
|
||||||
<NameSurrogate>1</NameSurrogate>
|
<NameSurrogate>1</NameSurrogate>
|
||||||
<Unique>1</Unique>
|
<Unique>1</Unique>
|
||||||
</index>
|
</index>
|
||||||
<key id="529" parent="486">
|
<key id="530" parent="486">
|
||||||
<ColNames>entry_id</ColNames>
|
<ColNames>entry_id</ColNames>
|
||||||
<Primary>1</Primary>
|
<Primary>1</Primary>
|
||||||
<UnderlyingIndexName>sqlite_autoindex_starboard_1</UnderlyingIndexName>
|
<UnderlyingIndexName>sqlite_autoindex_starboard_1</UnderlyingIndexName>
|
||||||
</key>
|
</key>
|
||||||
<key id="530" parent="486">
|
<key id="531" parent="486">
|
||||||
<ColNames>id</ColNames>
|
<ColNames>id</ColNames>
|
||||||
<UnderlyingIndexName>sqlite_autoindex_starboard_2</UnderlyingIndexName>
|
<UnderlyingIndexName>sqlite_autoindex_starboard_2</UnderlyingIndexName>
|
||||||
</key>
|
</key>
|
||||||
<column id="531" parent="487" name="entry_id">
|
<column id="532" parent="487" name="entry_id">
|
||||||
<DasType>CHAR(32)|0s</DasType>
|
<DasType>CHAR(32)|0s</DasType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
<Position>1</Position>
|
<Position>1</Position>
|
||||||
</column>
|
</column>
|
||||||
<column id="532" parent="487" name="id">
|
<column id="533" parent="487" name="id">
|
||||||
<DasType>VARCHAR(7)|0s</DasType>
|
<DasType>VARCHAR(7)|0s</DasType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
<Position>2</Position>
|
<Position>2</Position>
|
||||||
</column>
|
</column>
|
||||||
<column id="533" parent="487" name="user_id">
|
<column id="534" parent="487" name="user_id">
|
||||||
<DasType>BIGINT|0s</DasType>
|
<DasType>BIGINT|0s</DasType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
<Position>3</Position>
|
<Position>3</Position>
|
||||||
</column>
|
</column>
|
||||||
<column id="534" parent="487" name="name">
|
<column id="535" parent="487" name="name">
|
||||||
<DasType>VARCHAR(32)|0s</DasType>
|
<DasType>VARCHAR(32)|0s</DasType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
<Position>4</Position>
|
<Position>4</Position>
|
||||||
</column>
|
</column>
|
||||||
<index id="535" parent="487" name="sqlite_autoindex_students_1">
|
<index id="536" parent="487" name="sqlite_autoindex_students_1">
|
||||||
<ColNames>entry_id</ColNames>
|
<ColNames>entry_id</ColNames>
|
||||||
<NameSurrogate>1</NameSurrogate>
|
<NameSurrogate>1</NameSurrogate>
|
||||||
<Unique>1</Unique>
|
<Unique>1</Unique>
|
||||||
</index>
|
</index>
|
||||||
<index id="536" parent="487" name="sqlite_autoindex_students_2">
|
<index id="537" parent="487" name="sqlite_autoindex_students_2">
|
||||||
<ColNames>id</ColNames>
|
<ColNames>id</ColNames>
|
||||||
<NameSurrogate>1</NameSurrogate>
|
<NameSurrogate>1</NameSurrogate>
|
||||||
<Unique>1</Unique>
|
<Unique>1</Unique>
|
||||||
</index>
|
</index>
|
||||||
<index id="537" parent="487" name="sqlite_autoindex_students_3">
|
<index id="538" parent="487" name="sqlite_autoindex_students_3">
|
||||||
<ColNames>user_id</ColNames>
|
<ColNames>user_id</ColNames>
|
||||||
<NameSurrogate>1</NameSurrogate>
|
<NameSurrogate>1</NameSurrogate>
|
||||||
<Unique>1</Unique>
|
<Unique>1</Unique>
|
||||||
</index>
|
</index>
|
||||||
<key id="538" parent="487">
|
<key id="539" parent="487">
|
||||||
<ColNames>entry_id</ColNames>
|
<ColNames>entry_id</ColNames>
|
||||||
<Primary>1</Primary>
|
<Primary>1</Primary>
|
||||||
<UnderlyingIndexName>sqlite_autoindex_students_1</UnderlyingIndexName>
|
<UnderlyingIndexName>sqlite_autoindex_students_1</UnderlyingIndexName>
|
||||||
</key>
|
</key>
|
||||||
<key id="539" parent="487">
|
<key id="540" parent="487">
|
||||||
<ColNames>id</ColNames>
|
<ColNames>id</ColNames>
|
||||||
<UnderlyingIndexName>sqlite_autoindex_students_2</UnderlyingIndexName>
|
<UnderlyingIndexName>sqlite_autoindex_students_2</UnderlyingIndexName>
|
||||||
</key>
|
</key>
|
||||||
<key id="540" parent="487">
|
<key id="541" parent="487">
|
||||||
<ColNames>user_id</ColNames>
|
<ColNames>user_id</ColNames>
|
||||||
<UnderlyingIndexName>sqlite_autoindex_students_3</UnderlyingIndexName>
|
<UnderlyingIndexName>sqlite_autoindex_students_3</UnderlyingIndexName>
|
||||||
</key>
|
</key>
|
||||||
|
<column id="542" parent="488" name="entry_id">
|
||||||
|
<DasType>CHAR(32)|0s</DasType>
|
||||||
|
<NotNull>1</NotNull>
|
||||||
|
<Position>1</Position>
|
||||||
|
</column>
|
||||||
|
<column id="543" parent="488" name="target_id">
|
||||||
|
<DasType>VARCHAR(128)|0s</DasType>
|
||||||
|
<NotNull>1</NotNull>
|
||||||
|
<Position>2</Position>
|
||||||
|
</column>
|
||||||
|
<column id="544" parent="488" name="target">
|
||||||
|
<DasType>VARCHAR(128)|0s</DasType>
|
||||||
|
<NotNull>1</NotNull>
|
||||||
|
<Position>3</Position>
|
||||||
|
</column>
|
||||||
|
<column id="545" parent="488" name="is_up">
|
||||||
|
<DasType>BOOLEAN|0s</DasType>
|
||||||
|
<NotNull>1</NotNull>
|
||||||
|
<Position>4</Position>
|
||||||
|
</column>
|
||||||
|
<column id="546" parent="488" name="timestamp">
|
||||||
|
<DasType>FLOAT|0s</DasType>
|
||||||
|
<NotNull>1</NotNull>
|
||||||
|
<Position>5</Position>
|
||||||
|
</column>
|
||||||
|
<column id="547" parent="488" name="response_time">
|
||||||
|
<DasType>INTEGER|0s</DasType>
|
||||||
|
<Position>6</Position>
|
||||||
|
</column>
|
||||||
|
<column id="548" parent="488" name="notes">
|
||||||
|
<DasType>TEXT|0s</DasType>
|
||||||
|
<Position>7</Position>
|
||||||
|
</column>
|
||||||
|
<index id="549" parent="488" name="sqlite_autoindex_uptime_1">
|
||||||
|
<ColNames>entry_id</ColNames>
|
||||||
|
<NameSurrogate>1</NameSurrogate>
|
||||||
|
<Unique>1</Unique>
|
||||||
|
</index>
|
||||||
|
<key id="550" parent="488">
|
||||||
|
<ColNames>entry_id</ColNames>
|
||||||
|
<Primary>1</Primary>
|
||||||
|
<UnderlyingIndexName>sqlite_autoindex_uptime_1</UnderlyingIndexName>
|
||||||
|
</key>
|
||||||
</database-model>
|
</database-model>
|
||||||
</dataSource>
|
</dataSource>
|
|
@ -25,6 +25,7 @@ from selenium.webdriver.chrome.options import Options as ChromeOptions
|
||||||
from selenium.webdriver.chrome.service import Service as ChromeService
|
from selenium.webdriver.chrome.service import Service as ChromeService
|
||||||
from selenium.webdriver.firefox.options import Options as FirefoxOptions
|
from selenium.webdriver.firefox.options import Options as FirefoxOptions
|
||||||
from selenium.webdriver.firefox.service import Service as FirefoxService
|
from selenium.webdriver.firefox.service import Service as FirefoxService
|
||||||
|
|
||||||
# from selenium.webdriver.ie
|
# from selenium.webdriver.ie
|
||||||
|
|
||||||
from utils import console
|
from utils import console
|
||||||
|
@ -56,12 +57,7 @@ class OtherCog(commands.Cog):
|
||||||
"/usr/bin/firefox-esr",
|
"/usr/bin/firefox-esr",
|
||||||
"/usr/bin/firefox",
|
"/usr/bin/firefox",
|
||||||
],
|
],
|
||||||
"chrome": [
|
"chrome": ["/usr/bin/chromium", "/usr/bin/chrome", "/usr/bin/chrome-browser", "/usr/bin/google-chrome"],
|
||||||
"/usr/bin/chromium",
|
|
||||||
"/usr/bin/chrome",
|
|
||||||
"/usr/bin/chrome-browser",
|
|
||||||
"/usr/bin/google-chrome"
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
selected_driver = driver
|
selected_driver = driver
|
||||||
arr = drivers.pop(selected_driver)
|
arr = drivers.pop(selected_driver)
|
||||||
|
@ -128,11 +124,7 @@ class OtherCog(commands.Cog):
|
||||||
start_init = time()
|
start_init = time()
|
||||||
driver, friendly_url = await asyncio.to_thread(_setup)
|
driver, friendly_url = await asyncio.to_thread(_setup)
|
||||||
end_init = time()
|
end_init = time()
|
||||||
console.log(
|
console.log("Driver '{}' initialised in {} seconds.".format(driver_name, round(end_init - start_init, 2)))
|
||||||
"Driver '{}' initialised in {} seconds.".format(
|
|
||||||
driver_name, round(end_init - start_init, 2)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
async def _edit(content: str):
|
async def _edit(content: str):
|
||||||
self.bot.loop.create_task(ctx.interaction.edit_original_response(content=content))
|
self.bot.loop.create_task(ctx.interaction.edit_original_response(content=content))
|
||||||
|
@ -441,7 +433,7 @@ class OtherCog(commands.Cog):
|
||||||
name="capture-full-page",
|
name="capture-full-page",
|
||||||
description="(firefox only) whether to capture the full page or just the viewport.",
|
description="(firefox only) whether to capture the full page or just the viewport.",
|
||||||
default=False,
|
default=False,
|
||||||
)
|
),
|
||||||
):
|
):
|
||||||
"""Takes a screenshot of a URL"""
|
"""Takes a screenshot of a URL"""
|
||||||
window_width = max(min(1080 * 6, window_width), 1080 // 6)
|
window_width = max(min(1080 * 6, window_width), 1080 // 6)
|
||||||
|
@ -542,7 +534,7 @@ class OtherCog(commands.Cog):
|
||||||
f"\\* URL: <{friendly_url}>\n"
|
f"\\* URL: <{friendly_url}>\n"
|
||||||
f"\\* Load time: {fetch_time:.2f}ms\n"
|
f"\\* Load time: {fetch_time:.2f}ms\n"
|
||||||
f"\\* Screenshot render time: {screenshot_time:.2f}ms\n",
|
f"\\* Screenshot render time: {screenshot_time:.2f}ms\n",
|
||||||
file=screenshot
|
file=screenshot,
|
||||||
)
|
)
|
||||||
|
|
||||||
domains = discord.SlashCommandGroup("domains", "Commands for managing domains")
|
domains = discord.SlashCommandGroup("domains", "Commands for managing domains")
|
||||||
|
|
387
cogs/uptime.py
387
cogs/uptime.py
|
@ -1,33 +1,44 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import datetime
|
import datetime
|
||||||
|
import json
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from typing import Dict, Tuple
|
from pathlib import Path
|
||||||
|
from typing import Dict, Tuple, List, Optional
|
||||||
|
from urllib.parse import urlparse, parse_qs, ParseResult
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
import httpx
|
import httpx
|
||||||
from httpx import AsyncClient, Response
|
from httpx import AsyncClient, Response
|
||||||
from discord.ext import commands, tasks
|
from discord.ext import commands, tasks, pages
|
||||||
from utils import UptimeEntry, console
|
from utils import UptimeEntry, console
|
||||||
|
|
||||||
|
|
||||||
class UptimeCompetition(commands.Cog):
|
BASE_JSON = """[
|
||||||
USER_ID = 1063875884274163732
|
{
|
||||||
OUR_ENDPOINT = "http://wg.nexy7574.cyou:9000/"
|
"name": "SHRoNK Bot",
|
||||||
SHRONK_SERVER = "http://shronk.nexy7574.cyou:9000/"
|
"id": "SHRONK",
|
||||||
OUR_DROPLET = "http://droplet.nexy7574.co.uk:9000/"
|
"uri": "user://994710566612500550/1063875884274163732?online=1&idle=0&dnd=0"
|
||||||
TARGETS = {
|
},
|
||||||
"SHRONK": USER_ID,
|
{
|
||||||
"NEX_DROPLET": OUR_DROPLET,
|
"name": "Nex's Droplet",
|
||||||
"PI": OUR_ENDPOINT,
|
"id": "NEX_DROPLET",
|
||||||
"SHRONK_DROPLET": SHRONK_SERVER
|
"uri": "http://droplet.nexy7574.co.uk:9000/"
|
||||||
}
|
},
|
||||||
TARGETS_NAMES = {
|
{
|
||||||
"SHRONK": "SHRoNK Bot",
|
"name": "Nex's Pi",
|
||||||
"NEX_DROPLET": "Nex's Droplet",
|
"id": "PI",
|
||||||
"PI": "Nex's Pi",
|
"uri": "http://wg.nexy7574.cyou:9000/"
|
||||||
"SHRONK_DROPLET": "SHRoNK's Droplet"
|
},
|
||||||
|
{
|
||||||
|
"name": "SHRoNK Droplet",
|
||||||
|
"id": "SHRONK_DROPLET",
|
||||||
|
"uri": "http://shronkservz.tk:9000/"
|
||||||
}
|
}
|
||||||
|
]
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class UptimeCompetition(commands.Cog):
|
||||||
class CancelTaskView(discord.ui.View):
|
class CancelTaskView(discord.ui.View):
|
||||||
def __init__(self, task: asyncio.Task, expires: datetime.datetime):
|
def __init__(self, task: asyncio.Task, expires: datetime.datetime):
|
||||||
timeout = expires - discord.utils.utcnow()
|
timeout = expires - discord.utils.utcnow()
|
||||||
|
@ -39,10 +50,7 @@ class UptimeCompetition(commands.Cog):
|
||||||
self.stop()
|
self.stop()
|
||||||
if self.task:
|
if self.task:
|
||||||
self.task.cancel()
|
self.task.cancel()
|
||||||
await interaction.response.edit_message(
|
await interaction.response.edit_message(content="Uptime test cancelled.", view=None)
|
||||||
content="Uptime test cancelled.",
|
|
||||||
view=None
|
|
||||||
)
|
|
||||||
|
|
||||||
def __init__(self, bot: commands.Bot):
|
def __init__(self, bot: commands.Bot):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
|
@ -52,6 +60,46 @@ class UptimeCompetition(commands.Cog):
|
||||||
self.last_result: list[UptimeEntry] = []
|
self.last_result: list[UptimeEntry] = []
|
||||||
self.task_lock = asyncio.Lock()
|
self.task_lock = asyncio.Lock()
|
||||||
self.task_event = asyncio.Event()
|
self.task_event = asyncio.Event()
|
||||||
|
if not (pth := Path("targets.json")).exists():
|
||||||
|
pth.write_text(BASE_JSON)
|
||||||
|
self._cached_targets = self.read_targets()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def cached_targets(self) -> List[Dict[str, str]]:
|
||||||
|
return self._cached_targets.copy()
|
||||||
|
|
||||||
|
def get_target(self, name: str = None, target_id: str = None) -> Optional[Dict[str, str]]:
|
||||||
|
for target in self.cached_targets:
|
||||||
|
if name and target["name"].lower() == name.lower():
|
||||||
|
return target
|
||||||
|
if target["id"] == target_id:
|
||||||
|
return target
|
||||||
|
return
|
||||||
|
|
||||||
|
def stats_autocomplete(self, ctx: discord.ApplicationContext) -> List[str]:
|
||||||
|
return [x["name"] for x in self.cached_targets if ctx.value.lower() in x["name"].lower()] + ["ALL"]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def parse_user_uri(uri: str) -> Dict[str, str | None | List[str]]:
|
||||||
|
parsed = urlparse(uri)
|
||||||
|
response = {"guild": parsed.hostname, "user": None, "statuses": []}
|
||||||
|
if parsed.path:
|
||||||
|
response["user"] = parsed.path.strip("/")
|
||||||
|
if parsed.query:
|
||||||
|
response["statuses"] = [x for x, y in parse_qs(parsed.query).items() if y[0] == "1"]
|
||||||
|
return response
|
||||||
|
|
||||||
|
def read_targets(self) -> List[Dict[str, str]]:
|
||||||
|
with open("targets.json") as f:
|
||||||
|
data: list = json.load(f)
|
||||||
|
data.sort(key=lambda x: x["name"])
|
||||||
|
self._cached_targets = data.copy()
|
||||||
|
return data
|
||||||
|
|
||||||
|
def write_targets(self, data: List[Dict[str, str]]):
|
||||||
|
self._cached_targets = data
|
||||||
|
with open("targets.json", "w") as f:
|
||||||
|
json.dump(data, f, indent=4, default=str)
|
||||||
|
|
||||||
def cog_unload(self):
|
def cog_unload(self):
|
||||||
self.test_uptimes.cancel()
|
self.test_uptimes.cancel()
|
||||||
|
@ -61,14 +109,20 @@ class UptimeCompetition(commands.Cog):
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert response.text.strip() == "<!DOCTYPE html><html><body>Hello Jimmy!</body></html>"
|
assert response.text.strip() == "<!DOCTYPE html><html><body>Hello Jimmy!</body></html>"
|
||||||
|
|
||||||
async def _test_url(self, url: str, max_retries: int = 10, timeout: int = 30) -> Tuple[int, Response | Exception]:
|
async def _test_url(
|
||||||
|
self, url: str, *, max_retries: int | None = 10, timeout: int | None = 30
|
||||||
|
) -> Tuple[int, Response | Exception]:
|
||||||
attempts = 1
|
attempts = 1
|
||||||
|
if max_retries is None:
|
||||||
|
max_retries = 1
|
||||||
|
if timeout is None:
|
||||||
|
timeout = 10
|
||||||
err = RuntimeError("Unknown Error")
|
err = RuntimeError("Unknown Error")
|
||||||
while attempts < max_retries:
|
while attempts < max_retries:
|
||||||
try:
|
try:
|
||||||
response = await self.http.get(url, timeout=timeout)
|
response = await self.http.get(url, timeout=timeout)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
except (httpx.TimeoutException, httpx.HTTPStatusError) as err:
|
except (httpx.TimeoutException, httpx.HTTPStatusError, ConnectionError, TimeoutError) as err:
|
||||||
attempts += 1
|
attempts += 1
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
|
@ -76,7 +130,7 @@ class UptimeCompetition(commands.Cog):
|
||||||
return attempts, err
|
return attempts, err
|
||||||
|
|
||||||
async def do_test_uptimes(self):
|
async def do_test_uptimes(self):
|
||||||
console.log("Testing uptimes...")
|
targets = self.cached_targets
|
||||||
# First we need to check that we are online.
|
# First we need to check that we are online.
|
||||||
# If we aren't online, this isn't very fair.
|
# If we aren't online, this isn't very fair.
|
||||||
try:
|
try:
|
||||||
|
@ -86,16 +140,17 @@ class UptimeCompetition(commands.Cog):
|
||||||
|
|
||||||
create_tasks = []
|
create_tasks = []
|
||||||
# We need to collect the tasks in case the sqlite server is being sluggish
|
# We need to collect the tasks in case the sqlite server is being sluggish
|
||||||
|
for target in targets.copy():
|
||||||
for key, url in self.TARGETS.items():
|
if not target["uri"].startswith("http"):
|
||||||
if key == "SHRONK":
|
|
||||||
continue
|
continue
|
||||||
kwargs: Dict[str, str | int | None] = {
|
targets.remove(target)
|
||||||
"target_id": key,
|
request_kwargs = {}
|
||||||
"target": url,
|
if timeout := target.get("timeout"):
|
||||||
"notes": ""
|
request_kwargs["timeout"] = timeout
|
||||||
}
|
if max_retries := target.get("max_retries"):
|
||||||
attempts, response = await self._test_url(url)
|
request_kwargs["max_retries"] = max_retries
|
||||||
|
kwargs: Dict[str, str | int | None] = {"target_id": target["id"], "target": target["uri"], "notes": ""}
|
||||||
|
attempts, response = await self._test_url(target["uri"], **request_kwargs)
|
||||||
if isinstance(response, Exception):
|
if isinstance(response, Exception):
|
||||||
kwargs["is_up"] = False
|
kwargs["is_up"] = False
|
||||||
kwargs["response_time"] = None
|
kwargs["response_time"] = None
|
||||||
|
@ -112,13 +167,7 @@ class UptimeCompetition(commands.Cog):
|
||||||
kwargs["is_up"] = True
|
kwargs["is_up"] = True
|
||||||
kwargs["response_time"] = round(response.elapsed.total_seconds() * 1000)
|
kwargs["response_time"] = round(response.elapsed.total_seconds() * 1000)
|
||||||
kwargs["notes"] += "nothing notable."
|
kwargs["notes"] += "nothing notable."
|
||||||
create_tasks.append(
|
create_tasks.append(self.bot.loop.create_task(UptimeEntry.objects.create(**kwargs)))
|
||||||
self.bot.loop.create_task(
|
|
||||||
UptimeEntry.objects.create(
|
|
||||||
**kwargs
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
# We need to check if the shronk bot is online since matthew
|
# We need to check if the shronk bot is online since matthew
|
||||||
# Won't let us access their server (cough cough)
|
# Won't let us access their server (cough cough)
|
||||||
|
@ -126,30 +175,41 @@ class UptimeCompetition(commands.Cog):
|
||||||
# If we don't have presences this is useless.
|
# If we don't have presences this is useless.
|
||||||
if not self.bot.is_ready():
|
if not self.bot.is_ready():
|
||||||
await self.bot.wait_until_ready()
|
await self.bot.wait_until_ready()
|
||||||
|
for target in targets:
|
||||||
guild: discord.Guild = self.bot.get_guild(994710566612500550)
|
parsed = urlparse(target["uri"])
|
||||||
|
if parsed.scheme != "user":
|
||||||
|
continue
|
||||||
|
guild_id = int(parsed.hostname)
|
||||||
|
user_id = int(parsed.path.strip("/"))
|
||||||
|
okay_statuses = [
|
||||||
|
discord.Status.online if "online=1" in parsed.query.lower() else None,
|
||||||
|
discord.Status.idle if "idle=1" in parsed.query.lower() else None,
|
||||||
|
discord.Status.dnd if "dnd=1" in parsed.query.lower() else None,
|
||||||
|
]
|
||||||
|
if "offline=1" in parsed.query:
|
||||||
|
okay_statuses = [discord.Status.offline]
|
||||||
|
okay_statuses = list(filter(None, okay_statuses))
|
||||||
|
guild: discord.Guild = self.bot.get_guild(guild_id)
|
||||||
if guild is None:
|
if guild is None:
|
||||||
console.log(
|
console.log(
|
||||||
"[yellow]:warning: Unable to locate the LCC server! Can't uptime check shronk."
|
f"[yellow]:warning: Unable to locate the guild for {target['name']!r}! Can't uptime check."
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
shronk_bot: discord.Member | None = discord.utils.get(guild.members, name="SHRoNK Bot")
|
user: discord.Member | None = guild.get_member(user_id)
|
||||||
if not shronk_bot:
|
if not user:
|
||||||
# SHRoNK Bot is not in members cache.
|
# SHRoNK Bot is not in members cache.
|
||||||
shronk_bots = await guild.query_members(query="SHRoNK Bot", limit=1)
|
try:
|
||||||
if not shronk_bots:
|
user = await guild.fetch_member(user_id)
|
||||||
console.log(
|
except discord.HTTPException:
|
||||||
"[yellow]:warning: Unable to locate SHRoNK Bot! Can't uptime check shronk."
|
console.log(f"[yellow]:warning: Unable to locate {target['name']!r}! Can't uptime check.")
|
||||||
)
|
user = None
|
||||||
else:
|
if user:
|
||||||
shronk_bot = shronk_bots[0]
|
|
||||||
if shronk_bot:
|
|
||||||
create_tasks.append(
|
create_tasks.append(
|
||||||
self.bot.loop.create_task(
|
self.bot.loop.create_task(
|
||||||
UptimeEntry.objects.create(
|
UptimeEntry.objects.create(
|
||||||
target_id="SHRONK",
|
target_id=target["id"],
|
||||||
target="SHRoNK Bot",
|
target=target["name"],
|
||||||
is_up=shronk_bot.status is not discord.Status.offline,
|
is_up=user.status in okay_statuses,
|
||||||
response_time=None,
|
response_time=None,
|
||||||
notes="*Unable to monitor response time, not a HTTP request.*",
|
notes="*Unable to monitor response time, not a HTTP request.*",
|
||||||
)
|
)
|
||||||
|
@ -167,17 +227,15 @@ class UptimeCompetition(commands.Cog):
|
||||||
return await asyncio.gather(*create_tasks, return_exceptions=True)
|
return await asyncio.gather(*create_tasks, return_exceptions=True)
|
||||||
# All done!
|
# All done!
|
||||||
|
|
||||||
@tasks.loop(minutes=1)
|
# @tasks.loop(minutes=1)
|
||||||
|
@tasks.loop(seconds=10)
|
||||||
async def test_uptimes(self):
|
async def test_uptimes(self):
|
||||||
self.task_event.clear()
|
self.task_event.clear()
|
||||||
async with self.task_lock:
|
async with self.task_lock:
|
||||||
self.last_result = await self.do_test_uptimes()
|
self.last_result = await self.do_test_uptimes()
|
||||||
self.task_event.set()
|
self.task_event.set()
|
||||||
|
|
||||||
uptime = discord.SlashCommandGroup(
|
uptime = discord.SlashCommandGroup("uptime", "Commands for the uptime competition.")
|
||||||
"uptime",
|
|
||||||
"Commands for the uptime competition."
|
|
||||||
)
|
|
||||||
|
|
||||||
@uptime.command(name="stats")
|
@uptime.command(name="stats")
|
||||||
async def stats(
|
async def stats(
|
||||||
|
@ -188,53 +246,40 @@ class UptimeCompetition(commands.Cog):
|
||||||
name="target",
|
name="target",
|
||||||
description="The target to check the uptime of. Defaults to all.",
|
description="The target to check the uptime of. Defaults to all.",
|
||||||
required=False,
|
required=False,
|
||||||
choices=[
|
autocomplete=stats_autocomplete,
|
||||||
discord.OptionChoice("SHRoNK Bot", "SHRONK"),
|
default="ALL",
|
||||||
discord.OptionChoice("Nex Droplet", "NEX_DROPLET"),
|
|
||||||
discord.OptionChoice("Shronk Server", "SHRONK_DROPLET"),
|
|
||||||
discord.OptionChoice("Nex Pi", "PI"),
|
|
||||||
discord.OptionChoice("ALL", "ALL"),
|
|
||||||
],
|
|
||||||
default="ALL"
|
|
||||||
),
|
),
|
||||||
look_back: discord.Option(
|
look_back: discord.Option(
|
||||||
int,
|
int, description="How many days to look back. Defaults to a year.", required=False, default=365
|
||||||
description="How many days to look back. Defaults to a year.",
|
),
|
||||||
required=False,
|
|
||||||
default=365
|
|
||||||
)
|
|
||||||
):
|
):
|
||||||
"""View collected uptime stats."""
|
"""View collected uptime stats."""
|
||||||
|
org_target = query_target
|
||||||
|
|
||||||
def generate_embed(target, specific_entries: list[UptimeEntry]):
|
def generate_embed(target, specific_entries: list[UptimeEntry]):
|
||||||
|
targ = target
|
||||||
|
# targ = self.get_target(target_id=target)
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
title=f"Uptime stats for {self.TARGETS_NAMES[target]}",
|
title=f"Uptime stats for {targ['name']}",
|
||||||
description=f"Showing uptime stats for the last {look_back:,} days.",
|
description=f"Showing uptime stats for the last {look_back:,} days.",
|
||||||
color=discord.Color.blurple()
|
color=discord.Color.blurple(),
|
||||||
)
|
|
||||||
first_check = datetime.datetime.fromtimestamp(
|
|
||||||
specific_entries[-1].timestamp,
|
|
||||||
datetime.timezone.utc
|
|
||||||
)
|
)
|
||||||
|
first_check = datetime.datetime.fromtimestamp(specific_entries[-1].timestamp, datetime.timezone.utc)
|
||||||
last_offline = last_online = None
|
last_offline = last_online = None
|
||||||
online_count = offline_count = 0
|
online_count = offline_count = 0
|
||||||
for entry in reversed(specific_entries):
|
for entry in reversed(specific_entries):
|
||||||
if entry.is_up is False:
|
if entry.is_up is False:
|
||||||
last_offline = datetime.datetime.fromtimestamp(
|
last_offline = datetime.datetime.fromtimestamp(entry.timestamp, datetime.timezone.utc)
|
||||||
entry.timestamp,
|
|
||||||
datetime.timezone.utc
|
|
||||||
)
|
|
||||||
offline_count += 1
|
offline_count += 1
|
||||||
else:
|
else:
|
||||||
last_online = datetime.datetime.fromtimestamp(
|
last_online = datetime.datetime.fromtimestamp(entry.timestamp, datetime.timezone.utc)
|
||||||
entry.timestamp,
|
|
||||||
datetime.timezone.utc
|
|
||||||
)
|
|
||||||
online_count += 1
|
online_count += 1
|
||||||
total_count = online_count + offline_count
|
total_count = online_count + offline_count
|
||||||
online_avg = (online_count / total_count) * 100
|
online_avg = (online_count / total_count) * 100
|
||||||
average_response_time = (
|
average_response_time = (
|
||||||
sum(entry.response_time for entry in entries if entry.response_time is not None) / total_count
|
sum(entry.response_time for entry in entries if entry.response_time is not None) / total_count
|
||||||
)
|
)
|
||||||
|
if org_target != "ALL":
|
||||||
embed.add_field(
|
embed.add_field(
|
||||||
name="\u200b",
|
name="\u200b",
|
||||||
value=f"*Started monitoring {discord.utils.format_dt(first_check, style='R')}, "
|
value=f"*Started monitoring {discord.utils.format_dt(first_check, style='R')}, "
|
||||||
|
@ -247,6 +292,11 @@ class UptimeCompetition(commands.Cog):
|
||||||
f"\n"
|
f"\n"
|
||||||
f"**Average Response Time:**\n\t\\* {average_response_time:.2f}ms",
|
f"**Average Response Time:**\n\t\\* {average_response_time:.2f}ms",
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
embed.title = None
|
||||||
|
embed.description = f"{targ['name']}: {online_avg:.2f}% uptime, last offline: "
|
||||||
|
if last_offline:
|
||||||
|
embed.description += f"{discord.utils.format_dt(last_offline, 'R')}"
|
||||||
return embed
|
return embed
|
||||||
|
|
||||||
await ctx.defer()
|
await ctx.defer()
|
||||||
|
@ -254,27 +304,33 @@ class UptimeCompetition(commands.Cog):
|
||||||
look_back_timestamp = (now - timedelta(days=look_back)).timestamp()
|
look_back_timestamp = (now - timedelta(days=look_back)).timestamp()
|
||||||
targets = [query_target]
|
targets = [query_target]
|
||||||
if query_target == "ALL":
|
if query_target == "ALL":
|
||||||
targets = ["SHRONK", "NEX_DROPLET", "SHRONK_DROPLET", "PI"]
|
targets = [t["id"] for t in self.cached_targets]
|
||||||
embeds = []
|
embeds = []
|
||||||
for _target in targets:
|
for _target in targets:
|
||||||
query = UptimeEntry.objects.filter(UptimeEntry.columns.timestamp >= look_back_timestamp).filter(target_id=_target)
|
_target = self.get_target(_target, _target)
|
||||||
|
query = UptimeEntry.objects.filter(UptimeEntry.columns.timestamp >= look_back_timestamp).filter(
|
||||||
|
target_id=_target["id"]
|
||||||
|
)
|
||||||
query = query.order_by("-timestamp")
|
query = query.order_by("-timestamp")
|
||||||
entries = await query.all()
|
entries = await query.all()
|
||||||
if not entries:
|
if not entries:
|
||||||
embeds.append(
|
embeds.append(discord.Embed(description=f"No uptime entries found for {_target}."))
|
||||||
discord.Embed(
|
|
||||||
description=f"No uptime entries found for {_target}."
|
|
||||||
)
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
embeds.append(generate_embed(_target, entries))
|
embeds.append(generate_embed(_target, entries))
|
||||||
|
|
||||||
|
if org_target == "ALL":
|
||||||
|
new_embed = discord.Embed(
|
||||||
|
title="Uptime stats for all monitored targets:",
|
||||||
|
description=f"Showing uptime stats for the last {look_back:,} days.\n\n",
|
||||||
|
color=discord.Color.blurple(),
|
||||||
|
)
|
||||||
|
for embed_ in embeds:
|
||||||
|
new_embed.description += f"{embed_.description}\n"
|
||||||
|
embeds = [new_embed]
|
||||||
await ctx.respond(embeds=embeds)
|
await ctx.respond(embeds=embeds)
|
||||||
|
|
||||||
@uptime.command(name="view-next-run")
|
@uptime.command(name="view-next-run")
|
||||||
async def view_next_run(
|
async def view_next_run(self, ctx: discord.ApplicationContext):
|
||||||
self,
|
|
||||||
ctx: discord.ApplicationContext
|
|
||||||
):
|
|
||||||
"""View when the next uptime test will run."""
|
"""View when the next uptime test will run."""
|
||||||
await ctx.defer()
|
await ctx.defer()
|
||||||
next_run = self.test_uptimes.next_iteration
|
next_run = self.test_uptimes.next_iteration
|
||||||
|
@ -284,8 +340,7 @@ class UptimeCompetition(commands.Cog):
|
||||||
_wait = self.bot.loop.create_task(discord.utils.sleep_until(next_run))
|
_wait = self.bot.loop.create_task(discord.utils.sleep_until(next_run))
|
||||||
view = self.CancelTaskView(_wait, next_run)
|
view = self.CancelTaskView(_wait, next_run)
|
||||||
await ctx.respond(
|
await ctx.respond(
|
||||||
f"The next uptime test will run in {discord.utils.format_dt(next_run, 'R')}. Waiting...",
|
f"The next uptime test will run in {discord.utils.format_dt(next_run, 'R')}. Waiting...", view=view
|
||||||
view=view
|
|
||||||
)
|
)
|
||||||
await _wait
|
await _wait
|
||||||
if not self.task_event.is_set():
|
if not self.task_event.is_set():
|
||||||
|
@ -297,19 +352,139 @@ class UptimeCompetition(commands.Cog):
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
title="Error",
|
title="Error",
|
||||||
description=f"An error occurred while running an uptime test: {result}",
|
description=f"An error occurred while running an uptime test: {result}",
|
||||||
color=discord.Color.red()
|
color=discord.Color.red(),
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
result = await UptimeEntry.objects.get(entry_id=result.entry_id)
|
result = await UptimeEntry.objects.get(entry_id=result.entry_id)
|
||||||
|
target = self.get_target(target_id=result.target_id) or {"name": result.target_id}
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
title="Uptime for: " + self.TARGETS_NAMES[result.target_id],
|
title="Uptime for: " + target["name"],
|
||||||
description="Is up: {0.is_up!s}\n"
|
description="Is up: {0.is_up!s}\n"
|
||||||
"Response time: {1:,.2f}ms\n"
|
"Response time: {1:,.2f}ms\n"
|
||||||
"Notes: {0.notes!s}".format(result, result.response_time or -1),
|
"Notes: {0.notes!s}".format(result, result.response_time or -1),
|
||||||
color=discord.Color.green()
|
color=discord.Color.green(),
|
||||||
)
|
)
|
||||||
embeds.append(embed)
|
embeds.append(embed)
|
||||||
await ctx.edit(content="Uptime test complete! Results are in.", embeds=embeds, view=None)
|
if len(embeds) >= 3:
|
||||||
|
paginator = pages.Paginator(embeds, loop_pages=True)
|
||||||
|
await ctx.delete(delay=0.1)
|
||||||
|
await paginator.respond(ctx.interaction)
|
||||||
|
else:
|
||||||
|
await ctx.edit(content="Uptime test complete! Results are in:", embeds=embeds, view=None)
|
||||||
|
|
||||||
|
monitors = uptime.create_subgroup(name="monitors", description="Manage uptime monitors.")
|
||||||
|
|
||||||
|
@monitors.command(name="add")
|
||||||
|
@commands.is_owner()
|
||||||
|
async def add_monitor(
|
||||||
|
self,
|
||||||
|
ctx: discord.ApplicationContext,
|
||||||
|
name: discord.Option(str, description="The name of the monitor."),
|
||||||
|
uri: discord.Option(
|
||||||
|
str,
|
||||||
|
description="The URI to monitor. Enter HELP for more info.",
|
||||||
|
),
|
||||||
|
http_max_retries: discord.Option(
|
||||||
|
int,
|
||||||
|
description="The maximum number of HTTP retries to make if the request fails.",
|
||||||
|
required=False,
|
||||||
|
default=None,
|
||||||
|
),
|
||||||
|
http_timeout: discord.Option(
|
||||||
|
int, description="The timeout for the HTTP request.", required=False, default=None
|
||||||
|
),
|
||||||
|
):
|
||||||
|
"""Creates a monitor to... monitor"""
|
||||||
|
await ctx.defer()
|
||||||
|
name: str
|
||||||
|
uri: str
|
||||||
|
http_max_retries: Optional[int]
|
||||||
|
http_timeout: Optional[int]
|
||||||
|
|
||||||
|
uri: ParseResult = urlparse(uri.lower())
|
||||||
|
if uri.scheme == "" and uri.path.lower() == "help":
|
||||||
|
return await ctx.respond(
|
||||||
|
"URI can be either `HTTP(S)`, or the custom `USER` scheme.\n"
|
||||||
|
"Examples:\n"
|
||||||
|
"`http://example.com` - HTTP GET request to example.com\n"
|
||||||
|
"`https://example.com` - HTTPS GET request to example.com\n\n"
|
||||||
|
f"`user://{ctx.guild.id}/{ctx.user.id}?dnd=1` - Checks if user `{ctx.user.id}` (you) is"
|
||||||
|
f" in `dnd` (the red status) in the server `{ctx.guild.id}` (this server)."
|
||||||
|
f"\nQuery options are: `dnd`, `idle`, `online`, `offline`. `1` means the status is classed as "
|
||||||
|
f"'online' - anything else means offline.\n"
|
||||||
|
f"The format is `user://{{server_id}}/{{user_id}}?{{status}}={{1|0}}`\n"
|
||||||
|
f"Setting `server_id` to `$GUILD` will auto-fill in the current server ID."
|
||||||
|
)
|
||||||
|
|
||||||
|
options = {
|
||||||
|
"name": name,
|
||||||
|
"id": name.upper().strip().replace(" ", "_"),
|
||||||
|
}
|
||||||
|
|
||||||
|
if uri.scheme == "user":
|
||||||
|
uri: ParseResult = uri._replace(netloc=uri.hostname.replace("$guild", str(ctx.guild.id)))
|
||||||
|
data = self.parse_user_uri(uri.geturl())
|
||||||
|
if data["guild"] is None or data["guild"].isdigit() is False:
|
||||||
|
return await ctx.respond("Invalid guild ID in URI.")
|
||||||
|
if data["user"] is None or data["user"].isdigit() is False:
|
||||||
|
return await ctx.respond("Invalid user ID in URI.")
|
||||||
|
if not data["statuses"] or any(
|
||||||
|
x.lower() not in ("dnd", "idle", "online", "offline") for x in data["statuses"]
|
||||||
|
):
|
||||||
|
return await ctx.respond("Invalid status query string in URI. Did you forget to supply one?")
|
||||||
|
|
||||||
|
guild = self.bot.get_guild(int(data["guild"]))
|
||||||
|
if guild is None:
|
||||||
|
return await ctx.respond("Invalid guild ID in URI (not found).")
|
||||||
|
|
||||||
|
try:
|
||||||
|
guild.get_member(int(data["user"])) or await guild.fetch_member(int(data["user"]))
|
||||||
|
except discord.HTTPException:
|
||||||
|
return await ctx.respond("Invalid user ID in URI (not found).")
|
||||||
|
|
||||||
|
options["uri"] = uri.geturl()
|
||||||
|
|
||||||
|
elif uri.scheme in ["http", "https"]:
|
||||||
|
attempts, response = await self._test_url(uri.geturl(), max_retries=http_max_retries, timeout=http_timeout)
|
||||||
|
if response is None:
|
||||||
|
return await ctx.respond(
|
||||||
|
f"Failed to connect to {uri.geturl()!r} after {attempts} attempts. Please ensure the target page"
|
||||||
|
f" is up, and try again."
|
||||||
|
)
|
||||||
|
|
||||||
|
options["uri"] = uri.geturl()
|
||||||
|
options["http_max_retries"] = http_max_retries
|
||||||
|
options["http_timeout"] = http_timeout
|
||||||
|
else:
|
||||||
|
return await ctx.respond("Invalid URI scheme. Supported: HTTP[S], USER.")
|
||||||
|
|
||||||
|
targets = self.cached_targets
|
||||||
|
for target in targets:
|
||||||
|
if target["uri"] == options["uri"] or target["name"] == options["name"]:
|
||||||
|
return await ctx.respond("This monitor already exists.")
|
||||||
|
|
||||||
|
targets.append(options)
|
||||||
|
self.write_targets(targets)
|
||||||
|
await ctx.respond("Monitor added!")
|
||||||
|
|
||||||
|
@monitors.command(name="remove")
|
||||||
|
@commands.is_owner()
|
||||||
|
async def remove_monitor(
|
||||||
|
self,
|
||||||
|
ctx: discord.ApplicationContext,
|
||||||
|
name: discord.Option(str, description="The name of the monitor."),
|
||||||
|
):
|
||||||
|
"""Removes a monitor."""
|
||||||
|
await ctx.defer()
|
||||||
|
name: str
|
||||||
|
|
||||||
|
targets = self.cached_targets
|
||||||
|
for target in targets:
|
||||||
|
if target["name"] == name or target["id"] == name:
|
||||||
|
targets.remove(target)
|
||||||
|
self.write_targets(targets)
|
||||||
|
return await ctx.respond("Monitor removed.")
|
||||||
|
await ctx.respond("Monitor not found.")
|
||||||
|
|
||||||
|
|
||||||
def setup(bot):
|
def setup(bot):
|
||||||
|
|
2
main.py
2
main.py
|
@ -29,7 +29,7 @@ extensions = [
|
||||||
"cogs.timetable",
|
"cogs.timetable",
|
||||||
"cogs.other",
|
"cogs.other",
|
||||||
"cogs.starboard",
|
"cogs.starboard",
|
||||||
"cogs.uptime"
|
"cogs.uptime",
|
||||||
]
|
]
|
||||||
for ext in extensions:
|
for ext in extensions:
|
||||||
try:
|
try:
|
||||||
|
|
11
utils/db.py
11
utils/db.py
|
@ -21,7 +21,16 @@ class Tutors(IntEnum):
|
||||||
os.chdir(Path(__file__).parent.parent)
|
os.chdir(Path(__file__).parent.parent)
|
||||||
|
|
||||||
|
|
||||||
__all__ = ["registry", "get_or_none", "VerifyCode", "Student", "BannedStudentID", "Assignments", "Tutors", "UptimeEntry"]
|
__all__ = [
|
||||||
|
"registry",
|
||||||
|
"get_or_none",
|
||||||
|
"VerifyCode",
|
||||||
|
"Student",
|
||||||
|
"BannedStudentID",
|
||||||
|
"Assignments",
|
||||||
|
"Tutors",
|
||||||
|
"UptimeEntry",
|
||||||
|
]
|
||||||
|
|
||||||
T = TypeVar("T")
|
T = TypeVar("T")
|
||||||
T_co = TypeVar("T_co", covariant=True)
|
T_co = TypeVar("T_co", covariant=True)
|
||||||
|
|
Loading…
Reference in a new issue