123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503 |
- #!/usr/bin/php
- <?php
- /*
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
- // todo: add a "last updated on" header
- $SCRIPTNAME='pfaltgall';
- $SCRIPTVERSION='0.2.1';
- $SCRIPTURL='https://git.lattuga.net/Jones/pfaltgall';
- require 'lib/ckratelimit.php';
- require 'lib/httpjson.php';
- $configfp=null;
- $outfp=null;
- $conf=[
- 'host'=>null,
- 'token'=>null,
- ];
- $help=
- "[[[ SYNOPSIS ]]]
- {$SCRIPTNAME} [options] <configuration file path> <output file path>
- [[[ DESCRIPTION ]]]
- This is {$SCRIPTNAME} v{$SCRIPTVERSION}, a CLI PHP script that can generate an html file
- with a gallery from your Pixelfed profile. The html gallery file will load
- images dynamically, display each one using almost all the available screen
- space and let you jump right from the start to any point in the timeline.
- It will also show each post’s text content, its date, and provide each image
- with its description (alt-text), if present.
- See my example gallery here: https://rame.altervista.org/foto-pixelfed
- In order to create the html gallery file, you just need to login to your
- Pixelfed account and get an app token (Settings -> Applications -> Create new
- token), then create a configuration file for {$SCRIPTNAME} like this (don’t write
- the «---» lines):
- ---
- host=your_instance_host
- token=your_token
- ---
- For example:
- ---
- host=pixelfed.social
- token=as7f8a7s0d89f7as97df09a8s7d90f81jkl2h34lkj12h3jkl4
- ---
- Then run {$SCRIPTNAME} with the path of the configuration file you have created
- and the path of an output file as arguments (if the output file exists, it
- will be overwritten), e.g.: «{$SCRIPTNAME} goofy@pixelfed.social.conf index.html».
- This will create an html file that will be ready to be put where you want
- (you’ll also be able to see it locally, obviously).
- [[[ OPTIONS ]]]
- -h, --help
- Show this help text and exit.
- [[[ DISCLAIMER AND LICENSE ]]]
- 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";
- for ($i=1; $i<$argc; $i++) {
- if ($argv[$i]=='-h' || $argv[$i]=='--help') {
- echo $help;
- exit(0);
- } elseif ($argv[$i]=='--make-readme') {
- file_put_contents(__DIR__.'/README.md',"```text\n{$help}\n```\n");
- exit(0);
- } elseif (is_null($configfp)) {
- $configfp=$argv[$i];
- } elseif (is_null($outfp)) {
- $outfp=$argv[$i];
- } else {
- eecho("Error: «{$argv[$i]}» is not a valid option and configuration file and output file have already been set to «{$configfp}» and «{$outfp}» (use «-h» or «--help» to read help).\n");
- exit(1);
- }
- }
- $errors=[];
- if (is_null($configfp))
- $errors[]="you have not specified a config file";
- if (is_null($outfp))
- $errors[]="you have not specified an output file";
- if (count($errors)>0) {
- eecho("Errors:\n");
- foreach ($errors as $val)
- eecho(" - {$val}\n");
- eecho("Use «-h» or «--help» to read help.\n");
- exit(1);
- }
- $fconf=@parse_ini_file($configfp);
- if ($fconf===false) {
- eecho("Error: {$SCRIPTNAME} could not open configuration file «{$configfp}».\n");
- exit(1);
- }
- $errors=[];
- if (!array_key_exists('host',$fconf))
- $errors[]="no «host» defined";
- if (!array_key_exists('token',$fconf))
- $errors[]="no «token» defined";
- if (count($errors)>0) {
- eecho("Error: {$SCRIPTNAME} has found errors in «{$configfp}» configuration file:\n");
- foreach ($errors as $val)
- eecho(" - {$val}\n");
- eecho("Use «-h» or «--help» to read help.\n");
- exit(1);
- }
- foreach ($conf as $key=>$val)
- if (array_key_exists($key,$fconf))
- $conf[$key]=$fconf[$key];
- //print_r($conf);
- $acc=httpjson("https://{$conf['host']}/api/v1/accounts/verify_credentials",null,null,null,null,$conf['token']);
- //print_r($res);
- if (!$acc['ok']) {
- eecho("Error: {$SCRIPTNAME} could not retrieve the account associated with the given token ({$acc['errors']}).\n");
- exit(2);
- }
- ckrl($acc['headers']);
- $acc=$acc['content'];
- $imgurls=[];
- $imgs='';
- $i=0;
- $ic=0;
- do {
- $i++;
- echo "\rRetrieving chunk {$i}";
- $endpoint="https://{$conf['host']}/api/v1/accounts/{$acc['id']}/statuses?limit=40&only_media=1&exclude_replies=1&exclude_reblogs=1";
- if (isset($max_id)) $endpoint.="&max_id={$max_id}";
- $res=httpjson($endpoint,null,null,null,null,$conf['token']);
- //print_r($res);
- if (!$res['ok']) {
- eecho("\rError: {$SCRIPTNAME} could not retrieve chunk {$i} of statuses ({$res['errors']}).\n");
- exit(2);
- }
- $count=count($res['content']);
- if ($count>0) {
- foreach ($res['content'] as $status) {
- if (isset($status['created_at']) && preg_match('#^\s+$#',$status['created_at'])!==1) {
- $date=strtotime($status['created_at']);
- $date=' <span class="grey">['.date('Y/m/d',$date).']</span>';
- } else {
- $date='';
- }
- if (isset($status['content']) && preg_match('#^\s+$#',$status['content'])!==1) {
- $desc=strip_tags($status['content']);
- $desc=preg_replace('/#\w+/','',$desc);
- $desc=trim($desc);
- } else {
- $desc='';
- }
- if (isset($status['media_attachments']) && is_array($status['media_attachments'])) {
- $ca=count($status['media_attachments']);
- $ia=0;
- foreach ($status['media_attachments'] as $attachment) {
- if (isset($attachment['url'])) {
- $imgurl=$attachment['url'];
- $imgurls[]=$imgurl;
- $ia++;
- if (isset($attachment['description']) && preg_match('#^\s+$#',$attachment['description'])!==1)
- $altdesc=' alt="'.htmlspecialchars(trim($attachment['description']),ENT_QUOTES|ENT_HTML5).'"';
- else
- $altdesc='';
- if ($ca>1)
- $icnt=" ({$ia}/{$ca})";
- else
- $icnt='';
- $imgs.="<div class=\"page\"><table class=\"imgtab\"><tr><td class=\"imgcel\"><a href=\"{$imgurl}\" name=\"img{$ic}\"><img class=\"img\" decoding=\"async\" loading=\"lazy\" id=\"img{$ic}\" src=\"{$acc['avatar_static']}\"{$altdesc}></a></td></tr><caption class=\"imgcaptcel\">{$desc}{$icnt}{$date}</caption></table></div>\n";
- $ic++;
- }
- }
- }
- }
- $max_id=$res['content'][$count-1]['id'];
- //echo "count: {$count}; max_id: {$max_id}\n";
- if ($count<40)
- break;
- }
- ckrl($res['headers']);
- } while ($count>0);
- echo "\n";
- $accadd="{$acc['username']}@{$conf['host']}";
- $title=$accadd;
- $profhead="<a href=\"{$acc['url']}\">{$accadd}</a>";
- if ($acc['display_name']!='') {
- $accdispname=htmlspecialchars($acc['display_name'],ENT_QUOTES|ENT_HTML5);
- $title="{$accdispname} ({$title})";
- $profhead="<strong>{$accdispname}</strong><br>({$profhead})";
- }
- $html='<!DOCTYPE HTML>
- <html lang="en">
- <head>
- <title>'.$title.'</title>
- <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
- <meta name="description" content="Album">
- <meta name="viewport" content="width=device-width, height=device-height, initial-scale=1.0, minimum-scale=1.0, user-scalable=yes">
- <meta property="og:image" content="'.$acc['avatar_static'].'">
- <link rel="icon" type="image/png" href="'.$acc['avatar_static'].'">
- <!--
- <meta property="og:image" content="imgs/ogimage.jpg">
- <link rel="icon" type="image/png" href="imgs/icon-16.png" sizes="16x16">
- <link rel="icon" type="image/png" href="imgs/icon-24.png" sizes="24x24">
- <link rel="icon" type="image/png" href="imgs/icon-32.png" sizes="32x32">
- <link rel="icon" type="image/png" href="imgs/icon-64.png" sizes="64x64">
- <link rel="icon" type="image/png" href="imgs/icon-128.png" sizes="128x128">
- <link rel="apple-touch-icon-precomposed" href="imgs/icon-180.png">
- <link rel="icon" type="image/png" href="imgs/icon-192.png" sizes="192x192">
- <link rel="icon" type="image/png" href="imgs/icon-256.png" sizes="256x256">
- <link rel="icon" type="image/png" href="imgs/icon-512.png" sizes="512x512">
- -->
- <!-- <link rel="stylesheet" type="text/css" href="gallery.css"> -->
- <style>
- * {
- box-sizing: border-box;
- }
- html {
- scroll-behavior: smooth;
- height: 100vh;
- }
- body {
- font-family: "sans";
- font-size: 12pt;
- background-color: black;
- color: white;
- margin: 0;
- height: 100vh;
- }
- a {
- color: #87decd;
- }
- a:focus {
- outline: none;
- }
- p {
- margin: 0;
- padding: 0;
- text-indent: 3mm;
- line-height: 1.5em;
- }
- p.firstp, p.center {
- text-indent: 0;
- }
- p.center {
- text-align: center;
- text-wrap: balance;
- padding: 0 0 1.5em 0;
- }
- .profile {
- width: 640px;
- }
- .avatar {
- border-radius: 12px;
- }
- .grey {
- color: #888;
- }
- hr {
- display: block;
- border: none;
- height: 1px;
- background-color: #666666;
- margin: 3mm 0 3mm 0;
- }
- #maindiv {
- width: 100%;
- height: 100%;
- overflow: auto;
- scroll-snap-type: y mandatory;
- scroll-padding: 0;
- }
- .page {
- display: flex;
- align-items: center;
- justify-content: center;
- width: 100%;
- height: 100%;
- scroll-snap-stop: always;
- scroll-snap-align: start;
- scroll-margin: 0;
- /*border: 1px solid yellow;*/
- }
- .imgtab, .imgtab tr, .imgtab td {
- margin: 0;
- padding: 0;
- border: none;
- border-collapse: collapse;
- table-layout: fixed;
- scroll-snap-align: none;
- }
- .img {
- display: block;
- position: relative;
- max-width: 92vw;
- max-height: 92vh;
- border: 8px solid white;
- border-bottom: none;
- scroll-snap-align: none;
- }
- .imgcaptcel {
- background-color: white;
- color: black;
- padding: 4px 8px 4px 8px;
- font-size: 10pt;
- caption-side: bottom;
- text-align: left;
- line-height: 1.2;
- scroll-snap-align: none;
- }
- .textdiv {
- width: 20cm;
- max-width: 92%;
- background-color: #333333;
- border-radius: 8px;
- padding: 3mm;
- scroll-snap-align: none;
- }
- #notif {
- display: none;
- position: fixed;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- background-color: rgb(51 51 51 / .85);
- padding: 4px 6px 4px 6px;
- color: white;
- border-radius: 6px;
- font-size: 10pt;
- z-index: 1;
- cursor: pointer;
- }
- .link {
- margin-top: 2px;
- float: right;
- color: #333333;
- cursor: pointer;
- }
- #tools {
- /*display: none;*/
- width: 100%;
- position: fixed;
- left: 0;
- bottom: 0;
- background-color: rgb(51 51 51 / .85);;
- padding: 4px 6px 4px 6px;
- color: white;
- font-size: 10pt;
- z-index: 1;
- }
- @media only screen and (max-width:15cm) {
- .img {
- max-width: 100vw;
- max-height: 98vh;
- border: 2px solid white;
- }
- .imgcaptcel {
- padding: 1px 2px 1px 2px;
- font-size: 8pt;
- }
- .textdiv {
- font-size: 9pt;
- }
- .link {
- margin-top: 0;
- }
- }
- </style>
- <script type="text/javascript">
- let imgurls=["'.implode('", "',$imgurls).'"];
- let phimgurl="'.$acc['avatar_static'].'";//placeholder image url
- function prel() {
- var md=document.getElementById("maindiv"),
- ph=window.innerHeight,
- th=md.scrollHeight,
- pages=Math.round(th/ph),
- sy=Math.round(md.scrollTop),
- page=Math.round(pages-(th-sy)/ph)+1,
- img;
- //console.log(ph+" "+th+" "+pages+" "+sy+" "+page);
- if (page>1) {//current
- img=document.getElementById("img"+(page-2));
- if (img.src==phimgurl) {
- img.src=imgurls[page-2];
- img.loading="eager";
- }
- }
- if (page<pages) {//next
- img=document.getElementById("img"+(page-1));
- if (img.src==phimgurl) {
- img.src=imgurls[page-1];
- img.loading="eager";
- }
- }
- if (page>2) {//previous
- img=document.getElementById("img"+(page-3));
- if (img.src==phimgurl) {
- img.src=imgurls[page-3];
- img.loading="eager";
- }
- }
- if (page+1<pages) {//next-next
- img=document.getElementById("img"+page);
- if (img.src==phimgurl) {
- img.src=imgurls[page];
- img.loading="eager";
- }
- }
- if (page>3) {//previous-previous
- img=document.getElementById("img"+(page-4));
- if (img.src==phimgurl) {
- img.src=imgurls[page-4];
- img.loading="eager";
- }
- }
- }
- </script>
- </head>
- <body onload="prel();">
- <div id="maindiv" onscrollend="prel();">
- <div class="page">
- <div class="profile">
- <p class="center"><img src="'.$acc['avatar_static'].'" class="avatar"></p>
- <p class="center">'.$profhead.'</p>
- <p class="center">'.nl2br($acc['note']).'</p>
- <p class="center"><span class="grey">Made with <a href="'.$SCRIPTURL.'">'.$SCRIPTNAME.'</a> v'.$SCRIPTVERSION.'</span></p>
- </div>
- </div>
- '.$imgs.'
- </div>
- </body>
- </html>
- ';
- if (@file_put_contents($outfp,$html)===false) {
- eecho("Error: {$SCRIPTNAME} could not save html into «{$outfp}».\n");
- exit(2);
- }
- exit(0);
- function eecho($text) {
- fwrite(STDERR,$text);
- }
- function ckrl($headers) {
- $rl=ckratelimit($headers);
- if ($rl['ok'] && $rl['remaining']==0) {
- echo "\rInfo: reached rate limit, sleeping for {$rl['sleep']} second(s)... ";
- sleep($rl['sleep']);
- echo "\n";
- }
- }
- ?>
|