First commit
This commit is contained in:
commit
0150d57467
4 changed files with 573 additions and 0 deletions
41
README.md
Normal file
41
README.md
Normal file
|
@ -0,0 +1,41 @@
|
|||
```text
|
||||
[[[ SYNOPSIS ]]]
|
||||
|
||||
pfaltgall [options] <configuration file path>
|
||||
|
||||
[[[ DESCRIPTION ]]]
|
||||
|
||||
This is pfaltgall v0.1, a CLI PHP script that can generate an html file with
|
||||
a gallery from your Pixelfed profile.
|
||||
In order to create it, 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 pfaltgall like this (don’t write the «---» lines):
|
||||
|
||||
---
|
||||
host=your_instance_host
|
||||
token=your_token
|
||||
---
|
||||
|
||||
For example:
|
||||
|
||||
---
|
||||
host=pixelfed.social
|
||||
token=as7f8a7s0d89f7as97df09a8s7d90f81jkl2h34lkj12h3jkl4
|
||||
---
|
||||
|
||||
Then run pfaltgall with the path of the configuration file you have
|
||||
created. This will create an «index.html» file that will be ready to be put
|
||||
where you want (you can also 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.
|
||||
|
||||
```
|
50
lib/ckratelimit.php
Normal file
50
lib/ckratelimit.php
Normal file
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
function ckratelimit($headers) {
|
||||
$aaheaders=[];
|
||||
array_shift($headers);
|
||||
foreach ($headers as $header)
|
||||
if (preg_match('#^([^:]+): (.*)$#',$header,$matches)===1)
|
||||
$aaheaders[strtolower($matches[1])]=$matches[2];
|
||||
//$aaheaders['x-ratelimit-remaining']=0;
|
||||
//print_r($aaheaders);
|
||||
if (!isset($aaheaders['date'])) return ['ok'=>false,'error'=>'no «date» header'];
|
||||
if (!isset($aaheaders['x-ratelimit-reset'])) return ['ok'=>false,'error'=>'no «x-ratelimit-reset» header'];
|
||||
if (!isset($aaheaders['x-ratelimit-remaining'])) return ['ok'=>false,'error'=>'no «x-ratelimit-remaining» header'];
|
||||
if (preg_match('#^\d+$#',$aaheaders['x-ratelimit-remaining'])!==1) return ['ok'=>false,'error'=>'«x-ratelimit-remaining» header is not an integer'];
|
||||
$remaining=$aaheaders['x-ratelimit-remaining']+0;
|
||||
$date=@strtotime($aaheaders['date']);
|
||||
if (!is_int($date)) return ['ok'=>false,'error'=>'«date» header could not be converted to a unix timestamp'];
|
||||
$reset=@strtotime($aaheaders['x-ratelimit-reset']);
|
||||
if (!is_int($reset)) return ['ok'=>false,'error'=>'«x-ratelimit-reset» header could not be converted to a unix timestamp'];
|
||||
// don't do the one on the line below, since it happens lots of times
|
||||
//if ($reset<$date) return ['ok'=>false,'error'=>'the unix timestamp parsed from «x-ratelimit-reset» header is less than the unix timestamp parsed from «date» header'];
|
||||
if ($remaining==0)
|
||||
return ['ok'=>true,'sleep'=>$reset-$date+1,'remaining'=>$remaining];
|
||||
else
|
||||
return ['ok'=>true,'sleep'=>0,'remaining'=>$remaining];
|
||||
}
|
||||
|
||||
/*
|
||||
// test
|
||||
$context=[
|
||||
'http'=>[
|
||||
'header'=>"Accept: application/json\r\n";
|
||||
]
|
||||
];
|
||||
$context=stream_context_create($context);
|
||||
while (true) {
|
||||
$res=@file_get_contents('https://livellosegreto.it/api/v2/instance',false,$context);
|
||||
echo "{$res}\n";
|
||||
print_r($http_response_header);
|
||||
$rl=ckratelimit($http_response_header);
|
||||
print_r($rl);
|
||||
if ($rl['sleep']>0) {
|
||||
echo 'Reached rate limit, sleeping for '.ght($rl['sleep']).' (until '.date('c',time()+$rl['sleep']).') ...';
|
||||
sleep($rl['sleep']);
|
||||
echo "\n";
|
||||
}
|
||||
}
|
||||
exit(0);*/
|
||||
|
||||
?>
|
65
lib/httpjson.php
Normal file
65
lib/httpjson.php
Normal file
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
|
||||
function httpjson($endpoint,$timeout=null,$method=null,$postdata=null,$accept=null,$token=null,$okcodes=null) {
|
||||
if (is_null($timeout)) $timeout=5;
|
||||
if (is_null($method)) $method='GET';
|
||||
if (is_null($accept)) $accept='application/json';
|
||||
if (is_null($okcodes)) $okcodes=[200];
|
||||
$context=[
|
||||
'http'=>[
|
||||
'timeout'=>$timeout,
|
||||
'method'=>$method,
|
||||
'user_agent'=>'Mozilla/5.0 (X11; Linux x86_64; rv:121.0) Gecko/20100101 Firefox/121.0',
|
||||
'header'=>"Accept: {$accept}\r\n",
|
||||
'ignore_errors'=>true
|
||||
]
|
||||
];
|
||||
if (!is_null($token)) $context['http']['header'].="Authorization: Bearer {$token}\r\n";
|
||||
if (!is_null($postdata)) {
|
||||
$context['http']['header'].="Content-type: application/x-www-form-urlencoded\r\n";
|
||||
$context['http']['content']=http_build_query($postdata);
|
||||
}
|
||||
$context=stream_context_create($context);
|
||||
$headers=[];
|
||||
$errors=[];
|
||||
$ret=['ok'=>false,'headers'=>[],'content'=>[],'errors'=>null];
|
||||
$http_response_header=null;
|
||||
$res=@file_get_contents($endpoint,false,$context);
|
||||
if ($res===false) {
|
||||
$errors[]="could not connect";
|
||||
} else {
|
||||
if (is_array($http_response_header)) {
|
||||
$httprc=null;
|
||||
$li=count($http_response_header)-1;
|
||||
for ($i=$li; $i>=0; $i--) {
|
||||
array_unshift($headers,$http_response_header[$i]);
|
||||
if (preg_match('#HTTP/\S+\s+(\d+)#',$http_response_header[$i],$matches)===1) {
|
||||
$httprc=$matches[1]+0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (is_null($httprc))
|
||||
$errors[]="got no HTTP response status code";
|
||||
elseif (!in_array($httprc,$okcodes))
|
||||
$errors[]="got «{$httprc}» HTTP response status code";
|
||||
} else {
|
||||
$errors[]="«got no headers";
|
||||
}
|
||||
$res=@json_decode($res,true);
|
||||
if ($res===false) {
|
||||
$errors[]="got no valid JSON";
|
||||
} else {
|
||||
if (count($errors)>0 && isset($res['error']))
|
||||
$errors[]=$res['error'];
|
||||
$ret['content']=$res;
|
||||
}
|
||||
}
|
||||
if (count($errors)==0)
|
||||
$ret['ok']=true;
|
||||
$errors=implode('; ',$errors);
|
||||
$ret['headers']=$headers;
|
||||
$ret['errors']=$errors;
|
||||
return $ret;
|
||||
}
|
||||
|
||||
?>
|
417
pfaltgall
Executable file
417
pfaltgall
Executable file
|
@ -0,0 +1,417 @@
|
|||
#!/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/>.
|
||||
*/
|
||||
|
||||
$SCRIPTNAME='pfaltgall';
|
||||
$SCRIPTVERSION='0.1';
|
||||
|
||||
require 'lib/ckratelimit.php';
|
||||
require 'lib/httpjson.php';
|
||||
|
||||
$configfp=null;
|
||||
|
||||
$conf=[
|
||||
'host'=>null,
|
||||
'token'=>null,
|
||||
'accid'=>null
|
||||
];
|
||||
|
||||
$help=
|
||||
"[[[ SYNOPSIS ]]]
|
||||
|
||||
{$SCRIPTNAME} [options] <configuration 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.
|
||||
In order to create it, 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. This will create an «index.html» file that will be ready to be put
|
||||
where you want (you can also 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];
|
||||
} else {
|
||||
eecho("Error: «{$argv[$i]}» is not a valid option and the configuration file has already been set to «{$configfp}» (use «-h» or «--help» to read help).\n");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (is_null($configfp)) {
|
||||
eecho("Error: you have not specified a config file (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 id associated with the given token ({$acc['errors']}).\n");
|
||||
exit(2);
|
||||
}
|
||||
ckrl($acc['headers']);
|
||||
$acc=$acc['content'];
|
||||
|
||||
$urls=[];
|
||||
$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=' ['.date('Y/m/d',$date).']';
|
||||
} 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'])) {
|
||||
$url=$attachment['url'];
|
||||
$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=\"{$url}\" name=\"img{$ic}\"><img class=\"img\" src=\"{$url}\"{$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";
|
||||
|
||||
$title="{$acc['username']}@{$conf['host']}";
|
||||
if ($acc['display_name']!='') $title=htmlspecialchars($acc['display_name'],ENT_QUOTES|ENT_HTML5)." ({$title})";
|
||||
|
||||
$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.3em;
|
||||
}
|
||||
|
||||
p.firstp, p.center {
|
||||
text-indent: 0;
|
||||
}
|
||||
|
||||
p.center {
|
||||
text-align: center;
|
||||
text-wrap: balance;
|
||||
padding: 1em 0 1em 0;
|
||||
}
|
||||
|
||||
.profile {
|
||||
width: 640px;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
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>
|
||||
</head>
|
||||
<body>
|
||||
<div id="maindiv">
|
||||
<div class="page">
|
||||
<div class="profile">
|
||||
<p class="center"><img src="'.$acc['avatar_static'].'" class="avatar"></p>
|
||||
<p class="center"><a href="'.$acc['url'].'">'.$title.'</a></p>
|
||||
<p class="center">'.nl2br($acc['note']).'</p>
|
||||
</div>
|
||||
</div>
|
||||
'.$imgs.'
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
';
|
||||
|
||||
file_put_contents('index.html',$html);
|
||||
|
||||
exit(0);
|
||||
|
||||
|
||||
function eecho($text) {
|
||||
fwrite(STDERR,$text);
|
||||
}
|
||||
|
||||
function ckrl($headers) {
|
||||
$rl=ckratelimit($headers);
|
||||
if ($rl['ok'] && $rl['sleep']>0) {
|
||||
echo "\rInfo: reached rate limit, sleeping for {$rl['sleep']} second(s)... ";
|
||||
sleep($rl['sleep']);
|
||||
echo "\n";
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
Loading…
Reference in a new issue