index.php 50 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847
  1. <?php
  2. /*
  3. This program is free software: you can redistribute it and/or modify
  4. it under the terms of the GNU General Public License as published by
  5. the Free Software Foundation, either version 3 of the License, or
  6. (at your option) any later version.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU General Public License for more details.
  11. You should have received a copy of the GNU General Public License
  12. along with this program. If not, see <http://www.gnu.org/licenses/>.
  13. */
  14. // Logorrh, Verbose, Charliero, Tashugo?
  15. require 'lib/gettlds.php';
  16. $tlds=gettlds();
  17. $retlds=implode('|',$tlds);
  18. $jstlds="const tlds=['".implode("', '",$tlds)."'];\n";
  19. if (@file_put_contents('js/tlds.js',$jstlds)===false)
  20. dieyoung('Error: could not save «js/tlds.js»');
  21. unset($tlds,$jstlds);
  22. require 'lib/mastodon.php';
  23. require 'lib/ckratelimit.php';
  24. require 'lib/getfirstbrowserlang.php';
  25. require 'lib/ght.php';
  26. require 'lib/booltostr.php';
  27. const SNAME='Verbose';
  28. const SVERS='0.3.6';
  29. const SREPO='https://git.lattuga.net/pongrebio/verbose';
  30. const DEFAC=500;
  31. const MINAC=100;
  32. const MAXAC=100000;
  33. const MINCL=40;
  34. const MAXPOSTC=50000;
  35. const MAXREPTOLEN=1500;
  36. const POSTPAUSE=250;// pause between sending split posts (in milliseconds)
  37. const REQSTOPRESV=10;// requests to preserve
  38. const CONFFN='conf.ini';
  39. $sname=SNAME;
  40. $svers=SVERS;
  41. $now=time();
  42. $timeout=5;
  43. $formact=preg_replace('#[^/]+$#','',$_SERVER['REQUEST_URI']);
  44. $dodebug=false;
  45. $debug='';
  46. ini_set('max_execution_time',360);// 6 minutes: default mastodon ratelimits: https://docs.joinmastodon.org/api/rate-limits/
  47. $conf=['link'=>"\n\n[This post was split using ".SREPO.']'];
  48. if (file_exists(CONFFN)) {
  49. $fconf=@parse_ini_file(CONFFN,false,INI_SCANNER_RAW);
  50. if ($fconf===false) {
  51. dieyoung('Error: configuration file «'.CONFFN.'» was found but could not be opened for reading.');
  52. } else {
  53. if (isset($fconf['link']))
  54. $conf['link']="\n\n{$fconf['link']}";
  55. if (isset($fconf['footer']))
  56. $conf['footer']=$fconf['footer'];
  57. if (isset($fconf['webservertimeout'])) {
  58. if (preg_match('#^\d+$#',$fconf['webservertimeout'])===1)
  59. $conf['webservertimeout']=$fconf['webservertimeout']+0;
  60. else
  61. dieyoung('Error: configuration file «'.CONFFN.'»: value «'.$fconf['webservertimeout'].'» is not valid for «webservertimeout».');
  62. } else {
  63. dieyoung('Error: configuration file «'.CONFFN.'» does not define «webservertimeout».');
  64. }
  65. }
  66. unset($fconf);
  67. } else {
  68. dieyoung('Error: configuration file «'.CONFFN.'» was not found.');
  69. }
  70. $debug.='CONF: '.preprint($conf)."<br>\n";
  71. $coopts=[
  72. 'expires'=>$now+365*24*60*60,
  73. 'path'=>preg_replace('#[^/]+$#','',$_SERVER['REQUEST_URI']),
  74. 'domain'=>$_SERVER['SERVER_NAME'],
  75. 'secure'=>false,
  76. 'httponly'=>false,
  77. 'samesite'=>'Lax'// None || Lax || Strict
  78. ];
  79. $cooptsx=$coopts;
  80. $cooptsx['expires']=$now-3600;
  81. /*setcookie('verbose_client_id','',$cooptsx);
  82. setcookie('verbose_client_secret','',$cooptsx);
  83. setcookie('verbose_host','',$cooptsx);
  84. setcookie('verbose_token','',$cooptsx);*/
  85. $debug.='COOKIES: '.preprint($_COOKIE);
  86. $debug.='POST: '.preprint($_POST);
  87. $blang=getfirstbrowserlang();
  88. $blang=preg_replace('#^([a-z]+).*#','$1',$blang);
  89. $host=false;
  90. $token=false;
  91. (isset($_SERVER['HTTPS'])) ? $proto='https' : $proto='http';
  92. $rediruri=$proto.'://'.$_SERVER['HTTP_HOST'].preg_replace('#^([^?]*).*#','$1',$_SERVER['REQUEST_URI']);
  93. $authmsgs='';
  94. $scope='read write:statuses';
  95. $provided='you provided';
  96. setcookie('verbose_client_id','',$cooptsx);
  97. setcookie('verbose_client_secret','',$cooptsx);
  98. (isset($_POST['host'])) ? $_POST['host']=trim($_POST['host']) : $_POST['host']='';
  99. (isset($_POST['token'])) ? $_POST['token']=trim($_POST['token']) : $_POST['token']='';
  100. if (isset($_GET['code']) && isset($_COOKIE['verbose_host']) && isset($_COOKIE['verbose_client_id']) && isset($_COOKIE['verbose_client_secret'])) {
  101. $host=$_COOKIE['verbose_host'];
  102. $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];
  103. $res=mastpost($host,null,'/oauth/token',$postcont,$timeout);
  104. if ($res['ok']) {
  105. $res=$res['data'];
  106. $token=$res['access_token'];
  107. setcookie('verbose_token',$token,$coopts);
  108. $authmsgs.="<div class=\"normtext\">You successfully authorized {$sname} to use your account on {$host}.<br>\nYour access token is <span class=\"hili\">{$token}</span><br>\nPlease save it somewhere safe, like in a password manager (i suggest <a href=\"https://keepassxc.org/\">KeePassXC</a>), 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.</div>\n";
  109. } else {
  110. $authmsgs.='<div class="error">Sorry, there was an error trying to authorize you: '.htmlentities($res['error'])."</div>\n";
  111. }
  112. } elseif (isset($_POST['act']) && $_POST['act']=='login' && $_POST['host']!='' && $_POST['token']=='') {
  113. $host=$_POST['host'];
  114. $postcont=['client_name'=>'Verbose','redirect_uris'=>$rediruri,'scopes'=>$scope,'website'=>'https://git.lattuga.net/pongrebio/verbose'];
  115. $res=mastpost($host,$token,'/api/v1/apps',$postcont,$timeout);
  116. //$debug.='AUTH RESULTS: '.preprint($res);
  117. if ($res['ok']) {
  118. $res=$res['data'];
  119. setcookie('verbose_host',$host,$coopts);
  120. setcookie('verbose_client_id',$res['client_id'],$coopts);
  121. setcookie('verbose_client_secret',$res['client_secret'],$coopts);
  122. $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;
  123. if ($debug)
  124. header('Location: '.$location,true,302);
  125. else
  126. echo "<a href=\"{$location}\">".htmlentities($location)."</a><br>\n";
  127. exit(0);
  128. } else {
  129. $authmsgs.='<div class="error">Sorry, there was an error trying to authorize you: '.htmlentities($res['error'])."</div>\n";
  130. }
  131. } elseif (isset($_POST['act']) && $_POST['act']=='login' && $_POST['host']!='' && $_POST['token']!='') {
  132. $host=$_POST['host'];
  133. $token=$_POST['token'];
  134. } elseif (isset($_POST['act']) && $_POST['act']=='logout') {
  135. setcookie('verbose_host','',$cooptsx);
  136. setcookie('verbose_token','',$cooptsx);
  137. $_COOKIE=[];
  138. //$authmsgs.="<div class=\"success\">You have been successfully logged out :-)</div>\n";
  139. } elseif (isset($_COOKIE['verbose_host']) && isset($_COOKIE['verbose_token'])) {
  140. $host=$_COOKIE['verbose_host'];
  141. $token=$_COOKIE['verbose_token'];
  142. $provided='provided by your cookies';
  143. }
  144. (isset($_POST['round'])) ? $_POST['round']++ : $_POST['round']=0;
  145. $loggedin=false;
  146. if ($host!==false && $token!==false && trim($token)!='') {// trim($token)!='' is to keep it usable without js
  147. $res=mastget($host,$token,'/api/v1/apps/verify_credentials',$timeout);
  148. //$debug.='TOKEN: '.$token."<br>\nRES:".preprint($res);
  149. if ($res['ok']) {
  150. $myacc=mastget($host,$token,'/api/v1/accounts/verify_credentials',$timeout);
  151. //$debug.=preprint($myacc);
  152. if ($myacc['ok']) {
  153. setcookie('verbose_host',$host,$coopts);
  154. setcookie('verbose_token',$token,$coopts);
  155. $myacc=$myacc['data'];
  156. $loggedin=true;
  157. if ($_POST['round']==0 || (isset($_POST['act']) && $_POST['act']=='login')) {
  158. $res=mastget($host,$token,'/api/v1/instance',$timeout);
  159. if ($res['ok'] && isset($res['data']['configuration']['statuses']['max_characters']) && preg_match('#^\d+$#',$res['data']['configuration']['statuses']['max_characters'])===1) {
  160. $avchars=$res['data']['configuration']['statuses']['max_characters']+0;
  161. } else {
  162. $authmsgs.="<div class=\"warning\">Sorry, {$sname} could not detect how many characters per post your instance allows.</div>\n";
  163. }
  164. }
  165. } else {
  166. $authmsgs.='<div class="error">Sorry, verification of your account’s credentials failed (error: '.htmlentities($myacc['error']).")</div>\n";
  167. setcookie('verbose_host','',$cooptsx);
  168. setcookie('verbose_token','',$cooptsx);
  169. }
  170. } else {
  171. $authmsgs.="<div class=\"error\">Sorry, authentication with the credentials {$provided} failed (error: ".htmlentities($res['error']).")</div>\n";
  172. setcookie('verbose_host','',$cooptsx);
  173. setcookie('verbose_token','',$cooptsx);
  174. }
  175. }
  176. $splitmsgs='';
  177. if (isset($avchars)) {
  178. $_POST['avchars']=$avchars;
  179. } elseif (isset($_POST['avchars'])) {
  180. //if (!$loggedin) $authmsgs='<div class="warning">Warning: if you authorize now you’ll loose your current split session</div>';
  181. (preg_match('#^\d+$#',$_POST['avchars'])===1) ? $_POST['avchars']+=0 : $_POST['avchars']=DEFAC;
  182. } else {
  183. $_POST['avchars']=DEFAC;
  184. }
  185. if ($_POST['avchars']<MINAC) $_POST['avchars']=MINAC;
  186. if ($_POST['avchars']>MAXAC) $_POST['avchars']=MAXAC;
  187. $maxcwprec=$_POST['avchars']-MINCL;
  188. if (!isset($_POST['cw']) || trim($_POST['cw'])=='') $_POST['cw']='';
  189. $cwlen=mb_strlen($_POST['cw'],'UTF-8');
  190. if (!isset($_POST['pre']) || trim($_POST['pre'])=='') $_POST['pre']='';
  191. $prelen=mb_strlen($_POST['pre'],'UTF-8')+2;
  192. $cwprelen=$cwlen+$prelen;
  193. if ($cwprelen>$maxcwprec) $splitmsgs.="<div class=\"error\">“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)</div>\n";
  194. //$_POST['pre']=mb_substr($_POST['pre'],0,$maxprec,'UTF-8');
  195. if (!isset($_POST['post']) || trim($_POST['post'])=='') $_POST['post']='';
  196. $postlen=mb_strlen($_POST['post'],'UTF-8');
  197. if ($postlen>MAXPOSTC) $splitmsgs.="<div class=\"error\">“Post to split” is {$postlen} characters long, that is ".($postlen-MAXPOSTC)." characters longer than its maximum allowed length, ".MAXPOSTC." characters</div>\n";
  198. //$_POST['post']=mb_substr($_POST['post'],0,MAXPOSTC,'UTF-8');
  199. $rbsck=[
  200. 'cntpos_before'=>'',
  201. 'cntpos_after'=>'',
  202. 'addref_no'=>'',
  203. 'addref_ifav'=>'',
  204. ];
  205. if (isset($_POST['cntpos']) && $_POST['cntpos']=='before') {
  206. $cntbef=true;
  207. $rbsck['cntpos_before']=' checked';
  208. } else {
  209. $_POST['cntpos']='after';
  210. $cntbef=false;
  211. $rbsck['cntpos_after']=' checked';
  212. }
  213. if (isset($_POST['addref']) && $_POST['addref']=='no') {
  214. $aliif=false;// "add link if it fits"
  215. $rbsck['addref_no']=' checked';
  216. } else {
  217. $_POST['addref']='ifav';
  218. $aliif=true;
  219. $rbsck['addref_ifav']=' checked';
  220. }
  221. if (!isset($_POST['replyto']) || trim($_POST['replyto'])=='') $_POST['replyto']='';
  222. if (strlen($_POST['replyto'])>MAXREPTOLEN) {
  223. $_POST['replyto']='';
  224. $splitmsgs.='<div class="error">The “URL of post to reply to” you specified is too long (its maximum allowed length is '.MAXREPTOLEN." characters)</div>\n";
  225. }
  226. if (!isset($_POST['lang']) || trim($_POST['lang'])=='') $_POST['lang']=$blang;
  227. if (!isset($_POST['visib']) || trim($_POST['visib'])=='') $_POST['visib']='public';
  228. if ($_POST['round']==0) {
  229. $_POST['setcws']='1';
  230. $setcwsck=' checked';
  231. $_POST['setments']='1';
  232. $setmentsck=' checked';
  233. $_POST['setvisib']='1';
  234. $setvisibck=' checked';
  235. $_POST['setlang']='1';
  236. $setlangck=' checked';
  237. } else {
  238. (isset($_POST['setcws']) && $_POST['setcws']=='1') ? $setcwsck=' checked' : $setcwsck='';
  239. (isset($_POST['setments']) && $_POST['setments']=='1') ? $setmentsck=' checked' : $setmentsck='';
  240. (isset($_POST['setvisib']) && $_POST['setvisib']=='1') ? $setvisibck=' checked' : $setvisibck='';
  241. (isset($_POST['setlang']) && $_POST['setlang']=='1') ? $setlangck=' checked' : $setlangck='';
  242. }
  243. function getpostmentions($post,$exclude,$format) {
  244. $ments=[];
  245. if (isset($post['account']['acct']) && !in_array($post['account']['acct'],$exclude))
  246. $ments[]=$post['account']['acct'];
  247. if (isset($post['mentions']) && is_array($post['mentions']))
  248. foreach ($post['mentions'] as $ment)
  249. if (isset($ment['acct']) && !in_array($ment['acct'],$exclude) && !in_array($ment['acct'],$ments))
  250. $ments[]=$ment['acct'];
  251. if ($format=='array')
  252. return $ments;
  253. elseif ($format=='list')
  254. return implode(' ',$ments);
  255. elseif ($format=='recipients')
  256. return '@'.implode(' @',$ments);
  257. }
  258. $reptoid='';
  259. if ($loggedin && $_POST['replyto']!='') {
  260. //https://livellosegreto.it/@geranio/110627337076323759
  261. $res=mastget($host,$token,'/api/v2/search?type=statuses&resolve=1&limit=1&q='.urlencode($_POST['replyto']),$timeout);
  262. $debug.='accsearch res: <pre>'.preprint($res)."</pre>\n";
  263. if ($res['ok']) {
  264. if (isset($res['data']['statuses'][0]['id']))
  265. $reptopost=$res['data']['statuses'][0];
  266. else
  267. $splitmsgs.="<div class=\"error\">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.</div>\n";
  268. } else {
  269. $splitmsgs.="<div class=\"error\">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.</div>\n";
  270. }
  271. if (isset($reptopost)) {
  272. if (isset($reptopost['id']))
  273. $reptoid=$reptopost['id'];
  274. if (isset($_POST['setcws']) && $_POST['setcws']=='1' && isset($reptopost['spoiler_text']) && trim($reptopost['spoiler_text'])!='')
  275. $_POST['cw']=$reptopost['spoiler_text'];
  276. if (isset($_POST['setments']) && $_POST['setments']=='1' && isset($reptopost['mentions']))
  277. $_POST['pre']=getpostmentions($reptopost,[$myacc['acct']],'recipients');
  278. if (isset($_POST['setvisib']) && $_POST['setvisib']=='1' && isset($reptopost['visibility']))
  279. $_POST['visib']=$reptopost['visibility'];
  280. if (isset($_POST['setlang']) && $_POST['setlang']=='1' && isset($reptopost['language']))
  281. $_POST['lang']=$reptopost['language'];
  282. }
  283. }
  284. $headmsgs='';
  285. $intro="<div class=\"normtext\"><p class=\"firstp\">Hello, this is a {$sname} instance, it can split your long post considering <a href=\"https://docs.joinmastodon.org/user/posting/#text\">Mastodon rules</a>: 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.</p>
  286. <p>{$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.</p>
  287. <p>If you find issues please report them to <a href=\"https://puntarella.party/@umpi\">me on Mastodon</a>, or <a href=\"https://git.lattuga.net/pongrebio/verbose/issues\">here</a>.</p></div>\n";
  288. $postmsgs='';
  289. $postsoffset=0;
  290. $postscount=0;
  291. $postingok=true;
  292. $postwait=0;
  293. $postwaituntil=0;
  294. $pbtext='Post all';
  295. if (isset($_POST['postsoffset']) && preg_match('#^\d+$#',$_POST['postsoffset'])===1)
  296. $postsoffset=$_POST['postsoffset']+0;
  297. 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])) {
  298. $postscount=$_POST['postscount']+0;
  299. if (isset($_POST['lastsentpostid']) && $_POST['lastsentpostid']!='')
  300. $lspostid=$_POST['lastsentpostid'];
  301. $posts=[];
  302. $i=$postsoffset;
  303. do {
  304. $_POST['post_'.$i]=str_replace("\r\n","\n",$_POST['post_'.$i]);
  305. $posts[$i]=['cw'=>$_POST['cw'],'post'=>$_POST['post_'.$i],'mastlen'=>$_POST['mastlen_'.$i]];
  306. $i++;
  307. } while (isset($_POST['post_'.$i]));
  308. if ($now<$_POST['postwaituntil']) {
  309. $debug.="WAIT MORE!!!<br>\n";
  310. $postingok=false;
  311. $postwait=$_POST['postwaituntil']-$now+1;
  312. $postwaituntil=$_POST['postwaituntil']+1;
  313. $postmsgs.="<div class=\"warning\">Sorry, you have to wait ".ght($postwait)." more before {$sname} can post the remaining split post(s).</div>\n";
  314. $pbtext='Please wait '.ght($postwait).' before posting';
  315. } else {
  316. for ($i=$postsoffset; $i<$postscount; $i++) {
  317. if (time()-$now+1>=$conf['webservertimeout']) {
  318. $postingok=false;
  319. $postsoffset=$i;
  320. $postmsgs.="<div class=\"warning\">{$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.</div>\n";
  321. break;
  322. } else {
  323. $post=$posts[$i];
  324. $cont=[
  325. 'status'=>$post['post'],
  326. 'visibility'=>$_POST['visib'],
  327. 'language'=>$_POST['lang']
  328. ];
  329. if ($post['cw']!='')
  330. $cont['spoiler_text']=$post['cw'];
  331. if ($i==0 && $reptoid!='')
  332. $cont['in_reply_to_id']=$reptoid;
  333. elseif (isset($lspostid))
  334. $cont['in_reply_to_id']=$lspostid;
  335. if ($i>0 && $_POST['visib']=='public')
  336. $cont['visibility']='unlisted';
  337. //echo ($i+1).preprint($cont)."<br>\n";
  338. $res=mastpost($host,$token,'/api/v1/statuses',$cont,$timeout);
  339. //if ($i==2) $res=['ok'=>false,'error'=>'server exploded'];// test err
  340. //$res=['ok'=>true,'data'=>['id'=>'12345']];// test err
  341. if ($res['ok']) {
  342. $lspostid=$res['data']['id'];
  343. $debug.="lspostid: {$lspostid}<br>\n";
  344. $rls=ckratelimit($res['headers'],'necho',true,false);
  345. //if ($i==2) $rls=['remaining'=>0,'secstoreset'=>3895];// test err
  346. //$rls=['remaining'=>0,'secstoreset'=>5];// test err
  347. if (!is_null($rls)) {
  348. if ($rls['remaining']<=REQSTOPRESV && $i<$postscount-1) {
  349. $postingok=false;
  350. $postwait=$rls['secstoreset'];
  351. $postwaituntil=time()+$postwait;
  352. $postsoffset=$i+1;
  353. $postmsgs.='<div class="warning">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.</div>\n";
  354. $pbtext='Please wait '.ght($postwait).' before posting';
  355. break;
  356. }
  357. }
  358. if ($i<$postscount-1)
  359. usleep(POSTPAUSE*1000);
  360. } else {
  361. $postingok=false;
  362. $postsoffset=$i;
  363. $postmsgs.='<div class="warning">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.</div>\n";
  364. break;
  365. }
  366. }
  367. }
  368. if ($postingok) {
  369. $postmsgs.="<div class=\"success\">{$sname} successfully sent {$i}/{$postscount} split posts :-)</div>\n";
  370. $posts=[];
  371. $postscount=0;
  372. }
  373. }
  374. }
  375. $replyto='';
  376. $language='';
  377. $displang='';
  378. $visibility='';
  379. $dispvisib='';
  380. if (!$loggedin) {
  381. $auth="<a name=\"connect\"></a><h2>Connect?</h2>
  382. {$authmsgs}
  383. <div class=\"normtext\">
  384. <p class=\"firstp\">If you don’t want to connect {$sname} to your account, you can just <a href=\"#split\">skip to the “Split post” section</a>, 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).</p>
  385. <p>If you choose instead to connect {$sname} to your account, <span class=\"hili\">you’ll be able to post all split posts at once directly from within {$sname}</span>, 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.</p>
  386. <p>&nbsp;</p>
  387. <p class=\"firstp\">To connect {$sname} to your account</p>
  388. <ul class=\"ul\">
  389. <li>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);</li>
  390. <li>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 <span class=\"hili\">note that this process will make you loose your current split session</span>.</li>
  391. </ul>
  392. </div>
  393. <form method=\"post\" id=\"authform\" name=\"authform\" action=\"{$formact}\">
  394. ".oldpost2hid()."
  395. <div class=\"inputdiv\"><label for=\"host\">Your server’s hostname</label><input type=\"text\" id=\"host\" name=\"host\" class=\"input\" maxlength=\"253\" placeholder=\"E.g.: mastodon.whatever.net\" value=\"".htmlentities($_POST['host'])."\" required></div>
  396. <div class=\"inputdiv\"><label for=\"token\">Your token</label><input type=\"password\" id=\"token\" name=\"token\" class=\"input\" maxlength=\"43\" placeholder=\"Leave blank if you want to authorize {$sname} to access your account\" value=\"".htmlentities($_POST['token'])."\"></div>
  397. <div class=\"lastinputdiv\"><button type=\"submit\" name=\"act\" value=\"login\" class=\"button\">Connect</button></div>
  398. </form>\n";
  399. $replyto='<input type="hidden" name="replyto" value="'.htmlentities($_POST['replyto']).'"><input type="hidden" name="setcws" value="'.cb2hid('setcws').'"><input type="hidden" name="setments" value="'.cb2hid('setments').'"><input type="hidden" name="setvisib" value="'.cb2hid('setvisib').'"><input type="hidden" name="setlang" value="'.cb2hid('setlang').'">';
  400. $visibility='<input type="hidden" name="visib" value="'.$_POST['visib'].'">';
  401. $language='<input type="hidden" name="lang" value="'.$_POST['lang'].'">';
  402. } else {
  403. $auth="<a name=\"connect\"></a><h2>Connected :-)</h2>\n{$authmsgs}\n<form method=\"post\" id=\"authform\" name=\"authform\" action=\"{$formact}\">\n".oldpost2hid()."<div class=\"normtext\">{$sname} is connected to your {$myacc['acct']}@{$host} account.</div>\n<div><button type=\"submit\" id=\"logoutbutton\" name=\"act\" value=\"logout\" class=\"button\">Disconnect</button></div>\n</form>\n";
  404. $replyto='<div class="inputdiv"><label for="replyto">URL of post to reply to</label><input type="text" id="replyto" name="replyto" class="inputx" placeholder="Leave blank to make a new post" value="'.htmlentities($_POST['replyto']).'" maxlength="'.MAXREPTOLEN.'"><input type="hidden" name="prevreplyto" value="'.htmlentities($_POST['replyto']).'"><div class="lastborder"><div class="trow"><input type="checkbox" name="setcws" id="setcws" class="tcell" value="1"'.$setcwsck.'><label for="setcws" class="cblab">Set “Content Warning” to this post’s Content Warning (if any)</label></div><div class="trow"><input type="checkbox" name="setments" id="setments" class="tcell" value="1"'.$setmentsck.'><label for="setments" class="cblab">Set “Text to prepend” to this post’s sender and mentions (except you)</label></div><div class="trow"><input type="checkbox" name="setvisib" id="setvisib" class="tcell" value="1"'.$setvisibck.'><label for="setvisib" class="cblab">Set “Split posts’ visibility” to this post’s visibility</label></div><div class="trow"><input type="checkbox" name="setlang" id="setlang" class="tcell" value="1"'.$setlangck.'><label for="setlang" class="cblab">Set “Split posts’ language” to this post’s language</label></div></div></div>';
  405. $optsa=['public'=>'Public','unlisted'=>'Unlisted','private'=>'Followers-only','direct'=>'Direct'];
  406. $buff='';
  407. foreach ($optsa as $key=>$val) {
  408. if ($key==$_POST['visib']) {
  409. $selected=' selected';
  410. $dispvisib=$val;
  411. } else {
  412. $selected='';
  413. }
  414. $buff.="<option value=\"{$key}\"{$selected}>{$val}</option>\n";
  415. }
  416. $visibility='<div class="inputdiv"><label for="visib">Split posts’ visibility</label><select class="input" id="visib" name="visib">'.$buff.'</select></div>';
  417. // taken from 'preferences' -> 'other' on mastodon 4.1.2
  418. $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','ⵜⴰⵎⴰⵣⵉⵖⵜ']];
  419. $buff='';
  420. foreach ($optsa as $val) {
  421. if ($val[0]==$_POST['lang']) {
  422. $displang=$val[2].' ('.$val[1].')';
  423. $selected=' selected';
  424. } else {
  425. $selected='';
  426. }
  427. $buff.="<option value=\"{$val[0]}\"{$selected}>{$val[2]} ({$val[1]})</option>\n";
  428. }
  429. $language="<div class=\"inputdiv\"><label for=\"lang\">Split posts’ language</label><select class=\"input\" id=\"lang\" name=\"lang\">{$buff}</select></div>\n";
  430. unset($buff,$optsa);
  431. }
  432. $auth.="<div class=\"pseparator\"></div>\n";
  433. if ($dodebug) {
  434. $debug='<div class="debug">'.$debug.'</div>';
  435. $svers.='-'.rand(10000,999999);
  436. } else {
  437. $debug='';
  438. }
  439. (isset($_POST['act']) && $_POST['act']=='split') ? $splitfh='' : $splitfh=' class="fullheight"';
  440. header('Content-Language: en');
  441. echo "<!DOCTYPE HTML>
  442. <html lang=\"en\">
  443. <head>
  444. <title>{$sname}: a post splitter for Mastodon</title>
  445. <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">
  446. <meta name=\"description\" content=\"A post splitter for Mastodon\">
  447. <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no\">
  448. <meta property=\"og:image\" content=\"imgs/ogimage.png\">
  449. <link rel=\"icon\" type=\"image/png\" href=\"imgs/icon-16.png\" sizes=\"16x16\">
  450. <link rel=\"icon\" type=\"image/png\" href=\"imgs/icon-32.png\" sizes=\"32x32\">
  451. <link rel=\"icon\" type=\"image/png\" href=\"imgs/icon-192.png\" sizes=\"192x192\">
  452. <link rel=\"icon\" type=\"image/png\" href=\"imgs/icon-512.png\" sizes=\"512x512\">
  453. <link rel=\"apple-touch-icon-precomposed\" href=\"imgs/icon-180.png\">
  454. <link rel=\"stylesheet\" type=\"text/css\" href=\"css/main.css?v={$svers}\">
  455. <script language=\"JavaScript\" src=\"js/tlds.js?v={$svers}\"></script>
  456. <script language=\"JavaScript\" src=\"js/main.js?v={$svers}\"></script>
  457. <script language=\"JavaScript\">
  458. function swpdisp(elem,defdisp,altdisp) {
  459. var eh=document.getElementById(elem), disp=window.getComputedStyle(eh).getPropertyValue('display');
  460. if (disp==defdisp)
  461. eh.style.display=altdisp;
  462. else
  463. eh.style.display=defdisp;
  464. }
  465. function updinfo() {
  466. var avchars=document.getElementById('avchars').value, mincl=".MINCL.", precwavc=0, prelen=mastlength(document.getElementById('pre').value), cwlen=document.getElementById('cw').value.length, maxpostlen=".MAXPOSTC.", postlen=mastlength(document.getElementById('post').value);
  467. if (Number.isNaN(avchars) || avchars-mincl<0)
  468. precwavc=0;
  469. else
  470. precwavc=avchars-mincl;
  471. if (prelen>0) prelen+=2;
  472. postlen=postlen+cwlen+prelen;
  473. document.getElementById('cwlen').textContent=cwlen;
  474. document.getElementById('cwavc').textContent=precwavc;
  475. document.getElementById('cwrmc').textContent=precwavc-prelen-cwlen;
  476. document.getElementById('prelen').textContent=prelen;
  477. document.getElementById('preavc').textContent=precwavc;
  478. document.getElementById('prermc').textContent=precwavc-prelen-cwlen;
  479. document.getElementById('postlen').textContent=postlen;
  480. document.getElementById('postleft').textContent=maxpostlen-postlen;
  481. if (prelen+cwlen<=precwavc) {
  482. document.getElementById('cwlen').style.color='white';
  483. document.getElementById('cwrmc').style.color='white';
  484. document.getElementById('prelen').style.color='white';
  485. document.getElementById('prermc').style.color='white';
  486. } else {
  487. document.getElementById('cwlen').style.color='#550000';
  488. document.getElementById('cwrmc').style.color='#550000';
  489. document.getElementById('prelen').style.color='#550000';
  490. document.getElementById('prermc').style.color='#550000';
  491. }
  492. if (postlen<=maxpostlen) {
  493. document.getElementById('postlen').style.color='white';
  494. document.getElementById('postleft').style.color='white';
  495. } else {
  496. document.getElementById('postlen').style.color='#550000';
  497. document.getElementById('postleft').style.color='#550000';
  498. }
  499. }
  500. var secstowait=0, postint='notset', reptoid='{$reptoid}', postreq;
  501. function post(postindex) {
  502. if (postint!='notset') {
  503. clearInterval(postint);
  504. postint='notset';
  505. }
  506. var pstatus=document.getElementById('pstatus'), postscount=parseInt(document.forms['postform'].elements['postscount'].value,10), pperc=document.getElementById('pperc'), plog=document.getElementById('plog'), pbut=document.getElementById('postbutton'), form, msg, totres;
  507. if (postindex==0)
  508. plog.innerHTML='';
  509. msg='Posting split post '+(postindex+1)+' of '+postscount+' …';
  510. pstatus.textContent=msg;
  511. plog.innerHTML+=msg+'<br>';
  512. form=new FormData();
  513. if (postindex==0) {
  514. if (reptoid!='') form.set('in_reply_to_id',reptoid);
  515. } else {
  516. form.set('in_reply_to_id',reptoid);
  517. }
  518. form.set('language',document.forms['postform'].elements['lang'].value);
  519. if (postindex>0 && document.forms['postform'].elements['visib'].value=='public')
  520. form.set('visibility','unlisted');
  521. else
  522. form.set('visibility',document.forms['postform'].elements['visib'].value);
  523. if (document.forms['postform'].elements['cw'].value!='')
  524. form.set('spoiler_text',document.forms['postform'].elements['cw'].value);
  525. form.set('status',document.getElementById('post_'+postindex).value);
  526. console.log('postindex: '+postindex);
  527. console.log(form);
  528. postreq=new XMLHttpRequest();
  529. postreq.onloadend=(e)=> {
  530. console.log(postreq.response);
  531. //if (postindex==1) { postreq.response['ok']=false; postreq.response['error']='server exploded'; }// test err
  532. if (postreq.response===null) {
  533. console.log('Error: response is null');
  534. msg='An error occurred while posting split post '+(postindex+1)+' of '+postscount;
  535. if (postreq.status!='' && postreq.statusText!='')
  536. msg+=' (server returned «'+postreq.status+': '+postreq.statusText+'»)';
  537. pstatus.textContent=msg;
  538. plog.innerHTML+=msg+'<br>';
  539. document.forms['postform'].elements['postsoffset'].value=postindex;
  540. pbut.disabled=false;
  541. } else if (!postreq.response['ok']) {
  542. console.log('Error: response is not ok');
  543. msg='An error occurred while posting split post '+(postindex+1)+' of '+postscount+': '+postreq.response['error'];
  544. pstatus.textContent=msg;
  545. plog.innerHTML+=msg+'<br>';
  546. document.forms['postform'].elements['postsoffset'].value=postindex;
  547. pbut.disabled=false;
  548. } else {
  549. //console.log(e);
  550. //console.log('rate limit remaining: '+postreq.response['remaining']+'; secstoreset: '+postreq.response['secstoreset']+'; id: '+postreq.response['id']+'.');
  551. msg='Successfully posted split post '+(postindex+1)+' of '+postscount+' :-)';
  552. pstatus.textContent=msg;
  553. plog.innerHTML+=msg+'<br>';
  554. pperc.style.width=100/postscount*(postindex+1)+'%';
  555. reptoid=postreq.response['id'];
  556. if (postindex<postscount-1) {
  557. if (postreq.response['remaining']!==null && postreq.response['secstoreset']!==null && postreq.response['remaining']<=".REQSTOPRESV.") {
  558. secstowait=postreq.response['secstoreset'];
  559. plog.innerHTML+='Reached server’s rate limit, waiting '+ght(secstowait)+' for next rate limit reset …<br>';
  560. totres=setTimeout(post,secstowait*1000,postindex+1);
  561. postint=setInterval(postwait,500,postindex+1,Date.now()/1000);
  562. } else {
  563. totres=setTimeout(post,".POSTPAUSE.",postindex+1);
  564. }
  565. } else {
  566. pbut.style.display='none';
  567. }
  568. }
  569. return;
  570. };
  571. postreq.responseType='json';
  572. postreq.open('POST','post.php');
  573. postreq.send(form);
  574. }
  575. function postwait(postindex,start) {
  576. var remsecs=Math.ceil(secstowait-(Date.now()/1000-start));
  577. document.getElementById('pstatus').textContent='Reached server’s rate limit, waiting '+ght(remsecs)+' for next rate limit reset …';
  578. }
  579. function postboot() {
  580. document.getElementById('postbutton').disabled=true;
  581. document.getElementById('pmonitor').style.display='block';
  582. post(parseInt(document.forms['postform'].elements['postsoffset'].value,10));
  583. }
  584. function setpostbutton() {
  585. var pb=document.getElementById('postbutton');
  586. pb.type='button';
  587. pb.addEventListener('click',postboot);
  588. pb.textContent='Post';
  589. }
  590. var loggedin=".booltostr($loggedin).";
  591. function setpcb(index) {
  592. if (!loggedin)
  593. document.getElementById('pcb_'+index).style.display='block';
  594. }
  595. function ght(secs) {
  596. out='';
  597. // years
  598. x=Math.floor(secs/31536000);
  599. if (x>0)
  600. (x==1) ? out+=x+' year, ' : out+=x+' years, ';
  601. secs=secs-x*31536000;
  602. // weeks
  603. x=Math.floor(secs/604800);
  604. if (x>0)
  605. (x==1) ? out+=x+' week, ' : out+=x+' weeks, ';
  606. secs=secs-x*604800;
  607. // days
  608. x=Math.floor(secs/86400);
  609. if (x>0)
  610. (x==1) ? out+=x+' day, ' : out+=x+' days, ';
  611. secs=secs-x*86400;
  612. // hours
  613. x=Math.floor(secs/3600);
  614. if (x>0)
  615. (x==1) ? out+=x+' hour, ' : out+=x+' hours, ';
  616. secs=secs-x*3600;
  617. // minutes
  618. x=Math.floor(secs/60);
  619. if (x>0)
  620. (x==1) ? out+=x+' minute, ' : out+=x+' minutes, ';
  621. secs=secs-x*60;
  622. // seconds
  623. (secs==1) ? out+=secs+' second' : out+=secs+' seconds';
  624. return out;
  625. }
  626. function ckf() {
  627. var mincl=".MINCL.",
  628. maxpostc=".MAXPOSTC.",
  629. avchars=document.getElementById('avchars').value,
  630. cw=document.getElementById('cw'),
  631. cwlen=cw.value.length,
  632. pre=document.getElementById('pre');
  633. prelen=mastlength(pre.value),
  634. postlen=mastlength(document.getElementById('post').value),
  635. pmsg=document.getElementById('pupmsg'),
  636. ptit=document.getElementById('puptit'),
  637. maxcwprelen=avchars-mincl,
  638. ofelems=document.forms['authform'].elements,
  639. msg='',
  640. msgs=[];
  641. if (cwlen+prelen>maxcwprelen) msgs.push('text of Content Warning + text to prepend is '+(cwlen+prelen)+' characters long, that is '+(cwlen+prelen-maxcwprelen)+' characters longer than its maximum allowed length ('+maxcwprelen+' characters, that is '+avchars+' available characters minus '+mincl+' characters)');
  642. if (postlen>maxpostc) msgs.push('post to split is '+postlen+' characters long, that is '+(postlen-maxpostc)+' characters longer than its maximum allowed length, '+maxpostc);
  643. var len=msgs.length, sep=';';
  644. if (len>0) {
  645. ptit.textContent='Errors';
  646. msg='<ul>';
  647. for (i=0; i<len; i++) {
  648. if (i==len-1) sep='.';
  649. msg+='<li>'+msgs[i]+sep+'</li>';
  650. }
  651. pmsg.innerHTML=msg+'</ul>';
  652. swpdisp('popup','none','flex');
  653. return false;
  654. } else {
  655. msgs=[];
  656. if (document.getElementById('replyto')!=null && document.getElementById('replyto').value.trim()!='') {
  657. if (document.getElementById('setcws').checked && cw.value!=ofelems['cw'].value)
  658. msgs.push('you have changed “Content Warning”, but it will be overwritten by the Content Warning detected in the post you are replying to (if present)');
  659. if (document.getElementById('setments').checked && pre.value!=ofelems['pre'].value)
  660. msgs.push('you have changed “Text to prepend to each split post”, but it will be overwritten by the sender and mentions (except you) detected in the post you are replying to');
  661. if (document.getElementById('setvisib').checked && document.getElementById('visib').value!=ofelems['visib'].value)
  662. msgs.push('you have changed “Split posts’ visibility”, but it will be set to the visibility of the post you are replying to');
  663. if (document.getElementById('setlang').checked && document.getElementById('lang').value!=ofelems['lang'].value)
  664. msgs.push('you have changed “Split posts’ language”, but it will be set to the language of the post you are replying to');
  665. }
  666. len=msgs.length;
  667. sep=';';
  668. if (len>0) {
  669. ptit.textContent='Warnings';
  670. msg='<ul>';
  671. for (i=0; i<len; i++) {
  672. if (i==len-1) sep='.';
  673. msg+='<li>'+msgs[i]+sep+'</li>';
  674. }
  675. pmsg.innerHTML=msg+'</ul><p>&nbsp;</p><p class=\"firstp\">Do you want to continue?</p><p>&nbsp;</p><button class=\"halfbutton\" onclick=\"document.mainform.submit(true);\">Yes</button><button class=\"halfbutton\" onclick=\"swpdisp(\'popup\',\'flex\',\'none\');event.stopPropagation();\">No</button>';
  676. swpdisp('popup','none','flex');
  677. return false;
  678. } else {
  679. return true;
  680. }
  681. }
  682. }
  683. </script>
  684. </head>
  685. <body>
  686. <div id=\"notif\" title=\"Close\" onclick=\"this.style.display='none';\">Elo!</div>
  687. <div id=\"popup\" onclick=\"swpdisp('popup','flex','none');\">
  688. <div id=\"popupmsg\">
  689. <div id=\"puptitle\"><table class=\"tittab\"><tr><td style=\"width:24px;\"></td><td style=\"text-align:center;\" id=\"puptit\">Uz!</td><td style=\"width:24px;\"><img src=\"imgs/icon_close.png\" class=\"closeb\" onclick=\"swpdisp('popup','flex','none');event.stopPropagation();\"></td></tr></table></div>
  690. <div id=\"pupmsg\">Elo!</div>
  691. </div>
  692. </div>
  693. {$debug}
  694. <div id=\"main\">
  695. {$headmsgs}
  696. <h1>{$sname}: a post splitter for Mastodon</h1>
  697. {$intro}
  698. <div class=\"separator\"></div>
  699. {$auth}
  700. <div{$splitfh}>
  701. <a name=\"split\"></a><h2>Split post</h2>
  702. {$splitmsgs}
  703. <form method=\"post\" id=\"mainform\" name=\"mainform\" action=\"{$formact}\" onsubmit=\"return ckf();\">
  704. <input type=\"hidden\" name=\"round\" value=\"".$_POST['round']."\">
  705. <div class=\"inputdiv\"><label for=\"avchars\">Available characters per post (".MINAC."-".MAXAC.")</label><input type=\"number\" id=\"avchars\" name=\"avchars\" class=\"input\" placeholder=\"The number of characters per post available on your server\" value=\"{$_POST['avchars']}\" min=\"".MINAC."\" max=\"".MAXAC."\" onkeyup=\"updinfo();\" onchange=\"updinfo();\" required></div>
  706. {$replyto}
  707. <div class=\"inputdiv\"><label for=\"cw\">Content Warning (applies to all split posts) (<span id=\"cwlen\">{$cwlen}</span>/<span id=\"cwavc\">{$maxcwprec}</span>: <span id=\"cwrmc\">".($maxcwprec-$cwprelen)."</span> left)</label><input type=\"text\" id=\"cw\" name=\"cw\" class=\"input\" placeholder=\"Leave blank for no Content Warning\" value=\"".htmlentities($_POST['cw'])."\" maxlength=\"".(MAXAC-MINCL)."\" onkeyup=\"updinfo();\" onchange=\"updinfo();\"></div>
  708. <div class=\"inputdiv\"><label for=\"pre\">Text to prepend to each split post, eg. the recipient(s) (<span id=\"prelen\">{$prelen}</span>/<span id=\"preavc\">{$maxcwprec}</span>: <span id=\"prermc\">".($maxcwprec-$cwprelen)."</span> left)</label><input type=\"text\" id=\"pre\" name=\"pre\" class=\"input\" placeholder=\"Leave blank to prepend nothing\" value=\"".htmlentities($_POST['pre'])."\" maxlength=\"".(MAXAC-MINCL)."\" onkeyup=\"updinfo();\" onchange=\"updinfo();\"></div>
  709. {$visibility}
  710. {$language}
  711. <div class=\"inputdiv\"><label for=\"post\">Post to split (<span id=\"postlen\">{$postlen}</span>/".MAXPOSTC.": <span id=\"postleft\">".(MAXPOSTC-$postlen)."</span> left)</label><textarea placeholder=\"Write or paste your long post here\" id=\"post\" name=\"post\" class=\"input\" rows=\"10\" onkeyup=\"updinfo();\" onchange=\"updinfo();\" required>".htmlentities($_POST['post'])."</textarea></div>
  712. <script language=\"JavaScript\">
  713. updinfo();
  714. </script>
  715. <div class=\"inputdiv\"><label for=\"cntpos\">Split posts’ counter position</label>
  716. <fieldset>
  717. <div class=\"trow\"><input type=\"radio\" id=\"cntpos_before\" name=\"cntpos\" value=\"before\" class=\"tcell\" required{$rbsck['cntpos_before']}><label for=\"cntpos_before\" class=\"cblab\">Before each split post’s body</label></div>
  718. <div class=\"trow\"><input type=\"radio\" id=\"cntpos_after\" name=\"cntpos\" value=\"after\" class=\"tcell\" required{$rbsck['cntpos_after']}><label for=\"cntpos_after\" class=\"cblab\">After each split post’s body</label></div>
  719. </fieldset>
  720. </div>
  721. <div class=\"inputdiv\"><label for=\"addref\">Add link to this page at last split post’s end?</label>
  722. <fieldset>
  723. <div class=\"trow\"><input type=\"radio\" id=\"addref_no\" name=\"addref\" value=\"no\" class=\"tcell\" required{$rbsck['addref_no']}><label for=\"addref_no\" class=\"cblab\">No</label></div>
  724. <div class=\"trow\"><input type=\"radio\" id=\"addref_ifav\" name=\"addref\" value=\"ifav\" class=\"tcell\" required{$rbsck['addref_ifav']}><label for=\"addref_ifav\" class=\"cblab\">If last split post has enough space left</label></div>
  725. </fieldset>
  726. </div>
  727. <div class=\"lastinputdiv\"><button type=\"submit\" id=\"button\" name=\"act\" value=\"split\" class=\"button\">Split</button></div>\n</form>\n</div>\n";
  728. if ($splitmsgs=='') {
  729. if (isset($_POST['act']) && $_POST['act']=='split' && $_POST['post']!='') {
  730. $linklen=postlength($conf['link']);
  731. ($_POST['pre']!='') ? $pre=$_POST['pre']."\n\n" : $pre='';
  732. $posts=splitpost($_POST['post'],$_POST['avchars'],$_POST['cw'],$pre,$cntbef);
  733. $postscount=count($posts);
  734. $errposts=[];
  735. for ($i=0; $i<$postscount; $i++)
  736. if ($posts[$i]['mastlen']>$_POST['avchars']) $errposts[]="<a href=\"#pa_{$i}\">{$i}</a>";
  737. $epc=count($errposts);
  738. if ($epc>0) {
  739. if ($epc==1)
  740. echo "<p class=\"firstp\">Sorry, the length of one split post (namely {$errposts[0]}) exceeds {$_POST['avchars']} characters.";
  741. else
  742. echo "<p class=\"firstp\">Sorry, the length of {$epc} split posts (namely ".implode(', ',$errposts).") exceeds {$_POST['avchars']} characters.";
  743. echo " Please report this bug <a href=\"https://git.lattuga.net/pongrebio/verbose/issues\">here</a>, possibly citing the original post text.</p>\n";
  744. }
  745. if ($postscount>1 && $aliif && $posts[$postscount-1]['mastlen']+$linklen<=$_POST['avchars']) {
  746. $posts[$postscount-1]['post'].=$conf['link'];
  747. $posts[$postscount-1]['mastlen']+=$linklen;
  748. }
  749. }
  750. if ($postscount>0) {
  751. //if ($loggedin) $postmsgs.="<div class=\"normtext\">“Post all” button is at the bottom ;-)</div>\n";
  752. echo "<div class=\"pseparator\"></div>\n<div class=\"fullheight\"><a name=\"splitresults\"></a><h2>Split results</h2>\n{$postmsgs}<form method=\"post\" id=\"postform\" name=\"postform\" action=\"{$formact}\">\n";
  753. $info='Content Warning: ';
  754. ($posts[$postsoffset]['cw']!='') ? $info.=htmlentities($_POST['cw']) : $info.='<span class="notset">not set</span>';
  755. if ($loggedin) $info.='<br>Visibility: '.$dispvisib.'<br>Language: '.htmlentities($displang);
  756. ($loggedin) ? $postdivclass='postdivnobut' : $postdivclass='postdiv';
  757. for ($i=$postsoffset; $i<$postscount; $i++) {
  758. $io=$i+1;
  759. ($posts[$i]['mastlen']>$_POST['avchars']) ? $phclass='errposthead' : $phclass='posthead';
  760. echo "<div class=\"{$phclass}\"><a name=\"pa_{$i}\"></a>Post {$io}/{$postscount} (“Mastodon length”: {$posts[$i]['mastlen']}; real length: ".(mb_strlen($posts[$i]['post'],'UTF-8')+$cwlen).")</div>\n<div class=\"outputdiv\"><div class=\"outputnobb\">{$info}</div></div>\n<div class=\"outputdiv\"><div id=\"postdiv_{$i}\" class=\"{$postdivclass}\">".nl2br(htmlentities($posts[$i]['post']))."</div><input type=\"hidden\" id=\"post_{$i}\" name=\"post_{$i}\" value=\"".htmlentities($posts[$i]['post'])."\"><input type=\"hidden\" id=\"mastlen_{$i}\" name=\"mastlen_{$i}\" value=\"{$posts[$i]['mastlen']}\"></div>\n<div class=\"outputdiv\"><button type=\"button\" id=\"pcb_{$i}\" class=\"copybutton\" onclick=\"copytext({$i});\">Copy post text</button><script language=\"JavaScript\">\nsetpcb({$i});\n</script>\n</div>\n";
  761. }
  762. if ($loggedin) {
  763. if (!isset($lspostid)) $lspostid='';
  764. echo "<div class=\"lastoutputdiv\"><div id=\"pmonitor\">\n<div id=\"pstatus\"></div>\n<div id=\"ppercenv\"><div id=\"pperc\"> </div></div>\n<div id=\"plog\"> </div>\n</div>\n<button type=\"submit\" id=\"postbutton\" name=\"act\" value=\"sendposts\" class=\"postbutton\">{$pbtext}</button></div>\n".oldpost2hid()."<input type=\"hidden\" name=\"postscount\" value=\"{$postscount}\">\n<input type=\"hidden\" name=\"postwaituntil\" value=\"{$postwaituntil}\">\n<input type=\"hidden\" name=\"postsoffset\" value=\"{$postsoffset}\">\n<input type=\"hidden\" name=\"lastsentpostid\" value=\"{$lspostid}\">\n<script language=\"JavaScript\">\nsetpostbutton();\n</script>\n";
  765. }
  766. echo "</form></div>\n";
  767. } elseif ($postmsgs!='') {
  768. echo "<div class=\"separator\"></div>\n{$postmsgs}";
  769. }
  770. }
  771. if (isset($conf['footer']))
  772. echo "<div id=\"almfooter\">{$conf['footer']}</div>\n";
  773. echo "<div id=\"footer\"><a href=\"".SREPO."\">".SNAME." ".SVERS."</a></div>
  774. </div>
  775. </body>
  776. </html>\n";
  777. function preprint($var) {
  778. return '<pre>'.print_r($var,true)."</pre>\n";
  779. }
  780. function necho($var) {
  781. // do nothing :-)
  782. }
  783. function dieyoung($msg) {
  784. echo $msg;
  785. exit(1);
  786. }
  787. function cb2hid($pkey) {
  788. if (isset($_POST[$pkey]) && $_POST[$pkey]=='1')
  789. return '1';
  790. else
  791. return '0';
  792. }
  793. function oldpost2hid() {
  794. return "<input type=\"hidden\" name=\"avchars\" value=\"{$_POST['avchars']}\">\n<input type=\"hidden\" name=\"cw\" value=\"".htmlentities($_POST['cw'])."\">\n<input type=\"hidden\" name=\"pre\" value=\"".htmlentities($_POST['pre'])."\">\n<input type=\"hidden\" name=\"post\" value=\"".htmlentities($_POST['post'])."\">\n<input type=\"hidden\" name=\"replyto\" value=\"".htmlentities($_POST['replyto'])."\">\n<input type=\"hidden\" name=\"setcws\" value=\"".cb2hid('setcws')."\">\n<input type=\"hidden\" name=\"setments\" value=\"".cb2hid('setments')."\">\n<input type=\"hidden\" name=\"setvisib\" value=\"".cb2hid('setvisib')."\">\n<input type=\"hidden\" name=\"setlang\" value=\"".cb2hid('setlang')."\">\n<input type=\"hidden\" name=\"visib\" value=\"{$_POST['visib']}\">\n<input type=\"hidden\" name=\"lang\" value=\"{$_POST['lang']}\">\n<input type=\"hidden\" name=\"cntpos\" value=\"{$_POST['cntpos']}\">\n<input type=\"hidden\" name=\"addref\" value=\"{$_POST['addref']}\">\n<input type=\"hidden\" name=\"round\" value=\"{$_POST['round']}\">\n";
  795. }
  796. ?>