First commit

This commit is contained in:
pezcurrel 2023-11-20 12:42:57 +01:00
commit 6c38b489ff
18 changed files with 1814 additions and 0 deletions

41
README.md Normal file
View file

@ -0,0 +1,41 @@
## What is Verbose?
Verbose is a post splitter for Mastodon in a web page. You can write or paste a long post into it, set some options, push the “Split” button, and you get your long post split into many posts considering [Mastodon rules](https://docs.joinmastodon.org/user/posting/#text): every http(s) link counts as 23 characters and every mention counts only for the length of its username part. Each split post will come with `…` signs where it makes sense and a counter in `[n/t]` form, where `n` is the current post number and `t` is the total posts number.
You can connect Verbose to your account: this way *youll be able to post all split posts at once directly from within Verbose*, they will be automatically “chain posted” (the second will be a reply to the first, the third to the second, and so on) and, before posting them, you may set their visibility, language, and a post to reply to with the first split post.
You can also use it without connecting it to your account, but after splitting your long post youll have to copy and paste by hand each split post in sequence into Mastodon, and “chain posting” will be up to you.
Verbose doesnt save anywhere what you write or paste into it, it doesnt use third parties cookies, it sets some cookies of its own only if you choose to connect it to your account, and it can be used even without Javascript.
If you find issues please let me know [here](https://git.lattuga.net/pongrebio/verbose/issues), or using the e-mail address you can find in my profile page, or directly contacting [me on Mastodon](https://puntarella.party/@umpi).
## Setting up Verbose on a webserver
To set up Verbose on a webserver you need it to support PHP, you have to make the directory you put Verbose into and its `/js` subdirectory writeable to the user your webserver runs under, and you have to set a `conf.ini` file into Verbose main directory.
The `conf.ini` file *must* define a `webservertimeout` in seconds, i.e. the maximum time in seconds your webserver allows a request to last (with Apache its [this](https://httpd.apache.org/docs/current/mod/core.html#timeout)); for example:
```
webservertimeout=120
```
Inside `conf.ini` you can also customize the “link text” (text Verbose can add to last split post if theres enough space left; by default its `[This post was split using https://git.lattuga.net/pongrebio/verbose]`); for example:
```
link=[This post was split using https://my.server/verbose]
```
Of course you can put anything as a value for `link`, but the UI mentions it as «link to this page», so its expected to contain a link to the URL of your running Verbose instance ;-)
You can also set a `footer` that, if defined, will be added before the link to this repo in the page footer; for example:
```
footer=<a href="https://my.server/">Home</a>
```
## Are there running Verbose instances?
You can find a running Verbose instance [here](https://mastodon.help/verbose).
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, or directly contacting [me on Mastodon](https://puntarella.party/@umpi).

400
css/main.css Normal file
View file

@ -0,0 +1,400 @@
* {
box-sizing: border-box;
}
html {
scroll-behavior: smooth;
}
body {
font-family: "sans";
font-size: 12pt;
background-color: #222222;
color: white;
margin: 0;
padding: 0;
}
a {
color: #87decd;
}
form {
padding: 0;
margin: 0;
}
h1, h2, h3, h4, h5, h6 {
margin: 3mm 0 5mm 0;
padding: 0;
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;
}
.ul {
padding-left: 5mm;
margin-bottom: 0;
}
.notset {
color: #555555;
}
input:focus, textarea:focus, button:focus {
outline: none;
}
#main {
margin-left: auto;
margin-right: auto;
max-width: 20cm;
width: 100%;
padding: 3mm;
}
#notif {
width: 6cm;
position: fixed;
right: 3mm;
bottom: 3mm;
background-color: #16502d;
color: white;
border: 1px solid white;
padding: 3mm;
border-radius: 6px;
display: none;
font-size: 10pt;
cursor: pointer;
}
#popup {
display: none;
align-items: center;
position: fixed;
top: 0;
width: 100%;
height: 100vh;
z-index: 1;
background-color: rgba(0, 0, 0, .75);
}
#popupmsg {
margin-left: auto;
margin-right: auto;
max-width: 98%;
width: 15cm;
}
#puptitle {
border-radius: 9px 9px 0 0;
padding: 0;
background-color: white;
color: black;
font-weight: bold;
}
#pupmsg {
border-radius: 0 0 9px 9px;
border: 1px solid white;
border-top: 0;
padding: 1.5mm 3mm 1.5mm 3mm;
background-color: #555555;
color: white;
}
#pupmsg ul {
margin: 0;
margin-left: 3mm;
padding: 0;
}
.error, .success, .warning, .normtext {
width: 100%;
color: red;
margin-bottom: 15px;
border: 1px solid red;
border-radius: 6px;
padding: 3mm;
}
.warning {
color: orange;
border-color: orange;
}
.success {
color: lightgreen;
border-color: lightgreen;
}
.normtext {
background-color: #555555;
color: white;
border: none;
}
.hili {
color: #ffcc00;
}
.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, .postbutton, .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: 5px;
}
.halfbutton {
width: 50%;
height: 30px;
border-radius: 6px;
}
.button, .postbutton, .copybutton {
height: 40px;
border-radius: 6px;
font-weight: bold;
}
.button, .postbutton {
color: white;
background-color: #916f7c;
border-color: #ac939d #6c535d #6c535d #ac939d;
}
.button:hover, .postbutton:hover {
background-color: #6c535d;
border-color: #916f7c #48373e #48373e #916f7c;
}
.copybutton {
border-top: none;
border-radius: 0 0 6px 6px;
display: none;
}
.output, .outputnobb {
border-radius: 0;
margin: 0;
font-family: "sans";
}
.outputnobb {
background-color: white;
color: black;
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;
}
.trow {
display: table-row;
}
.tcell {
display: table-cell;
}
.cblab {
background-color: rgba(0, 0, 0, 0);
font-weight: normal;
display: table-cell;
}
.posthead, .errposthead {
font-weight: bold;
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;
}
.separator {
width: 100%;
height: 25px;
}
.pseparator {
width: 100%;
height: 40px;
}
.postdiv, .postdivnobut {
border: 1px solid #555555;
background-color: white;
color: black;
padding: 3px;
}
.postdivnobut {
border-radius: 0 0 6px 6px;
}
.fullheight {
min-height: 90vh;
}
hr {
height: 1px;
background-color: #555555;
color: #555555;
border: none;
}
.debug {
width: 100%;
font-size: 10pt;
padding: 2mm;
}
#pmonitor {
display: none;
}
#pstatus {
color: white;
font-size: 10pt;
padding: 3px;
border: 1px solid white;
border-radius: 4px;
margin-bottom: 1mm;
}
#ppercenv {
padding: 3px;
border: 1px solid white;
border-radius: 4px;
margin-bottom: 1mm;
}
#pperc {
height: 3mm;
background-color: green;
width: 0;
border-radius: 2px;
}
#plog {
font-size: 10pt;
border-radius: 4px;
border: 1px solid white;
color: white;
height: 120px;
padding: 3px;
overflow-y: scroll;
}
#footer, #almfooter {
width: 100%;
text-align: center;
font-size: 9pt;
margin: 3mm 0 0 0;
}
#almfooter {
font-size: 10.5pt;
}
@media only screen and (max-width:10cm) {
body, .input, .inputx, .textarea, .button, .postbutton, .halfbutton, .copybutton, .output, .outputnobb, .outputli, .posthead, .lastborder, fieldset {
font-size: 11pt;
}
#pstatus, #plog, .debug {
font-size: 9pt;
}
#footer {
font-size: 8.5pt;
}
#almfooter {
font-size: 9.5pt;
}
}

BIN
imgs/icon-16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 424 B

BIN
imgs/icon-180.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

BIN
imgs/icon-192.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

BIN
imgs/icon-32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
imgs/icon-512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
imgs/icon_close.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 633 B

BIN
imgs/ogimage.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

847
index.php Normal file

File diff suppressed because one or more lines are too long

26
js/main.js Normal file
View file

@ -0,0 +1,26 @@
/*function copytext(elid) {
window.getSelection().selectAllChildren(document.getElementById(elid));
document.execCommand('copy');
}*/
function copytext(index) {
navigator.clipboard.writeText(document.getElementById('post_'+index).value);
var notif=document.getElementById('notif');
notif.textContent='Post '+(index+1)+' text was successfully copied into your clipboard :-)';
notif.style.display='block';
}
function repments(match,p1,p2) {
return p1+p2;
}
function replinks(match,p1) {
return p1+'UUUUUUUUUUUUUUUUUUUUUUU';
}
function mastlength(cont) {
const mentre=/(^|\W)(@[a-zA-Z0-9_]+)@(([a-z0-9]([a-z0-9-]+[a-z0-9])?){1,63}\.)+([a-z0-9]([a-z0-9-]+[a-z0-9])?){1,63}\b/g;
const linkre=new RegExp(`(^|\\W)https?://(([a-z0-9]([a-z0-9-]+[a-z0-9])?){1,63}\\.)+(${tlds.join('|')})(/\\S*[\\w/=_\-])?`,'g');
cont=cont.replace(mentre,repments);
cont=cont.replace(linkre,replinks);
return cont.length;
}

8
lib/booltostr.php Normal file
View file

@ -0,0 +1,8 @@
<?php
function booltostr($bool,$true='true',$false='false') {
if ($bool)
return($true);
else
return($false);
}
?>

36
lib/ckratelimit.php Normal file
View 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;
}
?>

View file

@ -0,0 +1,20 @@
<?php
function getfirstbrowserlang($default='en') {
if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
$langs=[];
$buff=explode(',',$_SERVER['HTTP_ACCEPT_LANGUAGE']);
foreach ($buff as $ent) {
$ent=trim($ent);
$ent=explode(';',$ent);
(count($ent)<2) ? $ent[1]=1 : $ent[1]=preg_replace('/^q=/','',$ent[1])+0;
$ent[0]=locale_canonicalize($ent[0]);
$langs[$ent[0]]=$ent[1];
}
arsort($langs);
$blang=array_key_first($langs);
return $blang;
} else {
return $default;
}
}
?>

44
lib/gettlds.php Normal file

File diff suppressed because one or more lines are too long

50
lib/ght.php Normal file
View 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;
}
?>

282
lib/mastodon.php Normal file
View file

@ -0,0 +1,282 @@
<?php
// Warning: postlength function requires $retlds global variable to be defined,
// it has to be a reverse ordered list of "|" separated valid tlds, you can
// require gettlds.php in the calling script and use it to set it, like this:
// $retlds=gettlds(); $retlds=implode('|',$retlds);
function validtoken($token) {
if (preg_match('#^[A-Za-z0-9_-]{43}$#',$token)===1)
return true;
else
return false;
}
function mastreq($context,$host,$endpoint) {
$context=stream_context_create($context);
$endpoint="https://{$host}{$endpoint}";
$res=@file_get_contents($endpoint,false,$context);
if ($res===false)
return ['ok'=>false,'error'=>"could not connect to «{$host}»",'headers'=>null];
$res=@json_decode($res,true);
if (is_null($res))
return ['ok'=>false,'error'=>"could not decode JSON data from «{$endpoint}» (".json_last_error().': '.json_last_error_msg().")",'headers'=>$http_response_header];
if (isset($res['error']))
return ['ok'=>false,'error'=>lcfirst($res['error']),'headers'=>$http_response_header];
/*print_r($http_response_header);
preg_match('#^\S+\s+(\S+)\s+(\S+)#',$http_response_header[0],$matches);
print_r($matches);
$httpcode=$matches[1]+0;
$httpcodetext=$matches[2];
if (($httpcode>=400 && $httpcode<=499) || ($httpcode>=500 && $httpcode<=599))
return ['ok'=>false,'error'=>"HTTP error: {$httpcodetext}"];*/
return ['ok'=>true,'data'=>$res,'headers'=>$http_response_header];
}
function mastget($host,$token,$endpoint,$timeout) {
$context=[
'http'=>[
'header'=>"Content-type: application/x-www-form-urlencoded\r\nAccept: application/json\r\n",
'method'=>'GET',
'ignore_errors'=>true,
'timeout'=>$timeout
]
];
if (!is_null($token))
$context['http']['header'].="Authorization: Bearer {$token}\r\n";
$res=mastreq($context,$host,$endpoint);
return $res;
}
function mastpost($host,$token,$endpoint,$content,$timeout) {
$content=http_build_query($content);
$context=[
'http'=>[
'header'=>"Content-type: application/x-www-form-urlencoded\r\nAccept: application/json\r\n",
'method'=>'POST',
'ignore_errors'=>true,
'content'=>$content,
'timeout'=>$timeout
]
];
if (!is_null($token))
$context['http']['header'].="Authorization: Bearer {$token}\r\n";
$res=mastreq($context,$host,$endpoint);
return $res;
}
function mastpostfile($host,$token,$endpoint,$content,$timeout) {
$content=http_build_query($content);
$context=[
'http'=>[
'header'=>"Content-type: multipart/form-data;boundary=\"boundary\"\r\nAccept: application/json\r\n",
'method'=>'POST',
'ignore_errors'=>true,
'content'=>$content,
'timeout'=>$timeout
]
];
if (!is_null($token))
$context['http']['header'].="Authorization: Bearer {$token}\r\n";
$res=mastreq($context,$host,$endpoint);
return $res;
}
function mastdel($host,$token,$endpoint,$timeout) {
$context=[
'http'=>[
'header'=>"Content-type: application/x-www-form-urlencoded\r\nAccept: application/json\r\n",
'method'=>'DELETE',
'ignore_errors'=>true,
'timeout'=>$timeout
]
];
if (!is_null($token))
$context['http']['header'].="Authorization: Bearer {$token}\r\n";
$res=mastreq($context,$host,$endpoint);
return $res;
}
/*
some endpoints
get
auth required
verify app creds and get app info: /api/v1/apps/verify_credentials
verify user creds and get account info: /api/v1/accounts/verify_credentials
get a post: /api/v1/statuses/[id]
post
auth required
post a status: /api/v1/statuses
send follow request to an account: /api/v1/accounts/[id]/follow
unfollow an account: /api/v1/accounts/[id]/unfollow
*/
function splitpost($post,$avchars,$cw,$pre,$cntup) {
// decided use $matches[1] instead of $matches[0]
// to stay safe, $avchars should be at least 30 (didn't test with less);
// $pre can be used to list recipients (in this case it has to end with
// a "\n" or " "), or for anything else
$post=preg_replace('#[ \t\f\r]+\n#',"\n",$post);
$post=rtrim($post);
$postrlen=strlen($post);
$postlen=postlength($post);
$cwlen=mb_strlen($cw,'UTF-8');
$prelen=postlength($pre);
if ($postlen+$prelen+$cwlen<=$avchars)
return [['cw'=>$cw,'post'=>$pre.$post,'mastlen'=>$postlen+$prelen+$cwlen]];
// there is no way to know the total of posts before splitting, and its
// string length modifies the total, so we roughly estimate it very
// cautiosly to the decrease, just to spare cycles
$tot='';
$gtot=ceil($postlen/($avchars-7-2-$prelen-$cwlen));// "7" is the min length of the counter ("\n\n[x/x]"); 2 counts for start and end "…"
for ($i=0; $i<strlen($gtot); $i++)
$tot.='x';
$c=0;
while (true) {
$c++;
$totlen=strlen($tot);
$spost=[];
$buf='';
$off=0;
$i=1;
while (true) {
//echo "========================\n";
if (strlen($i)>$totlen) break;// do another cycle
$cnt="__[{$i}/{$tot}]";
//$lastcons=substr($post,$off,40);
preg_match('#(\S+)(\s+|$)#',$post,$matches,0,$off);
//var_dump($matches);
if (count($matches)==0) {// done, last post
$spost[]=['cw'=>$cw,'post'=>rtrim($buf)];
break 2;
}
$offadd=strlen($matches[0]);
($off+$offadd>=$postrlen) ? $dotsaddlen=0 : $dotsaddlen=2;// if we are on the last word, we don't add "…"
if ($prelen+$cwlen+postlength($buf.$matches[1].$cnt)+$dotsaddlen>$avchars) {// if current match would make buf+overhead overcome avchars
//echo "LONGMATCH: «$matches[0]»\n";
$nxcntlen=$totlen+strlen($i+1)+5;// next cnt may be different, so we precalc its length
($i==1 || $dotsaddlen==0) ? $nxdotsaddlen=2 : $nxdotsaddlen=4;// if we are on first or last post, we add 1 "…"; otherwise we add 2
if ($prelen+$cwlen+postlength($matches[1])+$nxcntlen+$nxdotsaddlen>$avchars) {// if next match+overhead is by itself longer than avchars
//echo "BLOCKMATCH: «$matches[0]»\n";
//$len=$avchars-$nxcntlen-$prelen-$nxdotsaddlen;
$len=$avchars-postlength($buf.$cnt)-$prelen-$cwlen-$dotsaddlen;
if ($len>0) {
// deactivate possible links because they will be broken
$matches[0]=preg_replace('#^http(s)?://#','zttp$1://',$matches[0]);
$matches[0]=preg_replace('#^@([a-zA-Z0-9_]+@[a-z0-9-]+)#','+$1',$matches[0]);
$matches[0]=mb_substr($matches[0],0,$len,'UTF-8');
//echo "SUBSTRING: «$matches[0]»\n";
$offadd=strlen($matches[0]);
//echo "{$matches[0]}: OFF: {$off}; OFFADD: {$offadd}\n";
$buf.=$matches[0];
$matches[0]='';
}
}
$spost[]=['cw'=>$cw,'post'=>rtrim($buf).' …'];
$buf='… ';
$i++;
}/* else {
echo "NORMATCH: «$matches[0]»\n";
}*/
$buf.=$matches[0];
$off+=$offadd;
}
$tot.='x';
}
//echo '<pre>'.print_r($spost,true).'</pre>';
if ($cntup)
foreach ($spost as $key=>$post) {
$spost[$key]['post']="{$pre}[".($key+1)."/{$i}]\n\n{$post['post']}";
$spost[$key]['mastlen']=postlength($spost[$key]['post'])+$cwlen;
}
else
foreach ($spost as $key=>$post) {
$spost[$key]['post']="{$pre}{$post['post']}\n\n[".($key+1)."/{$i}]";
$spost[$key]['mastlen']=postlength($spost[$key]['post'])+$cwlen;
}
//echo "CYCLES: {$c}\n";
//echo "LASTCONS: {$lastcons}\n";
return $spost;
}
function postlength($post) {
global $retlds;
// echo "-A-> |{$post}|\n";
// for some reason, mastodon seems to check tld existence only on http(s) links - see next regexp
$res=preg_replace('#(^|\W)(@[a-zA-Z0-9_]+)@(([a-z0-9]([a-z0-9-]+[a-z0-9])?){1,63}\.)+([a-z0-9]([a-z0-9-]+[a-z0-9])?){1,63}\b#u', '$1$2', $post);
if (!is_null($res)) $post=$res;
// $res=preg_replace('#(^|\W)https?://(([a-z0-9]([a-z0-9-]+[a-z0-9])?){1,63}\.)+([a-z0-9]([a-z0-9-]+[a-z0-9])?){1,63}(/\S*[\w=?_-])?#u', '$1HTTP://UUUUUUUUUUUUUUUU', $post);
// on http(s) links mastodon checks if tld exists...
$res=preg_replace('#(^|\W)https?://(([a-z0-9]([a-z0-9-]+[a-z0-9])?){1,63}\.)+('.$retlds.')(/\S*[\w/=_\-])?#u', '$1UUUUUUUUUUUUUUUUUUUUUUU', $post);
if (!is_null($res)) $post=$res;
// echo "-B-> |{$post}|\n";
return mb_strlen($post,'UTF-8');
}
// this function requires these to be defined:
// - an "evhandle" function to handle events
// - an "eecho" function to handle output
// - a "$doshut" global variable and a "shutdown" function that, since it's placed in secure places, can be used eg to safely shut down the program when "$doshut" is set to true by eg a function bound to a signal, like pcntl_signal(SIGTERM,'sighandler')
// see ocrbot for an example
function evlisten($host,$port,$endpoint,$token,$timeout) {
global $doshut;
while (true) {
shutdown($doshut);
$dispurl="tls://{$host}:{$port}";
eecho(1,"trying to connect to «{$dispurl}».");
$sh=@fsockopen("tls://{$host}",$port,$errno,$errstr,$timeout);
if ($sh===false) {
eecho(3,"could not connect to «{$dispurl}»: {$errstr} ({$errno}); will try again in 1 second.");
sleep(1);
} else {
//stream_set_blocking($sh,false);
stream_set_timeout($sh,1,0);
eecho(1,"succesfully connected to «{$dispurl}».");
$req="GET {$endpoint} HTTP/1.1\r\nHost: {$host}\r\nUser-Agent: a_bot\r\nAuthorization: Bearer {$token}\r\n\r\n";
if (fwrite($sh,$req)===false) {
eecho(3,"could not subscribe to user notifications on «{$dispurl}»; will try again in 1 second.");
fclose($sh);
unset($sh);// this is because shutdown can check if $sh is set and if it is, try to fclose it
sleep(1);
} else {
eecho(1,"listening for user notifications on «{$dispurl}».");
//$lc=0;
while (!feof($sh)) {
shutdown($doshut);
//$lc++;
$line=rtrim(fgets($sh),"\r\n");
//echo "{$lc}> {$line}\n";
if (preg_match('#^event: #',$line)===1) {
$event=['type'=>preg_replace('#^event: #','',$line),'data'=>''];
$line=rtrim(fgets($sh),"\r\n");
//echo "{$lc} DATA> {$line}\n";
if (preg_match('#^data: #',$line)===1) {
$event['data'].=preg_replace('#^data: #','',$line);
while ($line!='') {
$line=rtrim(fgets($sh),"\r\n");
if ($line=='') break;
//echo "{$lc} LENGTH> {$line}\n";
$line=rtrim(fgets($sh),"\r\n");
//echo "{$lc} DATA> {$line}\n";
$event['data'].=$line;
}
$event['data']=@json_decode($event['data'],true);
if ($event['data']===false) {
eecho(2,"could not decode data for event of type «{$event['type']}».");
} else {
//print_r($event);
evhandle($event);
}
}
}
}
fclose($sh);
unset($sh);// this is because shutdown can check if $sh is set and if it is, try to fclose it
eecho(3,"lost connection to «{$dispurl}»; will try reconnecting in 1 second.");
sleep(1);
}
}
}
}
?>

60
post.php Normal file
View file

@ -0,0 +1,60 @@
<?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/mastodon.php';
require 'lib/ckratelimit.php';
$resp=[
'ok'=>false,
'error'=>null,
'remaining'=>null,
'secstoreset'=>null,
'id'=>null
];
if (isset($_COOKIE['verbose_host']) && isset($_COOKIE['verbose_token']) && isset($_POST['visibility']) && in_array($_POST['visibility'],['public','unlisted','private','direct']) && isset($_POST['language']) && isset($_POST['status'])) {
$timeout=5;
$res=mastpost($_COOKIE['verbose_host'],$_COOKIE['verbose_token'],'/api/v1/statuses',$_POST,$timeout);
//$res=['ok'=>true, 'data'=>['id'=>999], 'error'=>'server exploded'];// test
if ($res['ok']) {
$resp['ok']=true;
$resp['id']=$res['data']['id'];
$rls=ckratelimit($res['headers'],'necho',true,false);
//$rls=['remaining'=>20,'secstoreset'=>5];// test
if (!is_null($rls)) {
$resp['remaining']=$rls['remaining'];
$resp['secstoreset']=$rls['secstoreset'];
}
} else {
$resp['error']=htmlentities($res['error']);
}
} else {
$resp['error']='malformed POST request';
}
header('Content-Type: application/json');
$resp=json_encode($resp);
echo $resp;
//echo '<pre>'.print_r($_POST,true).'</pre>';
function necho($msg) {
// do nothing :-)
}
?>