idefix 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647
  1. #!/usr/bin/php
  2. <?php
  3. const SNAME='idefix';
  4. const USNAME='Idefix';
  5. const SVERS='0.2';
  6. const N="\n";
  7. const RN="\r\n";
  8. $projdir=null;
  9. $logfh=null;
  10. $logfn=SNAME.'.log';
  11. $configfn=SNAME.'.conf';
  12. $lockfn=SNAME.'.lock';
  13. $opts=array(
  14. 'verbose'=>false,
  15. 'force'=>false,
  16. );
  17. $help=
  18. 'SYNOPSIS
  19. '.SNAME.' [options] <project directory>
  20. DESCRIPTION
  21. This is '.SNAME.' v'.SVERS.', a CLI php script that listens on a defined Mastodon «main bot» account for commands in «direct» or «public» toots from users and, according to the commands it receives from a user, it can make any defined «follow bot» account follow or unfollow the user (the user must write from an instance among those for which a «follow bot» account is defined: toots from other instances will be ignored).
  22. The script can be useful to give users - particularly new ones, with few or no followers - of a set of Mastodon instances the possibility to make their public toots appear on the «federated timelines» of the other instances, helping them to get to read and know each other.
  23. In order for '.SNAME.' to work, an existing «project directory» has to be passed as an argument to it, with a file named «'.$configfn.'» inside it. This file should contain the accounts definitions - at least two -, one definition for each line (empty lines and lines with an «;» character on their first column will be ignored).
  24. Each account definition should follow this syntax:
  25. <account> <label> <token>
  26. The first account definition will be considered as the «main bot» account definition; the corresponding «label» won’t matter and can be set to anything (it has to be set to something, though); the corresponding «token» should be the token of an app that has already been created, with «read» and «write» privileges, on the «main bot» account. Each following account definition will be considered as a «follow bot» account; the corresponding «label» should be set to an easy «short name» for the account’s domain; the corresponding «token» should be the token of an app that has already been created, with «follow» privileges, on the «follow bot» account. The «main bot» account and every «follow bot» account would better be marked as «bot» accounts on their instances.
  27. «label» and «token» should be separated from the previous entity by a single « » (space) character.
  28. --- Example «'.$configfn.'» file ---
  29. ; This line will be ignored and next line too, since it’s empty.
  30. ; Next line is the first, so it will define the «main» account.
  31. folbot@aaaa.aaa aiai xA9kZe9zB6EgtjsW5EVr4axYWW46c1TPC-RpQhnkAvw
  32. ; Next lines define four «follow bots» accounts.
  33. folbot@bbbb.bbb bibi 7fne902mc0954mi2ollWMNfasdf6aposdfADF9MFN12
  34. folbot@cccc.ccc cici 0XJfrQ2C9ddzB9H3brTcwt-afo3ZOpTN4jmZbQrPoO8
  35. folbot@dddd.ddd didi Gm3lvO4LxwaXkmhW_q6VSvcBkj_05N0bvJc0TlwnTkw
  36. folbot@eeee.eee eiei l8r3kQynViz2BHw5NKBw7GajnkrSCDeqTI58KUfs6X2
  37. --- End of example «'.$configfn.'» file ---
  38. Assuming all the defined accounts are properly set up, running '.SNAME.' on a «project directory» containing this «'.$configfn.'» file (like in «'.SNAME.' my_project_dir») will make it try to process commands in any «direct» or «public» toot addressed *only* to «folbot@aaaa.aaa», *only* if it’s coming from a user from «bbbb.bbb», «cccc.ccc», «dddd.ddd» or «eeee.eee» instances (toots coming from other instances, or with «unlisted» or «followers-only» privacy level, will be ignored).
  39. A «command toot» should have this syntax:
  40. <@main bot account> <command> [command argument[s]]
  41. '.USNAME.' understands commands in english and italian language.
  42. «Command» can be one of these:
  43. follow me
  44. Makes *all* the «follow bot» accounts, except the «follow bot»
  45. account on the same instance as the sender’s, follow the sender’s
  46. account.
  47. seguimi
  48. Same as above, in italian language.
  49. don\'t follow me
  50. Makes *all* the «follow bot» accounts, except the «follow bot»
  51. account on the same instance as the sender’s, unfollow the sender’s
  52. account.
  53. non seguirmi
  54. Same as above, in italian language.
  55. follow me from <list of instances’ domains/labels>
  56. Makes the «follow bot» account on each instance that is mentioned
  57. or referred to with the corresponding label in the list follow
  58. the sender’s account. Every instance’s domain or label in the list
  59. will have to be separated from each other with a « » (space)
  60. and-or «,» (comma) character.
  61. seguimi da <lista di domini/etichette di istanze>
  62. Same as above, in italian language.
  63. don\'t follow me from <list of instances’ domains/labels>
  64. Makes the «follow bot» account on each instance that is mentioned
  65. or referred to with the corresponding label in the list unfollow
  66. the sender’s account. Every instance’s domain or label in the list
  67. will have to be separated from each other with a « » (space)
  68. and-or «,» (comma) character.
  69. non seguirmi da <lista di domini/etichette di istanze>
  70. Same as above, in italian language.
  71. Let’s make some examples, based on the example «'.$configfn.'» file above.
  72. User «jimmy@cccc.ccc» sends this «public» toot to «folbot@aaaa.aaa»:
  73. --- Example toot ---
  74. @folbot@aaaa.aaa follow me
  75. --- End of example toot ---
  76. On receiving this toot, '.SNAME.' will try and make all the defined «follow bot» accounts except «folbot@cccc.ccc» («folbot@bbbb.bbb», «folbot@dddd.ddd», «folbot@eeee.eee») follow «jimmy@cccc.ccc», and then it will reply to «jimmy@cccc.ccc» with a toot with the same privacy level as the one it received («direct» or «public»), summarizing the results.
  77. Then, user «jimmy@cccc.ccc» sends this «public» toot to «folbot@aaaa.aaa»:
  78. --- Example toot ---
  79. @folbot@aaaa.aaa don\'t follow me from dddd.ddd, bibi
  80. --- End of example toot ---
  81. On receiving this toot, '.SNAME.' will try and make «folbot@dddd.ddd» and «folbot@bbbb.bbb» unfollow «jimmy@cccc.ccc», and then it will reply to «jimmy@cccc.ccc» with a toot summarizing the results.
  82. OPTIONS
  83. -h, --help
  84. Show this help text and exit.
  85. -f, --force
  86. Force run even if a lockfile exists in project directory.
  87. -v, --verbose
  88. The script will produce a lot of informational output about what
  89. it’s doing.
  90. EXIT VALUES
  91. 0: regular run
  92. 1: regular run, but some warnings where emitted
  93. 2: '.SNAME.' encountered a fatal error
  94. NOTES
  95. On every run, '.SNAME.' writes informations about what it’s doing and problems it may encounter in a «'.$logfn.'» file in the specified project directory.
  96. DISCLAIMER AND LICENSE
  97. This program comes with ABSOLUTELY NO WARRANTY; for details see the source. This is free software, and you are welcome to redistribute it under certain conditions; see <http://www.gnu.org/licenses/> for details.'.N;
  98. $help=wordwrap($help,75);
  99. function elecho($msg,$clitime,$err,$log,$ifverb) {
  100. global $logfp,$logfh,$opts;
  101. $time=microdate();
  102. if ($log)
  103. if (fwrite($logfh,$time.' '.$msg)==false)
  104. fwrite(STDERR,'Warning: could not write to «'.$logfp.'».'.N);
  105. if (!$ifverb || ($ifverb && $opts['verbose'])) {
  106. if ($clitime)
  107. $msg=$time.' '.$msg;
  108. if ($err)
  109. fwrite(STDERR,$msg);
  110. else
  111. echo($msg);
  112. }
  113. }
  114. for ($i=1; $i<$argc; $i++) {
  115. if ($argv[$i]!='' && $argv[$i][0]=='-') {
  116. switch($argv[$i]) {
  117. case '-h':
  118. case '--help':
  119. echo($help);
  120. exit(0);
  121. break;
  122. case '--makereadme':
  123. file_put_contents(__DIR__.'/README.md','```text'.N.$help.'```'.N);
  124. exit(0);
  125. break;
  126. case '-f':
  127. case '--force':
  128. $opts['force']=true;
  129. break;
  130. case '-v':
  131. case '--verbose':
  132. $opts['verbose']=true;
  133. break;
  134. default:
  135. elecho('Error: option «'.$argv[$i].'» is unknown (use «-h» or «--help» for help).'.N,false,true,false,false);
  136. exit(2);
  137. break;
  138. }
  139. } elseif ($argv[$i]!='' && is_null($projdir)) {
  140. $projdir=preg_replace('#/+$#','',$argv[$i]);
  141. } elseif ($argv[$i]!='') {
  142. elecho('Error: project directory must be specified only once (use «-h» or «--help» for help).'.N,false,true,false,false);
  143. exit(2);
  144. } else {
  145. elecho('Error: empty string («») is not a valid directory specification ;-) (use «-h» or «--help» for help).'.N,false,true,false,false);
  146. exit(2);
  147. }
  148. }
  149. if (is_null($projdir)) {
  150. elecho('Error: you have not specified a project directory (use «-h» or «--help» for help).'.N,false,true,false,false);
  151. exit(2);
  152. } elseif (!file_exists($projdir) || !is_dir($projdir) || !is_readable($projdir) || !is_writeable($projdir)) {
  153. elecho('Error: «'.$projdir.'» is not an existing, readable and writeable directory.'.N,false,true,false,false);
  154. exit(2);
  155. }
  156. $configfp=$projdir.'/'.$configfn;
  157. $logfp=$projdir.'/'.$logfn;
  158. $lockfp=$projdir.'/'.$lockfn;
  159. function microdate() {
  160. $time=microtime(false);
  161. $time=explode(' ',$time);
  162. return(date('Y-m-d H:i:s',$time[1]).'.'.substr($time[0],2));
  163. }
  164. function signalhandler($signal) {
  165. global $logfh, $lockfp, $exitvalue;
  166. $msg=USNAME.' was interrupted by signal «'.$signal.'».'.N;
  167. remlock($lockfp);
  168. if (!is_null($logfh) && $logfh!==false) {
  169. fwrite($logfh,microdate().' '.$msg);
  170. fclose($logfh);
  171. }
  172. echo(N.$msg);
  173. exit($exitvalue);
  174. }
  175. //declare(ticks=1);
  176. pcntl_async_signals(true);
  177. pcntl_signal(SIGTERM,'signalhandler');// Termination ('kill' was called)
  178. pcntl_signal(SIGHUP,'signalhandler');// Terminal log-out
  179. pcntl_signal(SIGINT,'signalhandler');// Interrupted (Ctrl-C is pressed)
  180. function remlock(&$lockfp) {
  181. if (is_file($lockfp))
  182. if (false===@unlink($lockfp))
  183. elecho('Warning: could not remove lockfile («'.$lockfp.'»).'.N,false,true,true,false);
  184. }
  185. $exitvalue=0;
  186. if (file_exists($lockfp) && !$opts['force']) {
  187. elecho('Error: lockfile exists («'.$lockfp.'»), aborting (use «-h» or «--help» for help).'.N,false,true,false,false);
  188. exit(2);
  189. }
  190. if (false===@file_put_contents($lockfp,microdate().N)) {
  191. elecho('Error: could not write lockfile («'.$lockfp.'»), aborting (use «-h» or «--help» for help).'.N,false,true,false,false);
  192. exit(2);
  193. }
  194. $logfh=fopen($logfp,'a');
  195. if ($logfh===false) {
  196. elecho('Error: could not open «'.$logfp.'» for writing.'.N,false,true,false,false);
  197. exit(2);
  198. }
  199. elecho('Starting '.SNAME.'.'.N,false,false,true,true);
  200. $buff=@file($configfp,FILE_IGNORE_NEW_LINES);
  201. if ($buff===false) {
  202. elecho('Error: could not open «'.$configfp.'» to read it (use «-h» or «--help» for help).'.N,false,true,true,false);
  203. remlock($lockfp);
  204. fclose($logfh);
  205. exit(2);
  206. }
  207. $bots=array();
  208. $i=0;
  209. foreach($buff as $line) {
  210. $i++;
  211. if ($line[0]!=';') {
  212. if (preg_match('/^\S+@\S+ \S+ \S{43}$/',$line)!==1) { // vary lazy regex
  213. elecho('Error: could not parse line '.$i.' in «'.$configfp.'» (use «-h» or «--help» for help).'.N,false,true,true,false);
  214. remlock($lockfp);
  215. fclose($logfh);
  216. exit(2);
  217. } else {
  218. $line=explode(' ',$line);
  219. $line[0]=explode('@',$line[0]);
  220. $bots[]=array('username'=>$line[0][0],'domain'=>$line[0][1],'tag'=>$line[1],'token'=>$line[2]);
  221. }
  222. }
  223. }
  224. if (count($bots)<2) {
  225. elecho('Error: «'.$configfp.'» should contain at least two account definitions (use «-h» or «--help» for help).'.N,false,true,true,false);
  226. remlock($lockfp);
  227. fclose($logfh);
  228. exit(2);
  229. }
  230. $mainacc=array_shift($bots);
  231. /*print_r($bots);
  232. $cont=' @agadi@nebbia.fail seguimi da nebbia , , , puntarella, cisti, nebbia.fail ';
  233. $comm=getcommand('agadi@nebbia.fail',$cont,$bots);
  234. if ($comm===false) {
  235. echo('WRONG COMMAND!'.N);
  236. } else {
  237. print_r($comm);
  238. }
  239. remlock($lockfp);
  240. fclose($logfh);
  241. exit(0);*/
  242. $hostname='ssl://'.$mainacc['domain'];
  243. $port=443;
  244. $timeout=5;
  245. $errno='';
  246. $errstr='';
  247. $fh=@fsockopen($hostname,$port,$errno,$errstr,$timeout);
  248. if ($fh===false) {
  249. echo('Error: could not open socket to «'.$hostname.':'.$port.'» ('.$errstr.' - '.$errno.')'.N);
  250. remlock($lockfp);
  251. fclose($logfh);
  252. exit(2);
  253. }
  254. //stream_set_blocking($fh,false);
  255. $method='GET';
  256. $endpoint='/api/v1/streaming/user';
  257. $host=$mainacc['domain'];
  258. $user_agent=SNAME.'-bot';
  259. $token=$mainacc['token'];
  260. $req=array(
  261. $method.' '.$endpoint.' HTTP/1.1',
  262. 'Host: '.$host,
  263. 'User-Agent: '.$user_agent,
  264. 'Authorization: Bearer '.$token,
  265. );
  266. $req=implode(RN,$req).RN.RN;
  267. fwrite($fh,$req);
  268. /*while (!feof($fh)) {
  269. $c=fgetc($fh);
  270. echo('['.ord($c).']'.sub($c));
  271. }
  272. remlock($lockfp);
  273. fclose($logfh);
  274. exit(0);*/
  275. function insubarray($val,$key,&$arr) {
  276. foreach ($arr as $elem)
  277. if ($elem[$key]==$val)
  278. return(true);
  279. return(false);
  280. }
  281. function ckeol(&$str,$eol) {
  282. $slen=strlen($str);
  283. $elen=strlen($eol);
  284. if ($slen<$elen)
  285. return(false);
  286. if (substr($str,$slen-$elen,$elen)==$eol)
  287. return(true);
  288. return(false);
  289. }
  290. function streamgetline(&$fh,$eol) {
  291. $line='';
  292. while (true) {
  293. $line.=fgetc($fh);
  294. if (ckeol($line,$eol))
  295. return($line);
  296. }
  297. }
  298. function strxr($str) {
  299. $xrs='';
  300. for ($i=0; $i<strlen($str); $i++) {
  301. $c=$str[$i];
  302. $xrs.='['.ord($c).']';
  303. if ($c=="\r")
  304. $c='\r';
  305. $xrs.=$c;
  306. }
  307. return($xrs);
  308. }
  309. $i=0;
  310. while (!feof($fh)) {
  311. $line=streamgetline($fh,RN);
  312. $i++;
  313. if (preg_match('/^[0-9a-f]+$/',rtrim($line,"\r\n"))===1) {
  314. $chunk=streamgetline($fh,"\n\r\n");
  315. $chunk=explode("\n",$chunk);
  316. foreach ($chunk as $key=>$val)
  317. $chunk[$key]=rtrim($val,"\r\n");
  318. if (preg_match('/^event: (\w+)$/',$chunk[0],$matches)===1 && $matches[1]=='notification' && preg_match('/^data: {/',$chunk[1])===1) {
  319. $event=$matches[1];
  320. $data='';
  321. for ($ii=1; $ii<count($chunk); $ii++)
  322. if (preg_match('/^[0-9a-f]+$/',$chunk[$ii])!==1)
  323. $data.=$chunk[$ii];
  324. $data=preg_replace('/^data: /','',$data);
  325. $data=json_decode($data,true);
  326. if ($data!==false) {
  327. //elecho('Event: '.$event.N.'Data: '.print_r($data,true),false,false,false,true);
  328. if (array_key_exists('type',$data) &&
  329. $data['type']=='mention' &&
  330. array_key_exists('status',$data) &&
  331. array_key_exists('id',$data['status']) &&
  332. array_key_exists('visibility',$data['status']) &&
  333. ($data['status']['visibility']=='direct' || $data['status']['visibility']=='public') &&
  334. array_key_exists('language',$data['status']) &&
  335. array_key_exists('content',$data['status']) &&
  336. array_key_exists('account',$data['status']) &&
  337. array_key_exists('acct',$data['status']['account'])) {
  338. $content=strip_tags($data['status']['content']);
  339. $account=$data['status']['account']['acct'];
  340. if (preg_match('/@/',$account)!==1)
  341. $account.='@'.$mainacc['domain'];
  342. $language=$data['status']['language'];
  343. $visibility=$data['status']['visibility'];
  344. $tootid=$data['status']['id'];
  345. $domain=explode('@',$account);
  346. $domain=$domain[1];
  347. if (insubarray($domain,'domain',$bots)) {
  348. elecho('Account: '.$account.N.'Content: '.$content.N.'Language: '.$language.N,false,false,false,true);
  349. $command=getcommand($mainacc['username'],$content,$bots,$domain);
  350. if ($command!==false && count($command['bots'])>0) {
  351. (!$command['follow']) ? $foll=false : $foll=true;
  352. $un=array('un','');
  353. $kofrom=array();
  354. $okfrom=array();
  355. foreach ($command['bots'] as $bot) {
  356. $botaccount=$bot['username'].'@'.$bot['domain'];
  357. $accid=getaccid($account,$bot['domain'],$bot['token'],false,$exitvalue);
  358. if (is_null($accid))
  359. $accid=getaccid($account,$bot['domain'],$bot['token'],true,$exitvalue);
  360. if (is_null($accid)) {
  361. elecho('Warning: could not get «'.$account.'» id from «'.$bot['domain'].'».'.N,false,true,true,false);
  362. $kofrom[]=$botaccount;
  363. $exitvalue=1;
  364. } elseif (followun($accid,$bot['domain'],$bot['token'],$foll,$exitvalue)) {
  365. elecho('SUCCESS! «'.$account.'» is now '.$un[$foll].'followed from «'.$botaccount.'».'.N,false,false,true,true);
  366. $okfrom[]=$botaccount;
  367. } else {
  368. elecho('Warning: could not '.$un[$foll].'follow «'.$account.'» from «'.$botaccount.'».'.N,false,true,true,false);
  369. $kofrom[]=$botaccount;
  370. $exitvalue=1;
  371. }
  372. }
  373. if (count($kofrom)==0) {
  374. if ($language!='it') {
  375. if ($foll)
  376. $retoot='Ok! I started following you from '.implode(', ',$okfrom).'! :-)';
  377. else
  378. $retoot='Ok, I stopped following you from '.implode(', ',$okfrom).'.';
  379. } else {
  380. if ($foll)
  381. $retoot='Ok! Ho cominciato a seguirti da '.implode(', ',$okfrom).'! :-)';
  382. else
  383. $retoot='Ok, ho smesso di seguirti da '.implode(', ',$okfrom).'.';
  384. }
  385. } elseif (count($okfrom)==0) {
  386. if ($language!='it') {
  387. if ($foll)
  388. $retoot='Sorry! I could not start following you from any of my accounts :\'-('.RN.'I can only suggest you to try again later.';
  389. else
  390. $retoot='Sorry! I could not stop following you from any of my accounts :\'-('.RN.'I can only suggest you to try again later.';
  391. } else {
  392. if ($foll)
  393. $retoot='Mannaggia! Non son riuscito a cominciare a seguirti da nessuno dei miei account! :\'-('.RN.'Posso solo suggerirti di ritentare più tardi.';
  394. else
  395. $retoot='Mannaggia! Non son riuscito a smettere di seguirti da nessuno dei miei account! :\'-('.RN.'Posso solo suggerirti di ritentare più tardi.';
  396. }
  397. } else {
  398. if ($language!='it') {
  399. if ($foll)
  400. $retoot='Hey, I started following you from '.implode(', ',$okfrom).', but I could not from '.implode(', ',$kofrom).'. I’m sorry! :\'-('.RN.'I can only suggest you to try again later.';
  401. else
  402. $retoot='Hey, I stopped following you from '.implode(', ',$okfrom).', but I could not from '.implode(', ',$kofrom).'. I’m sorry! :\'-('.RN.'I can only suggest you to try again later.';
  403. } else {
  404. if ($foll)
  405. $retoot='Oi, ho cominciato a seguirti da '.implode(', ',$okfrom).', ma non ci sono riuscito da '.implode(', ',$kofrom).', mannaggia! Mi spiace! :\'-('.RN.'Posso solo suggerirti di riprovare più tardi.';
  406. else
  407. $retoot='Oi, ho smesso di seguirti da '.implode(', ',$okfrom).', ma non ci sono riuscito da '.implode(', ',$kofrom).', mannaggia! Mi spiace! :\'-('.RN.'Posso solo suggerirti di riprovare più tardi.';
  408. }
  409. }
  410. } elseif ($command===false) {
  411. elecho('Warning: toot from «'.$account.'» has been ignored (no valid command in content).'.N,false,true,true,false);
  412. $exitvalue=1;
  413. if ($language!='it')
  414. $retoot='Sorry, I could not understand your request :-(';
  415. else
  416. $retoot='Mi spiace, non ho capito la tua richiesta :-(';
  417. } else {
  418. // this should happen only when only two accounts are defined, the main-bot one and just one follow-bot one, and the request is to follow or unfollow all
  419. elecho('Warning: toot from «'.$account.'» has been ignored (found no bots to apply command to).'.N,false,true,true,false);
  420. $exitvalue=1;
  421. if ($language!='it')
  422. $retoot='Sorry, no bots to apply your request to :-(';
  423. else
  424. $retoot='Mi spiace, non ci sono bot a cui applicare la tua richiesta :-(';
  425. }
  426. elecho('Reply toot message: «'.$retoot.'».'.N,false,false,false,true);
  427. $postdata=array(
  428. 'status'=>'@'.$account.RN.$retoot,
  429. 'visibility'=>$visibility,
  430. 'in_reply_to_id'=>$tootid,
  431. );
  432. $res=toot($postdata,$mainacc['domain'],$mainacc['token'],$exitvalue);
  433. } else {
  434. elecho('Warning: toot from «'.$account.'» has been ignored («foreign» domain).'.N,false,true,true,false);
  435. $exitvalue=1;
  436. }
  437. } elseif ($data['type']!='mention') {
  438. elecho('Warning: event data were not of type «mention» (type was «'.$data['type'].'»).'.N,false,true,true,false);
  439. $exitvalue=1;
  440. } elseif ($data['status']['visibility']!='direct' && $data['status']['visibility']!='public') {
  441. elecho('Warning: toot visibility was not «direct» or «public» (it was «'.$data['status']['visibility'].'»).'.N,false,true,true,false);
  442. $exitvalue=1;
  443. } else {
  444. elecho('Warning: event data were not valid.'.N,false,true,true,false);
  445. $exitvalue=1;
  446. }
  447. } else {
  448. elecho('Warning: event data were not valid JSON.'.N,false,true,true,false);
  449. $exitvalue=1;
  450. }
  451. }
  452. }
  453. }
  454. remlock($lockfp);
  455. fclose($logfh);
  456. exit($exitvalue);
  457. function getcommand($user,$cont,&$allbots,$domain) {
  458. $allbotsbut=array();
  459. foreach ($allbots as $bot)
  460. if ($bot['domain']!=$domain)
  461. $allbotsbut[]=$bot;
  462. $cont=str_replace(array("\n","\r","\t"),array(' ',' ',' '),$cont);
  463. $cont=preg_replace('/ +/',' ',$cont);
  464. $cont=trim($cont);
  465. if (preg_match('/^@'.$user.' (.*)$/i',$cont,$res)!==1)
  466. return(false);
  467. $incomm=trim($res[1]);
  468. if (preg_match('/^(seguimi|follow me)$/',$incomm)===1) {
  469. return(array('follow'=>true,'bots'=>$allbotsbut));
  470. } elseif (preg_match('/^(non seguirmi|don\'t follow me)$/',$incomm)===1) {
  471. return(array('follow'=>false,'bots'=>$allbotsbut));
  472. } elseif (preg_match('/^(seguimi da |follow me from |non seguirmi da |don\'t follow me from )(.+)$/',$incomm,$res)===1) {
  473. (preg_match('/^(seguimi da |follow me from ).+$/',$incomm,$res[1])===1) ? $follow=true : $follow=false;
  474. $buff=array();
  475. $bots=preg_split('/[ ,]+/',$res[2]);
  476. foreach ($bots as $key=>$botdom) {
  477. $found=false;
  478. foreach($allbots as $dbot) {
  479. if ($botdom==$dbot['domain'] || $botdom==$dbot['tag']) {
  480. if (!array_key_exists($dbot['domain'],$buff))
  481. $buff[$dbot['domain']]=$dbot;
  482. $found=true;
  483. break;
  484. }
  485. }
  486. if (!$found) return(false);
  487. }
  488. $bots=array();
  489. foreach ($buff as $bot)
  490. $bots[]=$bot;
  491. return(array('follow'=>$follow,'bots'=>$bots));
  492. }
  493. return(false);
  494. }
  495. function toot($postdata,$domain,$token,&$exitvalue) {
  496. $endpoint='https://'.$domain.'/api/v1/statuses';
  497. elecho('Trying to POST to «'.$endpoint.'».'.N,false,false,false,true);
  498. $content=http_build_query($postdata);
  499. $cxopts=array(
  500. 'http'=>array(
  501. 'header'=>'Authorization: Bearer '.$token.RN.
  502. 'Content-type: application/x-www-form-urlencoded'.RN,
  503. 'method'=>'POST',
  504. 'content'=>$content,
  505. )
  506. );
  507. $context=stream_context_create($cxopts);
  508. $res=@file_get_contents($endpoint,false,$context);
  509. if ($res===false) {
  510. elecho('Warning: could not POST to «'.$endpoint.'» (reason: '.implode(' | ',$http_response_header).').'.N,false,true,true,false);
  511. $exitvalue=1;
  512. } else {
  513. elecho('Toot was correctly POSTed :-)'.N,false,false,true,true);
  514. return(true);
  515. }
  516. return(false);
  517. }
  518. function followun($accid,$domain,$token,$follow,&$exitvalue) {
  519. $endpoint='https://'.$domain.'/api/v1/accounts/'.$accid.'/';
  520. if ($follow)
  521. $endpoint.='follow';
  522. else
  523. $endpoint.='unfollow';
  524. elecho('Trying to POST to «'.$endpoint.'».'.N,false,false,false,true);
  525. $cxopts=array(
  526. 'http'=>array(
  527. 'header'=>'Authorization: Bearer '.$token.RN.
  528. 'Content-type: application/x-www-form-urlencoded'.RN,
  529. 'method'=>'POST',
  530. )
  531. );
  532. $context=stream_context_create($cxopts);
  533. $res=@file_get_contents($endpoint,false,$context);
  534. if ($res===false) {
  535. elecho('Warning: could not POST to «'.$endpoint.'» (reason: '.implode(' | ',$http_response_header).').'.N,false,true,true,false);
  536. $exitvalue=1;
  537. } else {
  538. return(true);
  539. }
  540. return(false);
  541. }
  542. function getaccid($account,$domain,$token,$resolve,$exitvalue) {
  543. $endpoint='https://'.$domain.'/api/v2/search?q=@'.$account.'&type=accounts&limit=1';
  544. if ($resolve)
  545. $endpoint.='&resolve=true';
  546. elecho('Trying to GET «'.$endpoint.'».'.N,false,false,false,true);
  547. $cxopts=array(
  548. 'http'=>array(
  549. 'header'=>'Authorization: Bearer '.$token.RN.
  550. 'Content-type: application/x-www-form-urlencoded'.RN,
  551. 'method'=>'GET',
  552. )
  553. );
  554. $context=stream_context_create($cxopts);
  555. $res=@file_get_contents($endpoint,false,$context);
  556. if ($res===false) {
  557. elecho('Warning: could not GET «'.$endpoint.'» (reason: '.implode(' | ',$http_response_header).').'.N,false,true,true,false);
  558. $exitvalue=1;
  559. } else {
  560. $res=@json_decode($res,true);
  561. if ($res===false) {
  562. elecho('Warning: «'.$endpoint.'» did not return parsable JSON data.'.N,false,true,true,false);
  563. $exitvalue=1;
  564. } elseif (!array_key_exists('accounts',$res)) {
  565. elecho('Warning: «'.$endpoint.'» did not return a valid result («accounts» key missing from results array).'.N,false,true,true,false);
  566. $exitvalue=1;
  567. } elseif (count($res['accounts'])<1) {
  568. elecho('Warning: «'.$endpoint.'» did not return any result.'.N,false,true,true,false);
  569. $exitvalue=1;
  570. } elseif (!array_key_exists('id',$res['accounts'][0])) {
  571. elecho('Warning: «'.$endpoint.'» did not return a valid result («id» key missing from results «accounts» array).'.N,false,true,true,false);
  572. $exitvalue=1;
  573. } else {
  574. return($res['accounts'][0]['id']);
  575. }
  576. }
  577. return(null);
  578. }
  579. ?>