mirror of
https://github.com/iv-org/instances-api.git
synced 2025-08-06 05:14:11 -04:00
Initial commit
This commit is contained in:
commit
b25cb3cbc0
11 changed files with 966 additions and 0 deletions
135
src/instances.cr
Normal file
135
src/instances.cr
Normal file
|
@ -0,0 +1,135 @@
|
|||
# "instances.invidio.us" (which is a status page)
|
||||
# Copyright (C) 2019 Omar Roth
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
require "http/client"
|
||||
require "kemal"
|
||||
require "uri"
|
||||
|
||||
Kemal::CLI.new ARGV
|
||||
|
||||
macro rendered(filename)
|
||||
render "src/instances/views/#{{{filename}}}.ecr"
|
||||
end
|
||||
|
||||
alias Instance = NamedTuple(flag: String?, region: String?, stats: JSON::Any?, type: String, uri: String, monitor: JSON::Any?)
|
||||
|
||||
INSTANCES = {} of String => Instance
|
||||
|
||||
spawn do
|
||||
loop do
|
||||
monitors = [] of JSON::Any
|
||||
page = 1
|
||||
loop do
|
||||
begin
|
||||
response = JSON.parse(HTTP::Client.get(URI.parse("https://uptime.invidio.us/api/getMonitorList/89VnzSKAn?page=#{page}")).body)
|
||||
rescue ex
|
||||
next
|
||||
end
|
||||
|
||||
monitors += response["psp"]["monitors"].as_a
|
||||
page += 1
|
||||
|
||||
if response["psp"]["perPage"].as_i * page > response["psp"]["totalMonitors"].as_i
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
body = HTTP::Client.get(URI.parse("https://raw.githubusercontent.com/wiki/omarroth/invidious/Invidious-Instances.md")).body
|
||||
headers = HTTP::Headers.new
|
||||
|
||||
body.scan(/\[(?<host>[^ \]]+)\]\((?<uri>[^\)]+)\)( .(?<region>[\x{1f100}-\x{1f1ff}]{2}))?/mx).each do |md|
|
||||
region = md["region"]?.try { |region| region.codepoints.map { |codepoint| (codepoint - 0x1f1a5).chr }.join("") }
|
||||
flag = md["region"]?
|
||||
|
||||
uri = URI.parse(md["uri"])
|
||||
host = md["host"]
|
||||
|
||||
case type = host.split(".")[-1]
|
||||
when "onion"
|
||||
when "i2p"
|
||||
else
|
||||
type = uri.scheme.not_nil!
|
||||
client = HTTP::Client.new(uri)
|
||||
client.connect_timeout = 5.seconds
|
||||
client.read_timeout = 5.seconds
|
||||
begin
|
||||
stats = JSON.parse(client.get("/api/v1/stats", headers).body)
|
||||
rescue ex
|
||||
stats = nil
|
||||
end
|
||||
end
|
||||
|
||||
monitor = monitors.select { |monitor| monitor["name"].try &.as_s == host }[0]?
|
||||
INSTANCES[host] = {flag: flag, region: region, stats: stats, type: type, uri: uri.to_s, monitor: monitor}
|
||||
end
|
||||
|
||||
sleep 1.minute
|
||||
Fiber.yield
|
||||
end
|
||||
end
|
||||
|
||||
get "/" do |env|
|
||||
sort_by = env.params.query["sort_by"]?
|
||||
sort_by ||= "users-reverse"
|
||||
|
||||
sort_proc = ->(instance : Tuple(String, Instance)) { instance[0] }
|
||||
instances = INSTANCES.dup.to_a
|
||||
|
||||
case sort_by
|
||||
when .starts_with? "name"
|
||||
instances.sort_by! { |name, instance| name }
|
||||
when .starts_with? "version"
|
||||
instances = instances.sort_by { |name, instance| "#{instance[:stats]?.try &.["software"]?.try &.["version"].as_s.split("-")[0] || "0.0.0"}#{name}" }.reverse
|
||||
when .starts_with? "type"
|
||||
instances.sort_by! { |name, instance| instance[:type] }
|
||||
when .starts_with? "signup"
|
||||
instances.sort_by! { |name, instance| instance[:stats]?.try &.["openRegistrations"]?.try { |bool| bool.as_bool ? 0 : 1 } || 2 }
|
||||
when .starts_with? "location"
|
||||
instances.sort_by! { |name, instance| instance[:region]? || "ZZ" }
|
||||
when .starts_with? "health"
|
||||
instances = instances.sort_by { |name, instance| instance[:monitor]?.try &.["weeklyRatio"]["ratio"].as_s.to_f || 0.0 }.reverse
|
||||
when .starts_with? "users"
|
||||
instances = instances.sort_by { |name, instance| instance[:stats]?.try &.["usage"]?.try &.["users"]["total"].as_i || 0 }.reverse
|
||||
end
|
||||
|
||||
instances.reverse! if sort_by.ends_with?("-reverse")
|
||||
rendered "index"
|
||||
end
|
||||
|
||||
get "/instances.json" do |env|
|
||||
env.response.content_type = "application/json; charset=utf-8"
|
||||
|
||||
if env.params.query["pretty"]?.try &.== "1"
|
||||
INSTANCES.to_pretty_json
|
||||
else
|
||||
INSTANCES.to_json
|
||||
end
|
||||
end
|
||||
|
||||
error 404 do |env|
|
||||
env.redirect "/"
|
||||
halt env, status_code: 302, response: ""
|
||||
end
|
||||
|
||||
static_headers do |response, filepath, filestat|
|
||||
response.headers.add("Cache-Control", "max-age=86400")
|
||||
end
|
||||
|
||||
gzip true
|
||||
public_folder "assets"
|
||||
|
||||
Kemal.config.powered_by_header = false
|
||||
Kemal.run
|
98
src/instances/views/index.ecr
Normal file
98
src/instances/views/index.ecr
Normal file
|
@ -0,0 +1,98 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en-US">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="referrer" content="no-referrer" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
|
||||
<meta name="msapplication-TileColor" content="#575757" />
|
||||
<meta name="theme-color" content="#575757" />
|
||||
<link rel="stylesheet" href="/css/pure-min.css" />
|
||||
<link rel="stylesheet" href="/css/tables-min.css" />
|
||||
<style>
|
||||
body {
|
||||
margin: 40px auto;
|
||||
max-width: 1200px;
|
||||
padding: 0 10px;
|
||||
font-family: Open Sans, Arial;
|
||||
color: #454545;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
code {
|
||||
background: #f8f8f8;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #0366d6;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
pre code {
|
||||
overflow: auto;
|
||||
tab-size: 4;
|
||||
display: block;
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
.pure-table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.right {
|
||||
float: right;
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="header">
|
||||
<p>
|
||||
<span>Instances sourced from <a href="https://github.com/omarroth/invidious/wiki/Invidious-Instances">here</a>.</span>
|
||||
<span class="right"><a href="/instances.json?pretty=1">JSON</a></span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<table class="pure-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><a href="/?sort_by=<%= sort_by == "name" ? "name-reverse" : "name" %>">name</a></th>
|
||||
<th><a href="/?sort_by=<%= sort_by == "version" ? "version-reverse" : "version" %>">version</a></th>
|
||||
<th><a href="/?sort_by=<%= sort_by == "type" ? "type-reverse" : "type" %>">type</a></th>
|
||||
<th><a href="/?sort_by=<%= sort_by == "users" ? "users-reverse" : "users" %>">users</a></th>
|
||||
<th><a href="/?sort_by=<%= sort_by == "signup" ? "signup-reverse" : "signup" %>">signup</a></th>
|
||||
<th><a href="/?sort_by=<%= sort_by == "location" ? "location-reverse" : "location" %>">location</a></th>
|
||||
<th><a href="/?sort_by=<%= sort_by == "health" ? "health-reverse" : "health" %>">health</a></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<% instances.each_with_index do |tuple, index| %>
|
||||
<% name, instance = tuple %>
|
||||
<tr<% if index % 2 == 1 %> class="pure-table-odd" <% end %>>
|
||||
<td><a href="<%= instance[:uri] %>"><%= name %></a></td>
|
||||
<td><%= instance[:stats]?.try &.["software"]?.try &.["version"] || "-" %></td>
|
||||
<td><%= instance[:type] %></td>
|
||||
<td><%= instance[:stats]?.try &.["usage"]?.try &.["users"]["total"] || "-" %></td>
|
||||
<td><%= instance[:stats]?.try &.["openRegistrations"]?.try { |bool| bool.as_bool ? "✔" : "❌" } || "-" %></td>
|
||||
<td title="<%= instance[:region]? %>"><%= instance[:flag]? || "-" %></td>
|
||||
<td><%= instance[:monitor]?.try &.["weeklyRatio"]["ratio"] || "-" %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<p>
|
||||
Released under the AGPLv3. Source available <a href="https://github.com/omarroth/instances.invidio.us">here</a>.
|
||||
<br>
|
||||
This site is built with ❤️ using <a href="https://purecss.io/">Pure v1.0.1</a>.
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
Loading…
Add table
Add a link
Reference in a new issue