From 6c38b489ffe26457674afbc53880c3d9369c849f Mon Sep 17 00:00:00 2001 From: pezcurrel Date: Mon, 20 Nov 2023 12:42:57 +0100 Subject: [PATCH] First commit --- README.md | 41 ++ css/main.css | 400 +++++++++++++++++ imgs/icon-16.png | Bin 0 -> 424 bytes imgs/icon-180.png | Bin 0 -> 3975 bytes imgs/icon-192.png | Bin 0 -> 4268 bytes imgs/icon-32.png | Bin 0 -> 1135 bytes imgs/icon-512.png | Bin 0 -> 12499 bytes imgs/icon_close.png | Bin 0 -> 633 bytes imgs/ogimage.png | Bin 0 -> 11795 bytes index.php | 847 ++++++++++++++++++++++++++++++++++++ js/main.js | 26 ++ lib/booltostr.php | 8 + lib/ckratelimit.php | 36 ++ lib/getfirstbrowserlang.php | 20 + lib/gettlds.php | 44 ++ lib/ght.php | 50 +++ lib/mastodon.php | 282 ++++++++++++ post.php | 60 +++ 18 files changed, 1814 insertions(+) create mode 100644 README.md create mode 100644 css/main.css create mode 100644 imgs/icon-16.png create mode 100644 imgs/icon-180.png create mode 100644 imgs/icon-192.png create mode 100644 imgs/icon-32.png create mode 100644 imgs/icon-512.png create mode 100644 imgs/icon_close.png create mode 100644 imgs/ogimage.png create mode 100644 index.php create mode 100644 js/main.js create mode 100644 lib/booltostr.php create mode 100644 lib/ckratelimit.php create mode 100644 lib/getfirstbrowserlang.php create mode 100644 lib/gettlds.php create mode 100644 lib/ght.php create mode 100644 lib/mastodon.php create mode 100644 post.php diff --git a/README.md b/README.md new file mode 100644 index 0000000..9511c32 --- /dev/null +++ b/README.md @@ -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 *you’ll 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 you’ll have to copy and paste by hand each split post in sequence into Mastodon, and “chain posting” will be up to you. + +Verbose doesn’t save anywhere what you write or paste into it, it doesn’t 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 it’s [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 there’s enough space left; by default it’s `[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 it’s 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=Home +``` + +## 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). diff --git a/css/main.css b/css/main.css new file mode 100644 index 0000000..023bdbe --- /dev/null +++ b/css/main.css @@ -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; + } +} diff --git a/imgs/icon-16.png b/imgs/icon-16.png new file mode 100644 index 0000000000000000000000000000000000000000..dcaf64a06e473dbe7367e5c4c661722c06880908 GIT binary patch literal 424 zcmV;Z0ayNsP)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10X0cP zK~yNuWBAVif(%RyzJlv{U$XvU{l)h}U@e0e10z%hOz836*7-F1|E>RDf$+xv8UHmt z@m*)of+}FJ<@&tn-+wUJ1B4JT=P&ms26Lb+g9!HrnBf2aWiWvBwUigmy?*BcJcqje~-evhFs)nRXk%1YBr_S(Al7;=-2YyZ*3f?iW zePv=hzU@Ck$e+OnNx`=N%!h!U6#eoMt1sUE7y8Oz3JeFnsm5R4LItM)Aq0H+ult3! z3ldO_JpIC7XaD;S*Zb-J-TyoP3tWN+HIT9r*w6mc=tJU*!N%HA(8?x zlb6AXA%-EE!H$8OcV5&F=`Re4v10VlI(Z5_D7<6$c04m|)sN;Xgd=CIocK6J# Su6!y00000J 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 literal 0 HcmV?d00001 diff --git a/imgs/icon-192.png b/imgs/icon-192.png new file mode 100644 index 0000000000000000000000000000000000000000..5a292979131cf9ae90c16b10181862b4377c69f1 GIT binary patch literal 4268 zcmV;d5L54oP)G000009a7bBm006Nn z006Nn0b!>s?*IS*8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H15IIRi zK~#9!?VWp^PURNIe={yaNTEzF6_rFPl}I8rollB%L^mQwfqhyNY6OuA+Be!NSGr#l4jOLxO*R%J#=biUmduFfa^YMqh_A_fe-~I0W zT-LK55ZVi3(GacC6K`M&=3_aꗱu4h?L8KuCDL8-~*oZGN7h~}%I->>Zp_~W2 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 TLtrJ)00000NkvXXu0mjfv|bt) literal 0 HcmV?d00001 diff --git a/imgs/ogimage.png b/imgs/ogimage.png new file mode 100644 index 0000000000000000000000000000000000000000..1402dfb9ccb5ade45acedf4a6d7cd83b156dbef1 GIT binary patch literal 11795 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 literal 0 HcmV?d00001 diff --git a/index.php b/index.php new file mode 100644 index 0000000..3f3cfb5 --- /dev/null +++ b/index.php @@ -0,0 +1,847 @@ +. +*/ + +// Logorrh, Verbose, Charliero, Tashugo? + +require 'lib/gettlds.php'; +$tlds=gettlds(); +$retlds=implode('|',$tlds); +$jstlds="const tlds=['".implode("', '",$tlds)."'];\n"; +if (@file_put_contents('js/tlds.js',$jstlds)===false) + dieyoung('Error: could not save «js/tlds.js»'); +unset($tlds,$jstlds); +require 'lib/mastodon.php'; +require 'lib/ckratelimit.php'; +require 'lib/getfirstbrowserlang.php'; +require 'lib/ght.php'; +require 'lib/booltostr.php'; + +const SNAME='Verbose'; +const SVERS='0.3.5'; +const SREPO='https://git.lattuga.net/pongrebio/verbose'; +const DEFAC=500; +const MINAC=100; +const MAXAC=100000; +const MINCL=40; +const MAXPOSTC=50000; +const MAXREPTOLEN=1500; +const POSTPAUSE=250;// pause between sending split posts (in milliseconds) +const REQSTOPRESV=10;// requests to preserve +const CONFFN='conf.ini'; + +$sname=SNAME; +$svers=SVERS; +$now=time(); +$timeout=5; +$formact=preg_replace('#[^/]+$#','',$_SERVER['REQUEST_URI']); + +$dodebug=false; +$debug=''; + +ini_set('max_execution_time',360);// 6 minutes: default mastodon ratelimits: https://docs.joinmastodon.org/api/rate-limits/ +$conf=['link'=>"\n\n[This post was split using ".SREPO.']']; +if (file_exists(CONFFN)) { + $fconf=@parse_ini_file(CONFFN,false,INI_SCANNER_RAW); + if ($fconf===false) { + dieyoung('Error: configuration file «'.CONFFN.'» was found but could not be opened for reading.'); + } else { + if (isset($fconf['link'])) + $conf['link']="\n\n{$fconf['link']}"; + if (isset($fconf['footer'])) + $conf['footer']=$fconf['footer']; + if (isset($fconf['webservertimeout'])) { + if (preg_match('#^\d+$#',$fconf['webservertimeout'])===1) + $conf['webservertimeout']=$fconf['webservertimeout']+0; + else + dieyoung('Error: configuration file «'.CONFFN.'»: value «'.$fconf['webservertimeout'].'» is not valid for «webservertimeout».'); + } else { + dieyoung('Error: configuration file «'.CONFFN.'» does not define «webservertimeout».'); + } + } + unset($fconf); +} else { + dieyoung('Error: configuration file «'.CONFFN.'» was not found.'); +} +$debug.='CONF: '.preprint($conf)."
\n"; + +$coopts=[ + 'expires'=>$now+365*24*60*60, + 'path'=>preg_replace('#[^/]+$#','',$_SERVER['REQUEST_URI']), + 'domain'=>$_SERVER['SERVER_NAME'], + 'secure'=>false, + 'httponly'=>false, + 'samesite'=>'Lax'// None || Lax || Strict +]; +$cooptsx=$coopts; +$cooptsx['expires']=$now-3600; + +/*setcookie('verbose_client_id','',$cooptsx); +setcookie('verbose_client_secret','',$cooptsx); +setcookie('verbose_host','',$cooptsx); +setcookie('verbose_token','',$cooptsx);*/ + +$debug.='COOKIES: '.preprint($_COOKIE); +$debug.='POST: '.preprint($_POST); + +$blang=getfirstbrowserlang(); +$blang=preg_replace('#^([a-z]+).*#','$1',$blang); + +$host=false; +$token=false; +(isset($_SERVER['HTTPS'])) ? $proto='https' : $proto='http'; +$rediruri=$proto.'://'.$_SERVER['HTTP_HOST'].preg_replace('#^([^?]*).*#','$1',$_SERVER['REQUEST_URI']); +$authmsgs=''; + +$scope='read write:statuses'; +$provided='you provided'; + +setcookie('verbose_client_id','',$cooptsx); +setcookie('verbose_client_secret','',$cooptsx); + +(isset($_POST['host'])) ? $_POST['host']=trim($_POST['host']) : $_POST['host']=''; +(isset($_POST['token'])) ? $_POST['token']=trim($_POST['token']) : $_POST['token']=''; + +if (isset($_GET['code']) && isset($_COOKIE['verbose_host']) && isset($_COOKIE['verbose_client_id']) && isset($_COOKIE['verbose_client_secret'])) { + $host=$_COOKIE['verbose_host']; + $postcont=['grant_type'=>'authorization_code', 'code'=>$_GET['code'], 'client_id'=>$_COOKIE['verbose_client_id'], 'client_secret'=>$_COOKIE['verbose_client_secret'], 'redirect_uri'=>$rediruri, 'scope'=>$scope]; + $res=mastpost($host,null,'/oauth/token',$postcont,$timeout); + if ($res['ok']) { + $res=$res['data']; + $token=$res['access_token']; + setcookie('verbose_token',$token,$coopts); + $authmsgs.="
You successfully authorized {$sname} to use your account on {$host}.
\nYour access token is {$token}
\nPlease save it somewhere safe, like in a password manager (i suggest KeePassXC), because {$sname} does not save it on the server, but only in a cookie in your browser that lasts one year since your last visit, so it may expire or you may loose it and, without your access token, you would have to repeat the authorization process.
\n"; + } else { + $authmsgs.='
Sorry, there was an error trying to authorize you: '.htmlentities($res['error'])."
\n"; + } +} elseif (isset($_POST['act']) && $_POST['act']=='login' && $_POST['host']!='' && $_POST['token']=='') { + $host=$_POST['host']; + $postcont=['client_name'=>'Verbose','redirect_uris'=>$rediruri,'scopes'=>$scope,'website'=>'https://git.lattuga.net/pongrebio/verbose']; + $res=mastpost($host,$token,'/api/v1/apps',$postcont,$timeout); + //$debug.='AUTH RESULTS: '.preprint($res); + if ($res['ok']) { + $res=$res['data']; + setcookie('verbose_host',$host,$coopts); + setcookie('verbose_client_id',$res['client_id'],$coopts); + setcookie('verbose_client_secret',$res['client_secret'],$coopts); + $location='https://'.$host.'/oauth/authorize?client_id=' . $res['client_id'] . '&redirect_uri=' . urlencode($rediruri) . '&response_type=code&scope='. urlencode($postcont['scopes']) . '&force_login=0&lang='.$blang; + if ($debug) + header('Location: '.$location,true,302); + else + echo "".htmlentities($location)."
\n"; + exit(0); + } else { + $authmsgs.='
Sorry, there was an error trying to authorize you: '.htmlentities($res['error'])."
\n"; + } +} elseif (isset($_POST['act']) && $_POST['act']=='login' && $_POST['host']!='' && $_POST['token']!='') { + $host=$_POST['host']; + $token=$_POST['token']; +} elseif (isset($_POST['act']) && $_POST['act']=='logout') { + setcookie('verbose_host','',$cooptsx); + setcookie('verbose_token','',$cooptsx); + $_COOKIE=[]; + //$authmsgs.="
You have been successfully logged out :-)
\n"; +} elseif (isset($_COOKIE['verbose_host']) && isset($_COOKIE['verbose_token'])) { + $host=$_COOKIE['verbose_host']; + $token=$_COOKIE['verbose_token']; + $provided='provided by your cookies'; +} + +(isset($_POST['round'])) ? $_POST['round']++ : $_POST['round']=0; + +$loggedin=false; +if ($host!==false && $token!==false && trim($token)!='') {// trim($token)!='' is to keep it usable without js + $res=mastget($host,$token,'/api/v1/apps/verify_credentials',$timeout); + //$debug.='TOKEN: '.$token."
\nRES:".preprint($res); + if ($res['ok']) { + $myacc=mastget($host,$token,'/api/v1/accounts/verify_credentials',$timeout); + //$debug.=preprint($myacc); + if ($myacc['ok']) { + setcookie('verbose_host',$host,$coopts); + setcookie('verbose_token',$token,$coopts); + $myacc=$myacc['data']; + $loggedin=true; + if ($_POST['round']==0 || $_POST['act']=='login') { + $res=mastget($host,$token,'/api/v1/instance',$timeout); + if ($res['ok'] && isset($res['data']['configuration']['statuses']['max_characters']) && preg_match('#^\d+$#',$res['data']['configuration']['statuses']['max_characters'])===1) { + $avchars=$res['data']['configuration']['statuses']['max_characters']+0; + } else { + $authmsgs.="
Sorry, {$sname} could not detect how many characters per post your instance allows.
\n"; + } + } + } else { + $authmsgs.='
Sorry, verification of your account’s credentials failed (error: '.htmlentities($myacc['error']).")
\n"; + setcookie('verbose_host','',$cooptsx); + setcookie('verbose_token','',$cooptsx); + } + } else { + $authmsgs.="
Sorry, authentication with the credentials {$provided} failed (error: ".htmlentities($res['error']).")
\n"; + setcookie('verbose_host','',$cooptsx); + setcookie('verbose_token','',$cooptsx); + } +} + +$splitmsgs=''; + +if (isset($avchars)) { + $_POST['avchars']=$avchars; +} elseif (isset($_POST['avchars'])) { + //if (!$loggedin) $authmsgs='
Warning: if you authorize now you’ll loose your current split session
'; + (preg_match('#^\d+$#',$_POST['avchars'])===1) ? $_POST['avchars']+=0 : $_POST['avchars']=DEFAC; +} else { + $_POST['avchars']=DEFAC; +} +if ($_POST['avchars']MAXAC) $_POST['avchars']=MAXAC; +$maxcwprec=$_POST['avchars']-MINCL; +if (!isset($_POST['cw']) || trim($_POST['cw'])=='') $_POST['cw']=''; +$cwlen=mb_strlen($_POST['cw'],'UTF-8'); +if (!isset($_POST['pre']) || trim($_POST['pre'])=='') $_POST['pre']=''; +$prelen=mb_strlen($_POST['pre'],'UTF-8')+2; +$cwprelen=$cwlen+$prelen; +if ($cwprelen>$maxcwprec) $splitmsgs.="
“Content Warning” + “Text to prepend” is {$cwprelen} characters long, that is ".($cwprelen-$maxcwprec)." characters longer than its maximum allowed length ({$maxcwprec} characters, that is {$_POST['avchars']} available characters minus ".MINCL." characters)
\n"; +//$_POST['pre']=mb_substr($_POST['pre'],0,$maxprec,'UTF-8'); + +if (!isset($_POST['post']) || trim($_POST['post'])=='') $_POST['post']=''; +$postlen=mb_strlen($_POST['post'],'UTF-8'); +if ($postlen>MAXPOSTC) $splitmsgs.="
“Post to split” is {$postlen} characters long, that is ".($postlen-MAXPOSTC)." characters longer than its maximum allowed length, ".MAXPOSTC." characters
\n"; +//$_POST['post']=mb_substr($_POST['post'],0,MAXPOSTC,'UTF-8'); + +$rbsck=[ + 'cntpos_before'=>'', + 'cntpos_after'=>'', + 'addref_no'=>'', + 'addref_ifav'=>'', +]; + +if (isset($_POST['cntpos']) && $_POST['cntpos']=='before') { + $cntbef=true; + $rbsck['cntpos_before']=' checked'; +} else { + $_POST['cntpos']='after'; + $cntbef=false; + $rbsck['cntpos_after']=' checked'; +} + +if (isset($_POST['addref']) && $_POST['addref']=='no') { + $aliif=false;// "add link if it fits" + $rbsck['addref_no']=' checked'; +} else { + $_POST['addref']='ifav'; + $aliif=true; + $rbsck['addref_ifav']=' checked'; +} + +if (!isset($_POST['replyto']) || trim($_POST['replyto'])=='') $_POST['replyto']=''; +if (strlen($_POST['replyto'])>MAXREPTOLEN) { + $_POST['replyto']=''; + $splitmsgs.='
The “URL of post to reply to” you specified is too long (its maximum allowed length is '.MAXREPTOLEN." characters)
\n"; +} +if (!isset($_POST['lang']) || trim($_POST['lang'])=='') $_POST['lang']=$blang; +if (!isset($_POST['visib']) || trim($_POST['visib'])=='') $_POST['visib']='public'; + +if ($_POST['round']==0) { + $_POST['setcws']='1'; + $setcwsck=' checked'; + $_POST['setments']='1'; + $setmentsck=' checked'; + $_POST['setvisib']='1'; + $setvisibck=' checked'; + $_POST['setlang']='1'; + $setlangck=' checked'; +} else { + (isset($_POST['setcws']) && $_POST['setcws']=='1') ? $setcwsck=' checked' : $setcwsck=''; + (isset($_POST['setments']) && $_POST['setments']=='1') ? $setmentsck=' checked' : $setmentsck=''; + (isset($_POST['setvisib']) && $_POST['setvisib']=='1') ? $setvisibck=' checked' : $setvisibck=''; + (isset($_POST['setlang']) && $_POST['setlang']=='1') ? $setlangck=' checked' : $setlangck=''; +} + +function getpostmentions($post,$exclude,$format) { + $ments=[]; + if (isset($post['account']['acct']) && !in_array($post['account']['acct'],$exclude)) + $ments[]=$post['account']['acct']; + if (isset($post['mentions']) && is_array($post['mentions'])) + foreach ($post['mentions'] as $ment) + if (isset($ment['acct']) && !in_array($ment['acct'],$exclude) && !in_array($ment['acct'],$ments)) + $ments[]=$ment['acct']; + if ($format=='array') + return $ments; + elseif ($format=='list') + return implode(' ',$ments); + elseif ($format=='recipients') + return '@'.implode(' @',$ments); +} + +$reptoid=''; + +if ($loggedin && $_POST['replyto']!='') { + //https://livellosegreto.it/@geranio/110627337076323759 + $res=mastget($host,$token,'/api/v2/search?type=statuses&resolve=1&limit=1&q='.urlencode($_POST['replyto']),$timeout); + $debug.='accsearch res:
'.preprint($res)."
\n"; + if ($res['ok']) { + if (isset($res['data']['statuses'][0]['id'])) + $reptopost=$res['data']['statuses'][0]; + else + $splitmsgs.="
Sorry, {$sname} found no post with the URL you provided as “URL of post to reply to”, please check it and make sure it points to a post you have access to.
\n"; + } else { + $splitmsgs.="
Sorry, {$sname} encountered the following error while trying to retrieve the URL you provided as “URL of post to reply to”: ".htmlentities($res['error'])."; please check the URL and make sure it points to a post you have access to.
\n"; + } + if (isset($reptopost)) { + if (isset($reptopost['id'])) + $reptoid=$reptopost['id']; + if (isset($_POST['setcws']) && $_POST['setcws']=='1' && isset($reptopost['spoiler_text']) && trim($reptopost['spoiler_text'])!='') + $_POST['cw']=$reptopost['spoiler_text']; + if (isset($_POST['setments']) && $_POST['setments']=='1' && isset($reptopost['mentions'])) + $_POST['pre']=getpostmentions($reptopost,[$myacc['acct']],'recipients'); + if (isset($_POST['setvisib']) && $_POST['setvisib']=='1' && isset($reptopost['visibility'])) + $_POST['visib']=$reptopost['visibility']; + if (isset($_POST['setlang']) && $_POST['setlang']=='1' && isset($reptopost['language'])) + $_POST['lang']=$reptopost['language']; + } +} + +$headmsgs=''; + +$intro="

Hello, this is a {$sname} instance, it can split your long post considering Mastodon rules: every http(s) link counts for 23 characters and every mention counts only for its username part. Each split post comes with “…” signs where it makes sense and with a counter in “[n/t]” form, where “n” is current post number and “t” is total posts number.

+

{$sname} doesn’t save anywhere what you write or paste into it, it doesn’t 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 report them to me on Mastodon, or here.

\n"; + +$postmsgs=''; +$postsoffset=0; +$postscount=0; +$postingok=true; +$postwait=0; +$postwaituntil=0; +$pbtext='Post all'; + +if (isset($_POST['postsoffset']) && preg_match('#^\d+$#',$_POST['postsoffset'])===1) + $postsoffset=$_POST['postsoffset']+0; + +if ($loggedin && isset($_POST['postwaituntil']) && preg_match('#^\d+$#',$_POST['postwaituntil'])===1 && isset($_POST['postscount']) && preg_match('#^\d+$#',$_POST['postscount'])===1 && isset($_POST['act']) && $_POST['act']=='sendposts' && isset($_POST['cw']) && isset($_POST['post_'.$postsoffset]) && isset($_POST['mastlen_'.$postsoffset])) { + $postscount=$_POST['postscount']+0; + if (isset($_POST['lastsentpostid']) && $_POST['lastsentpostid']!='') + $lspostid=$_POST['lastsentpostid']; + $posts=[]; + $i=$postsoffset; + do { + $_POST['post_'.$i]=str_replace("\r\n","\n",$_POST['post_'.$i]); + $posts[$i]=['cw'=>$_POST['cw'],'post'=>$_POST['post_'.$i],'mastlen'=>$_POST['mastlen_'.$i]]; + $i++; + } while (isset($_POST['post_'.$i])); + if ($now<$_POST['postwaituntil']) { + $debug.="WAIT MORE!!!
\n"; + $postingok=false; + $postwait=$_POST['postwaituntil']-$now+1; + $postwaituntil=$_POST['postwaituntil']+1; + $postmsgs.="
Sorry, you have to wait ".ght($postwait)." more before {$sname} can post the remaining split post(s).
\n"; + $pbtext='Please wait '.ght($postwait).' before posting'; + } else { + for ($i=$postsoffset; $i<$postscount; $i++) { + if (time()-$now+1>=$conf['webservertimeout']) { + $postingok=false; + $postsoffset=$i; + $postmsgs.="
{$sname} could not post all the split posts because «{$_SERVER['SERVER_NAME']}» HTTP server was reaching its timeout (".ght($conf['webservertimeout']).'). You can send the remaining split posts '.($i+1)."-{$postscount} below.
\n"; + break; + } else { + $post=$posts[$i]; + $cont=[ + 'status'=>$post['post'], + 'visibility'=>$_POST['visib'], + 'language'=>$_POST['lang'] + ]; + if ($post['cw']!='') + $cont['spoiler_text']=$post['cw']; + if ($i==0 && $reptoid!='') + $cont['in_reply_to_id']=$reptoid; + elseif (isset($lspostid)) + $cont['in_reply_to_id']=$lspostid; + if ($i>0 && $_POST['visib']=='public') + $cont['visibility']='unlisted'; + //echo ($i+1).preprint($cont)."
\n"; + $res=mastpost($host,$token,'/api/v1/statuses',$cont,$timeout); + //if ($i==2) $res=['ok'=>false,'error'=>'server exploded'];// test err + //$res=['ok'=>true,'data'=>['id'=>'12345']];// test err + if ($res['ok']) { + $lspostid=$res['data']['id']; + $debug.="lspostid: {$lspostid}
\n"; + $rls=ckratelimit($res['headers'],'necho',true,false); + //if ($i==2) $rls=['remaining'=>0,'secstoreset'=>3895];// test err + //$rls=['remaining'=>0,'secstoreset'=>5];// test err + if (!is_null($rls)) { + if ($rls['remaining']<=REQSTOPRESV && $i<$postscount-1) { + $postingok=false; + $postwait=$rls['secstoreset']; + $postwaituntil=time()+$postwait; + $postsoffset=$i+1; + $postmsgs.='
Sending split post '.($i+1)."/{$postscount}, {$sname} reached your {$host}’s account posting rate limit, so it stopped sending; you’ll find the rest of your split posts (those which have not been sent, ".($i+2)."-{$postscount}) below; before posting them, you’ll have to wait ".ght($postwait)." for rate limit reset.
\n"; + $pbtext='Please wait '.ght($postwait).' before posting'; + break; + } + } + if ($i<$postscount-1) + usleep(POSTPAUSE*1000); + } else { + $postingok=false; + $postsoffset=$i; + $postmsgs.='
Trying to send split post '.($i+1)."/{$postscount}, {$sname} encountered the following error: «".htmlentities($res['error']).'»; so it stopped sending. You can (re)try sending split posts '.($i+1)."-{$postscount} below.
\n"; + break; + } + } + } + if ($postingok) { + $postmsgs.="
{$sname} successfully sent {$i}/{$postscount} split posts :-)
\n"; + $posts=[]; + $postscount=0; + } + } +} + +$replyto=''; +$language=''; +$displang=''; +$visibility=''; +$dispvisib=''; + +if (!$loggedin) { + $auth="

Connect?

+{$authmsgs} +
+

If you don’t want to connect {$sname} to your account, you can just skip to the “Split post” section, but after splitting your long post you’ll have to copy and paste by hand each split post in sequence into Mastodon (i recommend “chain posting”, that is replying to the first post with the second, to the second with the third, and so on).

+

If you choose instead to connect {$sname} to your account, you’ll be able to post all split posts at once directly from within {$sname}, they will be automatically “chain posted” and, before posting them, you may set their visibility, language, and a post to reply to with the first split post.

+

 

+

To connect {$sname} to your account

+
    +
  • if you already have an authentication token you just need to specify your server’s hostname and your token (your current split session will be unaffected);
  • +
  • if you have not an authentication token yet, you first have to authorize {$sname} to access your account by specifying only your server’s hostname, leaving the “Your token” field empty; then you’ll be guided through a very quick and easy one-time authorization process, at the end of which {$sname} will tell you your token for future connections, and will be already connected to your account - but please note that this process will make you loose your current split session.
  • +
+
+
+".oldpost2hid()." +
+
+
+
\n"; + $replyto=''; + $visibility=''; + $language=''; +} else { + $auth="

Connected :-)

\n{$authmsgs}\n
\n".oldpost2hid()."
{$sname} is connected to your {$myacc['acct']}@{$host} account.
\n
\n
\n"; + $replyto='
'; + $optsa=['public'=>'Public','unlisted'=>'Unlisted','private'=>'Followers-only','direct'=>'Direct']; + $buff=''; + foreach ($optsa as $key=>$val) { + if ($key==$_POST['visib']) { + $selected=' selected'; + $dispvisib=$val; + } else { + $selected=''; + } + $buff.="\n"; + } + $visibility='
'; + // taken from 'preferences' -> 'other' on mastodon 4.1.2 + $optsa=[['aa','Afar','Afaraf'],['ab','Abkhaz','аҧсуа бызшәа'],['ae','Avestan','avesta'],['af','Afrikaans','Afrikaans'],['ak','Akan','Akan'],['am','Amharic','አማርኛ'],['an','Aragonese','aragonés'],['ar','Arabic','اللغة العربية'],['as','Assamese','অসমীয়া'],['av','Avaric','авар мацӀ'],['ay','Aymara','aymar aru'],['az','Azerbaijani','azərbaycan dili'],['ba','Bashkir','башҡорт теле'],['be','Belarusian','беларуская мова'],['bg','Bulgarian','български език'],['bh','Bihari','भोजपुरी'],['bi','Bislama','Bislama'],['bm','Bambara','bamanankan'],['bn','Bengali','বাংলা'],['bo','Tibetan','བོད་ཡིག'],['br','Breton','brezhoneg'],['bs','Bosnian','bosanski jezik'],['ca','Catalan','Català'],['ce','Chechen','нохчийн мотт'],['ch','Chamorro','Chamoru'],['co','Corsican','corsu'],['cr','Cree','ᓀᐦᐃᔭᐍᐏᐣ'],['cs','Czech','čeština'],['cu','Old Church Slavonic','ѩзыкъ словѣньскъ'],['cv','Chuvash','чӑваш чӗлхи'],['cy','Welsh','Cymraeg'],['da','Danish','dansk'],['de','German','Deutsch'],['dv','Divehi','Dhivehi'],['dz','Dzongkha','རྫོང་ཁ'],['ee','Ewe','Eʋegbe'],['el','Greek','Ελληνικά'],['en','English','English'],['eo','Esperanto','Esperanto'],['es','Spanish','Español'],['et','Estonian','eesti'],['eu','Basque','euskara'],['fa','Persian','فارسی'],['ff','Fula','Fulfulde'],['fi','Finnish','suomi'],['fj','Fijian','Vakaviti'],['fo','Faroese','føroyskt'],['fr','French','Français'],['fy','Western Frisian','Frysk'],['ga','Irish','Gaeilge'],['gd','Scottish Gaelic','Gàidhlig'],['gl','Galician','galego'],['gu','Gujarati','ગુજરાતી'],['gv','Manx','Gaelg'],['ha','Hausa','هَوُسَ'],['he','Hebrew','עברית'],['hi','Hindi','हिन्दी'],['ho','Hiri Motu','Hiri Motu'],['hr','Croatian','Hrvatski'],['ht','Haitian','Kreyòl ayisyen'],['hu','Hungarian','magyar'],['hy','Armenian','Հայերեն'],['hz','Herero','Otjiherero'],['ia','Interlingua','Interlingua'],['id','Indonesian','Bahasa Indonesia'],['ie','Interlingue','Interlingue'],['ig','Igbo','Asụsụ Igbo'],['ii','Nuosu','ꆈꌠ꒿ Nuosuhxop'],['ik','Inupiaq','Iñupiaq'],['io','Ido','Ido'],['is','Icelandic','Íslenska'],['it','Italian','Italiano'],['iu','Inuktitut','ᐃᓄᒃᑎᑐᑦ'],['ja','Japanese','日本語'],['jv','Javanese','basa Jawa'],['ka','Georgian','ქართული'],['kg','Kongo','Kikongo'],['ki','Kikuyu','Gĩkũyũ'],['kj','Kwanyama','Kuanyama'],['kk','Kazakh','қазақ тілі'],['kl','Kalaallisut','kalaallisut'],['km','Khmer','ខេមរភាសា'],['kn','Kannada','ಕನ್ನಡ'],['ko','Korean','한국어'],['kr','Kanuri','Kanuri'],['ks','Kashmiri','कश्मीरी'],['ku','Kurmanji (Kurdish)','Kurmancî'],['kv','Komi','коми кыв'],['kw','Cornish','Kernewek'],['ky','Kyrgyz','Кыргызча'],['la','Latin','latine'],['lb','Luxembourgish','Lëtzebuergesch'],['lg','Ganda','Luganda'],['li','Limburgish','Limburgs'],['ln','Lingala','Lingála'],['lo','Lao','ລາວ'],['lt','Lithuanian','lietuvių kalba'],['lu','Luba-Katanga','Tshiluba'],['lv','Latvian','latviešu valoda'],['mg','Malagasy','fiteny malagasy'],['mh','Marshallese','Kajin M̧ajeļ'],['mi','Māori','te reo Māori'],['mk','Macedonian','македонски јазик'],['ml','Malayalam','മലയാളം'],['mn','Mongolian','Монгол хэл'],['mr','Marathi','मराठी'],['ms','Malay','Bahasa Melayu'],['mt','Maltese','Malti'],['my','Burmese','ဗမာစာ'],['na','Nauru','Ekakairũ Naoero'],['nb','Norwegian Bokmål','Norsk bokmål'],['nd','Northern Ndebele','isiNdebele'],['ne','Nepali','नेपाली'],['ng','Ndonga','Owambo'],['nl','Dutch','Nederlands'],['nn','Norwegian Nynorsk','Norsk Nynorsk'],['no','Norwegian','Norsk'],['nr','Southern Ndebele','isiNdebele'],['nv','Navajo','Diné bizaad'],['ny','Chichewa','chiCheŵa'],['oc','Occitan','occitan'],['oj','Ojibwe','ᐊᓂᔑᓈᐯᒧᐎᓐ'],['om','Oromo','Afaan Oromoo'],['or','Oriya','ଓଡ଼ିଆ'],['os','Ossetian','ирон æвзаг'],['pa','Panjabi','ਪੰਜਾਬੀ'],['pi','Pāli','पाऴि'],['pl','Polish','Polski'],['ps','Pashto','پښتو'],['pt','Portuguese','Português'],['qu','Quechua','Runa Simi'],['rm','Romansh','rumantsch grischun'],['rn','Kirundi','Ikirundi'],['ro','Romanian','Română'],['ru','Russian','Русский'],['rw','Kinyarwanda','Ikinyarwanda'],['sa','Sanskrit','संस्कृतम्'],['sc','Sardinian','sardu'],['sd','Sindhi','सिन्धी'],['se','Northern Sami','Davvisámegiella'],['sg','Sango','yângâ tî sängö'],['si','Sinhala','සිංහල'],['sk','Slovak','slovenčina'],['sl','Slovenian','slovenščina'],['sn','Shona','chiShona'],['so','Somali','Soomaaliga'],['sq','Albanian','Shqip'],['sr','Serbian','српски језик'],['ss','Swati','SiSwati'],['st','Southern Sotho','Sesotho'],['su','Sundanese','Basa Sunda'],['sv','Swedish','Svenska'],['sw','Swahili','Kiswahili'],['ta','Tamil','தமிழ்'],['te','Telugu','తెలుగు'],['tg','Tajik','тоҷикӣ'],['th','Thai','ไทย'],['ti','Tigrinya','ትግርኛ'],['tk','Turkmen','Türkmen'],['tl','Tagalog','Wikang Tagalog'],['tn','Tswana','Setswana'],['to','Tonga','faka Tonga'],['tr','Turkish','Türkçe'],['ts','Tsonga','Xitsonga'],['tt','Tatar','татар теле'],['tw','Twi','Twi'],['ty','Tahitian','Reo Tahiti'],['ug','Uyghur','ئۇيغۇرچە‎'],['uk','Ukrainian','Українська'],['ur','Urdu','اردو'],['uz','Uzbek','Ўзбек'],['ve','Venda','Tshivenḓa'],['vi','Vietnamese','Tiếng Việt'],['vo','Volapük','Volapük'],['wa','Walloon','walon'],['wo','Wolof','Wollof'],['xh','Xhosa','isiXhosa'],['yi','Yiddish','ייִדיש'],['yo','Yoruba','Yorùbá'],['za','Zhuang','Saɯ cueŋƅ'],['zh','Chinese','中文'],['zu','Zulu','isiZulu'],['ast','Asturian','Asturianu'],['ckb','Sorani (Kurdish)','سۆرانی'],['cnr','Montenegrin','crnogorski'],['jbo','Lojban','la .lojban.'],['kab','Kabyle','Taqbaylit'],['kmr','Kurmanji (Kurdish)','Kurmancî'],['ldn','Láadan','Láadan'],['lfn','Lingua Franca Nova','lingua franca nova'],['sco','Scots','Scots'],['sma','Southern Sami','Åarjelsaemien Gïele'],['smj','Lule Sami','Julevsámegiella'],['szl','Silesian','ślůnsko godka'],['tai','Tai','ภาษาไท or ภาษาไต'],['tok','Toki Pona','toki pona'],['zba','Balaibalan','باليبلن'],['zgh','Standard Moroccan Tamazight','ⵜⴰⵎⴰⵣⵉⵖⵜ']]; + $buff=''; + foreach ($optsa as $val) { + if ($val[0]==$_POST['lang']) { + $displang=$val[2].' ('.$val[1].')'; + $selected=' selected'; + } else { + $selected=''; + } + $buff.="\n"; + } + $language="
\n"; + unset($buff,$optsa); +} +$auth.="
\n"; + +if ($dodebug) { + $debug='
'.$debug.'
'; + $svers.='-'.rand(10000,999999); +} else { + $debug=''; +} + +(isset($_POST['act']) && $_POST['act']=='split') ? $splitfh='' : $splitfh=' class="fullheight"'; + +header('Content-Language: en'); + +echo " + + +{$sname}: a post splitter for Mastodon + + + + + + + + + + + + + + + +
Elo!
+
+
+
Uz!
+
Elo!
+
+
+{$debug} +
+{$headmsgs} +

{$sname}: a post splitter for Mastodon

+{$intro} +
+{$auth} + +

Split post

+{$splitmsgs} +
+ +
+{$replyto} +
+
+{$visibility} +{$language} +
+ +
+
+
+
+
+
+
+
+
+
+
+
+
\n
\n
\n"; + +if ($splitmsgs=='') { + if (isset($_POST['act']) && $_POST['act']=='split' && $_POST['post']!='') { + $linklen=postlength($conf['link']); + ($_POST['pre']!='') ? $pre=$_POST['pre']."\n\n" : $pre=''; + $posts=splitpost($_POST['post'],$_POST['avchars'],$_POST['cw'],$pre,$cntbef); + $postscount=count($posts); + $errposts=[]; + for ($i=0; $i<$postscount; $i++) + if ($posts[$i]['mastlen']>$_POST['avchars']) $errposts[]="{$i}"; + $epc=count($errposts); + if ($epc>0) { + if ($epc==1) + echo "

Sorry, the length of one split post (namely {$errposts[0]}) exceeds {$_POST['avchars']} characters."; + else + echo "

Sorry, the length of {$epc} split posts (namely ".implode(', ',$errposts).") exceeds {$_POST['avchars']} characters."; + echo " Please report this bug here, possibly citing the original post text.

\n"; + } + if ($postscount>1 && $aliif && $posts[$postscount-1]['mastlen']+$linklen<=$_POST['avchars']) { + $posts[$postscount-1]['post'].=$conf['link']; + $posts[$postscount-1]['mastlen']+=$linklen; + } + } + if ($postscount>0) { + //if ($loggedin) $postmsgs.="
“Post all” button is at the bottom ;-)
\n"; + echo "
\n

Split results

\n{$postmsgs}
\n"; + $info='Content Warning: '; + ($posts[$postsoffset]['cw']!='') ? $info.=htmlentities($_POST['cw']) : $info.='not set'; + if ($loggedin) $info.='
Visibility: '.$dispvisib.'
Language: '.htmlentities($displang); + ($loggedin) ? $postdivclass='postdivnobut' : $postdivclass='postdiv'; + for ($i=$postsoffset; $i<$postscount; $i++) { + $io=$i+1; + ($posts[$i]['mastlen']>$_POST['avchars']) ? $phclass='errposthead' : $phclass='posthead'; + echo "
Post {$io}/{$postscount} (“Mastodon length”: {$posts[$i]['mastlen']}; real length: ".(mb_strlen($posts[$i]['post'],'UTF-8')+$cwlen).")
\n
{$info}
\n
".nl2br(htmlentities($posts[$i]['post']))."
\n
\n
\n"; + } + if ($loggedin) { + if (!isset($lspostid)) $lspostid=''; + echo "
\n
\n
\n
\n
\n
\n".oldpost2hid()."\n\n\n\n\n"; + } + echo "
\n"; + } elseif ($postmsgs!='') { + echo "
\n{$postmsgs}"; + } +} + +if (isset($conf['footer'])) + echo "
{$conf['footer']}
\n"; + +echo " + + +\n"; + +function preprint($var) { + return '
'.print_r($var,true)."
\n"; +} + +function necho($var) { + // do nothing :-) +} + +function dieyoung($msg) { + echo $msg; + exit(1); +} + +function cb2hid($pkey) { + if (isset($_POST[$pkey]) && $_POST[$pkey]=='1') + return '1'; + else + return '0'; +} + +function oldpost2hid() { + return "\n\n\n\n\n\n\n\n\n\n\n\n\n\n"; +} + +?> diff --git a/js/main.js b/js/main.js new file mode 100644 index 0000000..948c501 --- /dev/null +++ b/js/main.js @@ -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; +} diff --git a/lib/booltostr.php b/lib/booltostr.php new file mode 100644 index 0000000..08bc0cd --- /dev/null +++ b/lib/booltostr.php @@ -0,0 +1,8 @@ + diff --git a/lib/ckratelimit.php b/lib/ckratelimit.php new file mode 100644 index 0000000..184bbfd --- /dev/null +++ b/lib/ckratelimit.php @@ -0,0 +1,36 @@ +$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; +} +?> diff --git a/lib/getfirstbrowserlang.php b/lib/getfirstbrowserlang.php new file mode 100644 index 0000000..37318d1 --- /dev/null +++ b/lib/getfirstbrowserlang.php @@ -0,0 +1,20 @@ + diff --git a/lib/gettlds.php b/lib/gettlds.php new file mode 100644 index 0000000..c8106f3 --- /dev/null +++ b/lib/gettlds.php @@ -0,0 +1,44 @@ +86400 || !file_exists($tldsfp)) {// if more than 1 day has passed since last list dl or list file can't be found + $url='https://data.iana.org/TLD/tlds-alpha-by-domain.txt'; + $buf=@file_get_contents($url); + if ($buf===false) + echo "gettlds: could not download «{$url}»\n"; + elseif (@file_put_contents($tldsfp,$buf)===false) + echo "gettlds: could not save «{$tldsfp}»\n"; + elseif (@file_put_contents($ldlfp,$now."\n")===false) + echo "gettlds: could not save «{$ldlfp}»\n"; + } + if (!isset($buf)) + $buf=@file_get_contents($tldsfp); + if ($buf!==false) { + $tlds=[]; + $buf=explode("\n",$buf); + foreach ($buf as $val) + if (trim($val)!=='' && $val[0]!='#') + $tlds[]=$val; + rsort($tlds); + foreach ($tlds as $key=>$val) + $tlds[$key]=strtolower($val); + } + return $tlds; +} + +?> diff --git a/lib/ght.php b/lib/ght.php new file mode 100644 index 0000000..32844ab --- /dev/null +++ b/lib/ght.php @@ -0,0 +1,50 @@ +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; +} + +?> diff --git a/lib/mastodon.php b/lib/mastodon.php new file mode 100644 index 0000000..8516ce0 --- /dev/null +++ b/lib/mastodon.php @@ -0,0 +1,282 @@ +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$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 '
'.print_r($spost,true).'
'; + 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); + } + } + } +} + +?> diff --git a/post.php b/post.php new file mode 100644 index 0000000..546bf09 --- /dev/null +++ b/post.php @@ -0,0 +1,60 @@ +. +*/ + +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 '
'.print_r($_POST,true).'
'; + +function necho($msg) { + // do nothing :-) +} + +?>