First commit
20
README.md
Normal file
|
@ -0,0 +1,20 @@
|
|||
## What is this?
|
||||
|
||||
Acck is a web page which allows to easily check if a Mastodon account is moderated (silenced, aka “limited”, and-or suspended, aka “blocked”) by a Mastodon server (aka “instance”).
|
||||
|
||||
## Setting up Acck on a webserver
|
||||
|
||||
To set up Acck on a webserver you just need it to support PHP.
|
||||
|
||||
If you want you can put a «sec/conf.ini» file where you can set some html that will be added before the link to this repo in page footer, and a proxy for connections; for example:
|
||||
|
||||
```
|
||||
footer=<a href="https://my.server/">Home</a>
|
||||
proxy=tcp://localhost:8118
|
||||
```
|
||||
|
||||
## Are there running Acck instances?
|
||||
|
||||
You can find a running Acck instance [here](https://mastodon.help/acck).
|
||||
|
||||
If you set up your own and you want it to be listed here, please let me know using the e-mail address you can find in my profile page.
|
237
css/main.css
Normal file
|
@ -0,0 +1,237 @@
|
|||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: "sans";
|
||||
background-color: #222222;
|
||||
background-image: url("../imgs/bg.png");
|
||||
background-position: -10% -10%;
|
||||
background-repeat: repeat;
|
||||
background-attachment: fixed;
|
||||
color: white;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
/*color: #87decd;*/
|
||||
color: lightgreen;
|
||||
}
|
||||
|
||||
form {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
text-align: center;
|
||||
color: white;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
color: white;
|
||||
text-indent: 3mm;
|
||||
/*text-align: justify;
|
||||
-webkit-hyphens: auto;
|
||||
-ms-hyphens: auto;
|
||||
hyphens: auto;*/
|
||||
}
|
||||
|
||||
.firstp {
|
||||
text-indent: 0;
|
||||
}
|
||||
|
||||
input:focus, textarea:focus, button:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
#main {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-width: 20cm;
|
||||
width: 100%;
|
||||
padding: 3mm;
|
||||
}
|
||||
|
||||
.error, .success, .warning, .neutral, .normtext {
|
||||
width: 100%;
|
||||
color: red;
|
||||
margin-bottom: 15px;
|
||||
border: 1px solid red;
|
||||
border-radius: 6px;
|
||||
padding: 3mm;
|
||||
}
|
||||
|
||||
.neutral {
|
||||
color: white;
|
||||
border-color: white;
|
||||
}
|
||||
|
||||
.warning {
|
||||
color: orange;
|
||||
border-color: orange;
|
||||
}
|
||||
|
||||
.success {
|
||||
color: lightgreen;
|
||||
border-color: lightgreen;
|
||||
}
|
||||
|
||||
.normtext {
|
||||
background-color: #444444;
|
||||
color: white;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.hili {
|
||||
color: yellow;
|
||||
}
|
||||
|
||||
.tittab {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.tittab tr {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.tittab td {
|
||||
margin: 0;
|
||||
padding: 1mm;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.closeb {
|
||||
cursor: pointer;
|
||||
top: 3px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.inputdiv, .lastinputdiv, .outputdiv, .lastoutputdiv {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.inputdiv {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.lastoutputdiv {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.input, .inputx, .textarea, .button, .halfbutton, .copybutton, .output, .outputnobb, .outputli, .posthead, .lastborder, fieldset {
|
||||
width: 100%;
|
||||
border: 1px solid #555555;
|
||||
border-radius: 0 6px 6px 6px;
|
||||
font-size: 12pt;
|
||||
margin: 0;
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
.input, .inputx, .textarea {
|
||||
font-family: "sans";
|
||||
}
|
||||
|
||||
.inputx {
|
||||
border-radius: 0 6px 0 0;
|
||||
}
|
||||
|
||||
.lastborder {
|
||||
border-top: none;
|
||||
border-radius: 0 0 6px 6px;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
padding: 6px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.halfbutton {
|
||||
width: 50%;
|
||||
height: 30px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.button, .copybutton {
|
||||
height: 40px;
|
||||
border-radius: 6px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.copybutton {
|
||||
border-top: none;
|
||||
border-radius: 0 0 6px 6px;
|
||||
}
|
||||
|
||||
.output, .outputnobb {
|
||||
border-radius: 0;
|
||||
margin: 0;
|
||||
font-family: "sans";
|
||||
}
|
||||
|
||||
.outputnobb {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.outputli {
|
||||
border-radius: 0 0 6px 6px;
|
||||
}
|
||||
|
||||
label {
|
||||
max-width: 96%;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
background-color: #555555;
|
||||
border-bottom: none;
|
||||
border-radius: 6px 6px 0 0;
|
||||
padding: 2px 6px 3px 6px;
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.cblab {
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
font-weight: normal;
|
||||
display: table-cell;
|
||||
}
|
||||
|
||||
.posthead, .errposthead {
|
||||
font-weight: bold;
|
||||
font-size: 12pt;
|
||||
margin-bottom: 0;
|
||||
border-bottom: none;
|
||||
border-radius: 6px 6px 0 0;
|
||||
color: white;
|
||||
background-color: #555555;
|
||||
padding: 3px 6px 3px 6px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.errposthead {
|
||||
background-color: red;
|
||||
}
|
||||
|
||||
.horsep {
|
||||
width: 100%;
|
||||
height: 25px;
|
||||
}
|
||||
|
||||
#footer, #almfooter {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
font-size: 9pt;
|
||||
margin: 3mm 0 0 0;
|
||||
}
|
||||
|
||||
#almfooter {
|
||||
font-size: 10.5pt;
|
||||
}
|
BIN
imgs/bg.png
Normal file
After Width: | Height: | Size: 74 KiB |
BIN
imgs/icon-16.png
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
imgs/icon-180.png
Normal file
After Width: | Height: | Size: 49 KiB |
BIN
imgs/icon-192.png
Normal file
After Width: | Height: | Size: 55 KiB |
BIN
imgs/icon-32.png
Normal file
After Width: | Height: | Size: 5.2 KiB |
BIN
imgs/icon-512.png
Normal file
After Width: | Height: | Size: 240 KiB |
BIN
imgs/ogimage.png
Normal file
After Width: | Height: | Size: 118 KiB |
240
index.php
Normal file
|
@ -0,0 +1,240 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
require 'lib/validhostname.php';
|
||||
require 'lib/ckratelimit.php';
|
||||
require 'lib/ght.php';
|
||||
|
||||
const SNAME='acck';
|
||||
const SVERS='0.1.4';
|
||||
const SREPO='https://git.lattuga.net/pongrebio/acck';
|
||||
const MAXACCLEN=30+253+2;
|
||||
const MAXHOSTLEN=253;
|
||||
const INIFP='sec/conf.ini';
|
||||
|
||||
$usname=ucfirst(SNAME);
|
||||
$timeout=5;
|
||||
$cjr=rand(0,999999);
|
||||
|
||||
header('Content-Language: en');
|
||||
|
||||
if (file_exists(INIFP)) {
|
||||
$conf=@parse_ini_file(INIFP,false,INI_SCANNER_RAW);
|
||||
if ($conf===false)
|
||||
die('Configuration file «'.INIFP."» exists but {$usname} could not open it.");
|
||||
}
|
||||
|
||||
$errors=[];
|
||||
|
||||
if (isset($_GET['acctock'])) {
|
||||
if (strlen($_GET['acctock'])>MAXACCLEN) {
|
||||
$_GET['acctock']='';
|
||||
$errors[]='Value for “Account address” is too long';
|
||||
}
|
||||
$_GET['acctock']=trim($_GET['acctock']);
|
||||
if ($_GET['acctock']!='' && preg_match('#^@?[0-9a-zA-Z_]+(@[a-z0-9.-]{4,253})?$#',$_GET['acctock'])!==1)// todo: make it better, like split ecc.
|
||||
$errors[]='Value for “Account address” is not a valid Mastodon account';
|
||||
} else {
|
||||
$_GET['acctock']='';
|
||||
}
|
||||
|
||||
$hostok=false;
|
||||
if (isset($_GET['host'])) {
|
||||
if (strlen($_GET['host'])>MAXHOSTLEN) {
|
||||
$_GET['host']='';
|
||||
$errors[]='Value for “Server domain” is too long';
|
||||
}
|
||||
$_GET['host']=trim($_GET['host']);
|
||||
if ($_GET['host']!='' && !validhostname($_GET['host']))
|
||||
$errors[]='Value for “Server domain” is not a valid hostname';
|
||||
else
|
||||
$hostok=true;
|
||||
} else {
|
||||
$_GET['host']='';
|
||||
}
|
||||
|
||||
$rl=['remaining'=>400,'restime'=>0];
|
||||
if ($hostok) {
|
||||
$rlfp='sec/'.$_GET['host'].'.rl.state';
|
||||
if (file_exists($rlfp)) {
|
||||
$buf=@file($rlfp,FILE_IGNORE_NEW_LINES);
|
||||
if ($buf===false)
|
||||
die('Could not open rate limiting state file.');
|
||||
if (count($buf)!=2 || preg_match('#^\d+$#',$buf[0])!==1 || preg_match('#^\d+$#',$buf[1])!==1)
|
||||
die('Malformed rate limiting state file.');
|
||||
$rl=['remaining'=>$buf[0]+0,'restime'=>$buf[1]+0];
|
||||
}
|
||||
}
|
||||
$now=time();
|
||||
if ($rl['remaining']<=10 && $now<=$rl['restime'])// ten to leave a margin for "many people using it" and to account for 3 calls to host's endpoints
|
||||
$errors[]="This {$usname} instance has reached rate limit on «{$_GET['host']}», please wait at least ".ght($rl['restime']-$now,null,0).'.';
|
||||
|
||||
if (count($errors)>0)
|
||||
$errors='<div class="warning">There are some errors<ul><li>'.implode('</li><li>',$errors).'</li></ul></div><div class="horsep"></div>';
|
||||
else
|
||||
$errors='';
|
||||
|
||||
echo "<!DOCTYPE HTML>
|
||||
<html lang=\"en\">
|
||||
<head>
|
||||
<title>{$usname}</title>
|
||||
<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">
|
||||
<meta name=\"description\" content=\"A tool to check if a Mastodon account is moderated by a Mastodon server\">
|
||||
<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no\">
|
||||
<meta property=\"og:image\" content=\"imgs/ogimage.png\">
|
||||
<link rel=\"icon\" type=\"image/png\" href=\"imgs/icon-16.png\" sizes=\"16x16\">
|
||||
<link rel=\"icon\" type=\"image/png\" href=\"imgs/icon-32.png\" sizes=\"32x32\">
|
||||
<link rel=\"icon\" type=\"image/png\" href=\"imgs/icon-192.png\" sizes=\"192x192\">
|
||||
<link rel=\"icon\" type=\"image/png\" href=\"imgs/icon-512.png\" sizes=\"512x512\">
|
||||
<link rel=\"apple-touch-icon-precomposed\" href=\"imgs/icon-180.png\">
|
||||
<link rel=\"stylesheet\" type=\"text/css\" href=\"css/main.css?v={$cjr}\">
|
||||
</head>
|
||||
<body>
|
||||
<div id=\"main\">
|
||||
<h1>{$usname}</h1>
|
||||
<div class=\"normtext\">
|
||||
<p class=\"firstp\">Hello, this is {$usname}, a tool to easily check if a Mastodon account is moderated (<a href=\"https://docs.joinmastodon.org/admin/moderation/#limit-user\">silenced, aka “limited”</a>, and-or <a href=\"https://docs.joinmastodon.org/admin/moderation/#suspend-user\">suspended, aka “blocked”</a>) by a Mastodon server (aka “instance”).</p>
|
||||
<p>Since an account can be reported as moderated by a Mastodon server even when the server is moderating the whole account’s domain, {$usname} also tries to detect if this is the case, and tells about it.</p>
|
||||
<p>To check if an account is moderated by the given server, {$usname} tries to use the server’s <a href=\"https://docs.joinmastodon.org/methods/accounts/#lookup\">«/api/v1/accounts/lookup» API endpoint</a>, which is public and was introduced in Mastodon v3.4.0; to check if the server is moderating the whole account’s domain, {$usname} tries to use the server’s <a href=\"https://docs.joinmastodon.org/methods/instance/#domain_blocks\">«/api/v1/instance/domain_blocks» API endpoint</a>, which is not always set to public by the server’s admins, and was introduced in Mastodon v4.0.0. If it can’t use one or both of these endpoints, it tells.</p>
|
||||
<p>{$usname} does not use cookies or Javascript and does not store any data about you anywhere.</p>
|
||||
<p>You can find {$usname}’s code <a href=\"".SREPO."\">here</a>, and you can contact me on Mastodon <a href=\"https://puntarella.party/@umpi\">here</a>.</p>
|
||||
</div>
|
||||
<div class=\"horsep\"></div>
|
||||
".$errors."
|
||||
<h2>Check</h2>
|
||||
<form method=\"get\" id=\"mainform\" name=\"mainform\">
|
||||
<div class=\"inputdiv\"><label for=\"acctock\">Account address</label><input type=\"text\" id=\"acctock\" name=\"acctock\" class=\"input\" placeholder=\"Example: paperino@paperopoli.net\" value=\"{$_GET['acctock']}\" maxlength=\"".MAXACCLEN."\" required></div>
|
||||
<div class=\"inputdiv\"><label for=\"host\">Mastodon server (“instance”) domain</label><input type=\"text\" id=\"host\" name=\"host\" class=\"input\" placeholder=\"Example: topolinia.net\" value=\"{$_GET['host']}\" maxlength=\"".MAXHOSTLEN."\" required></div>
|
||||
<div class=\"lastinputdiv\"><button type=\"submit\" id=\"button\" class=\"button\">Check</button></div>\n</form>\n";
|
||||
|
||||
if ($errors=='' && $_GET['acctock']!='' && $_GET['host']!='') {
|
||||
echo "<div class=\"horsep\"></div>\n";
|
||||
$context=[
|
||||
'http'=>[
|
||||
'header'=>"Content-type: application/x-www-form-urlencoded\r\n",
|
||||
'method'=>'GET',
|
||||
'ignore_errors'=>true,
|
||||
'user_agent'=>'Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0',
|
||||
'timeout'=>5
|
||||
]
|
||||
];
|
||||
if (isset($conf['proxy']))
|
||||
$context['http']['proxy']=$conf['proxy'];
|
||||
$http_response_header=null;
|
||||
$context=stream_context_create($context);
|
||||
$hhost=htmlentities($_GET['host']);
|
||||
$url=$_GET['host'].'/api/v1/instance';
|
||||
$hurl=htmlentities($url);
|
||||
$res=@file_get_contents('https://'.$url,false,$context);
|
||||
//echo preprint($http_response_header);
|
||||
if (isset($http_response_header))
|
||||
$rl=ckratelimit($http_response_header,'echofun',true,false);
|
||||
if ($res===false) {
|
||||
echo "<div class=\"error\">Error: could not connect to «{$hhost}».</div>\n";
|
||||
} elseif (is_array($http_response_header) && gethttpcode($http_response_header)!='200') {
|
||||
echo "<div class=\"error\">Error: «{$hhost}» seems not to be running Mastodon.</div>\n";
|
||||
} elseif (null===$res=@json_decode($res,true)) {
|
||||
echo "<div class=\"error\">Error: «{$hurl}» returned data which could not be parsed as JSON (".json_last_error().': '.json_last_error_msg().").</div>\n";
|
||||
} elseif (isset($res['error'])) {
|
||||
echo "<div class=\"error\">Error: «{$hurl}» replied with this error message: «".htmlentities($res['error'])."».</div>\n";
|
||||
} elseif (!isset($res['version'])) {
|
||||
echo "<div class=\"error\">Error: «{$hurl}» returned data in an unexpected format.</div>\n";
|
||||
} elseif (preg_replace('#[^\d\.].*#','',$res['version'])<'3.4.0') {
|
||||
echo "<div class=\"error\">Error: «{$hhost}» is running a version of Mastodon that is earlier than 3.4.0 («{$res['version']}»).</div>\n";
|
||||
} else {
|
||||
$acchost=preg_replace('#^@?[^@]+@(.*)$#','$1',$_GET['acctock']);
|
||||
if ($acchost==$_GET['acctock'] || $acchost=='')
|
||||
$acchost=$_GET['host'];
|
||||
$acchostck=false;
|
||||
$http_response_header=null;
|
||||
$url=$_GET['host'].'/api/v1/instance/domain_blocks';
|
||||
$hurl=htmlentities($url);
|
||||
$res=@file_get_contents('https://'.$url,false,$context);
|
||||
//echo preprint($http_response_header);
|
||||
if (isset($http_response_header))
|
||||
$rl=ckratelimit($http_response_header,'echofun',true,false);
|
||||
if ($res!==false && is_array($http_response_header) && gethttpcode($http_response_header)=='200' && null!==$res=@json_decode($res,true)) {
|
||||
foreach ($res as $val) {
|
||||
if (isset($val['domain'],$val['severity']) && $val['domain']==$acchost) {
|
||||
if ($val['severity']=='silence')
|
||||
$acchostck="The whole domain of the account to check, «{$acchost}», is silenced by «{$_GET['host']}».";
|
||||
elseif ($val['severity']=='suspend')
|
||||
$acchostck="The whole domain of the account to check, «{$acchost}», is suspended by «{$_GET['host']}».";
|
||||
else
|
||||
$acchostck="The whole domain of the account to check, «{$acchost}», is moderated by «{$_GET['host']}», but {$usname} could not detect the moderation severity.";
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($acchostck===false)
|
||||
$acchostck="The domain of the account to check, «{$acchost}», is not moderated (silenced or suspended) by «{$_GET['host']}».";
|
||||
} else {
|
||||
$acchostck="{$usname} could not detect if the domain of the account to check, «{$acchost}», is moderated (silenced or suspended) by «{$_GET['host']}».";
|
||||
}
|
||||
$acchostck=htmlentities($acchostck);
|
||||
$http_response_header=null;
|
||||
$url=$_GET['host'].'/api/v1/accounts/lookup?acct='.urlencode($_GET['acctock']);
|
||||
$hacctock=htmlentities($_GET['acctock']);
|
||||
$hurl=htmlentities($url);
|
||||
$res=@file_get_contents('https://'.$url,false,$context);
|
||||
//echo preprint($http_response_header);
|
||||
if (isset($http_response_header))
|
||||
$rl=ckratelimit($http_response_header,'echofun',true,false);
|
||||
if ($res===false) {
|
||||
echo "<div class=\"error\">Error: could not connect to «{$hhost}».</div>\n";
|
||||
} elseif (null===$res=@json_decode($res,true)) {
|
||||
echo "<div class=\"error\">Error: «{$hurl}» returned data which could not be parsed as JSON (".json_last_error().': '.json_last_error_msg().").</div>\n";
|
||||
} elseif (isset($res['error'])) {
|
||||
if ($res['error']=='Record not found')
|
||||
echo "<div class=\"neutral\">Server «{$hhost}» doesn’t know «{$hacctock}» account.<br><br>{$acchostck}</div>\n";
|
||||
else
|
||||
echo "<div class=\"error\">Error: «{$hurl}» replied with this error message: «".htmlentities($res['error'])."».</div>\n";
|
||||
} elseif (!isset($res['id'])) {
|
||||
echo "<div class=\"error\">Error: «{$hurl}» returned data in an unexpected format.</div>\n";
|
||||
} else {
|
||||
$out="<div class=\"neutral\">\n«{$hacctock}» ";
|
||||
(isset($res['limited']) && $res['limited']) ? $out.='is' : $out.='is not';
|
||||
$out.=" silenced by «{$hhost}»<br>\n«{$hacctock}» ";
|
||||
(isset($res['suspended']) && $res['suspended']) ? $out.='is' : $out.='is not';
|
||||
$out.=" suspended by «{$hhost}»<br><br>\n{$acchostck}</div>\n";
|
||||
echo $out;
|
||||
}
|
||||
}
|
||||
if (is_array($rl) && @file_put_contents($rlfp,$rl['remaining']."\n".($rl['secstoreset']+time())."\n")===false)
|
||||
echo "<div class=\"warning\">Warning: could not write to rate limit state file.</div>\n";
|
||||
}
|
||||
|
||||
if (isset($conf['footer']))
|
||||
echo "<div id=\"almfooter\">{$conf['footer']}</div>\n";
|
||||
|
||||
echo "<div id=\"footer\"><a href=\"".SREPO."\">".SNAME." ".SVERS."</a></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>\n";
|
||||
|
||||
function preprint($var) {
|
||||
return '<pre>'.print_r($var,true)."</pre>\n";
|
||||
}
|
||||
|
||||
function gethttpcode($headers) {
|
||||
return preg_replace('#^[^ ]+ (\d+).*$#','$1',$headers[0]);
|
||||
}
|
||||
|
||||
function echofun($msg) {
|
||||
echo $msg;
|
||||
}
|
||||
|
||||
?>
|
36
lib/ckratelimit.php
Normal file
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
function ckratelimit($headers,$echofun,$onlyret=false,$verbose=false) {//$echofun has to be the name of a defined function to pass messages to
|
||||
$ret=null;
|
||||
if (is_array($headers)) {
|
||||
//echo "ckratelimit: {$headers}: ".print_r($headers,true));
|
||||
$buff=[];
|
||||
array_shift($headers);
|
||||
foreach ($headers as $header)
|
||||
if (preg_match('/^([^:]+):(.*)$/Uu',$header,$matches)===1)
|
||||
$buff[$matches[1]]=trim($matches[2]);
|
||||
$headers=$buff;
|
||||
//print_r($headers);
|
||||
if (isset($headers['Date']) && isset($headers['X-RateLimit-Reset']) && isset($headers['X-RateLimit-Remaining'])) {
|
||||
//Wed, 30 Mar 2022 21:27:22 GMT
|
||||
$srvnow=strtotime($headers['Date']);
|
||||
//2022-03-31T04:05:00.058705Z
|
||||
$srvrlreset=strtotime($headers['X-RateLimit-Reset']);
|
||||
$srvrlremain=$headers['X-RateLimit-Remaining'];
|
||||
$secstoreset=$srvrlreset-$srvnow;
|
||||
$ret=['remaining'=>$srvrlremain,'secstoreset'=>$secstoreset];
|
||||
if ($onlyret)
|
||||
return $ret;
|
||||
if ($verbose) $echofun("ckratelimit: X-RateLimit-Remaining: {$srvrlremain}; server time: {$srvnow}: ".gmdate('c',$srvnow).'; X-RateLimit-Reset: '.gmdate('c',$srvrlreset).'; current seconds before reset: '.$secstoreset.".\n");
|
||||
if ($srvrlremain==0) {
|
||||
$echofun("Reached rate limit, waiting {$secstoreset} seconds for rate limit reset ...\n");
|
||||
sleep($secstoreset);
|
||||
}
|
||||
} else {
|
||||
if ($verbose) $echofun("ckratelimit: no «Date» / «X-RateLimit-Reset» / «X-RateLimit-Remaining» header(s)!\n");
|
||||
}
|
||||
} else {
|
||||
if ($verbose) $echofun("ckratelimit: headers is not an array!\n");
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
?>
|
50
lib/ght.php
Normal file
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
function ght($ts,$fa=null,$sd=2) {
|
||||
/*
|
||||
$ts is seconds (can be float)
|
||||
if not null, $fa has to be an array defining the output suffixes, see below
|
||||
its default ;-)
|
||||
$sd is how many decimals to put after a dot after seconds (can be 0)
|
||||
*/
|
||||
if ($fa==null)
|
||||
$fa=[' year, ',' years, ',' week, ',' weeks, ',' day, ',' days, ',' hour, ',' hours, ',' minute, ',' minutes, ',' second',' seconds'];
|
||||
$out='';
|
||||
$i=0;
|
||||
// years
|
||||
$x=floor($ts/31536000);
|
||||
if ($x>0)
|
||||
($x==1) ? $out.=$x.$fa[$i] : $out.=$x.$fa[$i+1];
|
||||
$ts=$ts-$x*31536000;
|
||||
$i+=2;
|
||||
// weeks
|
||||
$x=floor($ts/604800);
|
||||
if ($x>0)
|
||||
($x==1) ? $out.=$x.$fa[$i] : $out.=$x.$fa[$i+1];
|
||||
$ts=$ts-$x*604800;
|
||||
$i+=2;
|
||||
// days
|
||||
$x=floor($ts/86400);
|
||||
if ($x>0)
|
||||
($x==1) ? $out.=$x.$fa[$i] : $out.=$x.$fa[$i+1];
|
||||
$ts=$ts-$x*86400;
|
||||
$i+=2;
|
||||
// hours
|
||||
$x=floor($ts/3600);
|
||||
if ($x>0)
|
||||
($x==1) ? $out.=$x.$fa[$i] : $out.=$x.$fa[$i+1];
|
||||
$ts=$ts-$x*3600;
|
||||
$i+=2;
|
||||
// minutes
|
||||
$x=floor($ts/60);
|
||||
if ($x>0)
|
||||
($x==1) ? $out.=$x.$fa[$i] : $out.=$x.$fa[$i+1];
|
||||
$ts=$ts-$x*60;
|
||||
$i+=2;
|
||||
// seconds
|
||||
$x=round($ts,$sd);
|
||||
($x==1) ? $out.=$x.$fa[$i] : $out.=$x.$fa[$i+1];
|
||||
return $out;
|
||||
}
|
||||
|
||||
?>
|
15
lib/validhostname.php
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
function validhostname($inp) {
|
||||
if (strlen($inp)>253)
|
||||
return(false);
|
||||
$inp=explode('.',$inp);
|
||||
foreach ($inp as $lab) {
|
||||
$len=strlen($lab);
|
||||
if ($len==0 || $len>63)
|
||||
return(false);
|
||||
if (preg_match('#^[a-z0-9]([a-z0-9-]*[a-z0-9])?$#',$lab)!==1)
|
||||
return(false);
|
||||
}
|
||||
return(true);
|
||||
}
|
||||
?>
|
2
sec/.htaccess
Normal file
|
@ -0,0 +1,2 @@
|
|||
Order deny,allow
|
||||
Deny from all
|