Initial commit

main
aymm 2021-03-08 20:44:02 +01:00
commit f5a781d416
Signed by: phlaym
GPG Key ID: A06651BAB6777237
19 changed files with 646 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
index.php
vendor/
log-*

8
.nova/Configuration.json Normal file
View File

@ -0,0 +1,8 @@
{
"com.thorlaksson.phpcs.runOnChange" : "onSave",
"com.thorlaksson.phpcs.standard" : "\/Volumes\/Alyx\/Users\/max\/Dev\/Pnut\/Untitled\/phpcs.xml",
"editor.default_syntax" : "php",
"php.validate" : "onSave",
"workspace.color" : 1,
"workspace.name" : "APnutI"
}

21
composer.json Normal file
View File

@ -0,0 +1,21 @@
{
"name": "hutattedonmyarm/apnuti",
"type": "library",
"description": "PHP Pnut library",
"keywords": ["pnut", "api"],
"license": "MIT",
"require": {
"monolog/monolog": "^2.0",
"psr/log": "^1.1",
"php": "^7.4|^8.0",
"ext-pdo": "*",
"ext-curl": "*",
"ext-json": "*",
"ext-mbstring": "*"
},
"autoload": {
"psr-4": {
"APnutI\\": "src"
}
}
}

171
composer.lock generated Normal file
View File

@ -0,0 +1,171 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "3c041ce633113232f43e991a77d802f9",
"packages": [
{
"name": "monolog/monolog",
"version": "2.2.0",
"source": {
"type": "git",
"url": "https://github.com/Seldaek/monolog.git",
"reference": "1cb1cde8e8dd0f70cc0fe51354a59acad9302084"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Seldaek/monolog/zipball/1cb1cde8e8dd0f70cc0fe51354a59acad9302084",
"reference": "1cb1cde8e8dd0f70cc0fe51354a59acad9302084",
"shasum": ""
},
"require": {
"php": ">=7.2",
"psr/log": "^1.0.1"
},
"provide": {
"psr/log-implementation": "1.0.0"
},
"require-dev": {
"aws/aws-sdk-php": "^2.4.9 || ^3.0",
"doctrine/couchdb": "~1.0@dev",
"elasticsearch/elasticsearch": "^7",
"graylog2/gelf-php": "^1.4.2",
"mongodb/mongodb": "^1.8",
"php-amqplib/php-amqplib": "~2.4",
"php-console/php-console": "^3.1.3",
"phpspec/prophecy": "^1.6.1",
"phpstan/phpstan": "^0.12.59",
"phpunit/phpunit": "^8.5",
"predis/predis": "^1.1",
"rollbar/rollbar": "^1.3",
"ruflin/elastica": ">=0.90 <7.0.1",
"swiftmailer/swiftmailer": "^5.3|^6.0"
},
"suggest": {
"aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
"doctrine/couchdb": "Allow sending log messages to a CouchDB server",
"elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client",
"ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
"ext-mbstring": "Allow to work properly with unicode symbols",
"ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)",
"graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
"mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)",
"php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
"php-console/php-console": "Allow sending log messages to Google Chrome",
"rollbar/rollbar": "Allow sending log messages to Rollbar",
"ruflin/elastica": "Allow sending log messages to an Elastic Search server"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "2.x-dev"
}
},
"autoload": {
"psr-4": {
"Monolog\\": "src/Monolog"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jordi Boggiano",
"email": "j.boggiano@seld.be",
"homepage": "https://seld.be"
}
],
"description": "Sends your logs to files, sockets, inboxes, databases and various web services",
"homepage": "https://github.com/Seldaek/monolog",
"keywords": [
"log",
"logging",
"psr-3"
],
"support": {
"issues": "https://github.com/Seldaek/monolog/issues",
"source": "https://github.com/Seldaek/monolog/tree/2.2.0"
},
"funding": [
{
"url": "https://github.com/Seldaek",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/monolog/monolog",
"type": "tidelift"
}
],
"time": "2020-12-14T13:15:25+00:00"
},
{
"name": "psr/log",
"version": "1.1.3",
"source": {
"type": "git",
"url": "https://github.com/php-fig/log.git",
"reference": "0f73288fd15629204f9d42b7055f72dacbe811fc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/log/zipball/0f73288fd15629204f9d42b7055f72dacbe811fc",
"reference": "0f73288fd15629204f9d42b7055f72dacbe811fc",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.1.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Log\\": "Psr/Log/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"description": "Common interface for logging libraries",
"homepage": "https://github.com/php-fig/log",
"keywords": [
"log",
"psr",
"psr-3"
],
"support": {
"source": "https://github.com/php-fig/log/tree/1.1.3"
},
"time": "2020-03-23T09:12:05+00:00"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
"php": "^7.4|^8.0",
"ext-pdo": "*",
"ext-curl": "*",
"ext-json": "*",
"ext-mbstring": "*"
},
"platform-dev": [],
"plugin-api-version": "2.0.0"
}

28
phpcs.xml Normal file
View File

@ -0,0 +1,28 @@
<?xml version="1.0"?>
<ruleset name="Slim PSR2">
<!--
Include all sniffs in the PEAR standard. Note that the
path to the standard does not have to be specified as the
PEAR standard exists inside the PHP_CodeSniffer install
directory.
-->
<rule ref="PSR2"/>
<!--
Another useful example of changing sniff settings is
to specify the end of line character that your standard
should check for.
-->
<rule ref="Generic.Files.LineEndings">
<properties>
<property name="eolChar" value="\n"/>
</properties>
</rule>
<arg name="tab-width" value="2"/>
<rule ref="Generic.WhiteSpace.ScopeIndent">
<properties>
<property name="indent" value="2"/>
</properties>
</rule>
</ruleset>

77
src/APnutI.php Normal file
View File

@ -0,0 +1,77 @@
<?php
namespace APnutI;
use APnutI\Entities\Post;
use APnutI\Entities\User;
use APnutI\Meta;
use Monolog\Logger;
use Monolog\Handler\RotatingFileHandler;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
class APnutI
{
protected string $api_url = 'https://api.pnut.io/v1/';
protected string $auth_url = 'https://pnut.io/oauth/authenticate';
protected string $client_secret;
protected string $client_id;
protected string $scope;
protected string $redirect_uri;
protected $rate_limit = null;
protected $rate_limit_remaining = null;
protected $rate_limit_rset = null;
protected $scopes;
protected ?string $needed_scope;
protected ?string $redirect_target = null;
protected array $headers = [];
protected string $app_name = 'Abstract API';
protected ?string $server_token;
protected ?string $access_token;
protected LoggerInterface $logger;
protected string $token_session_key;
protected ?string $server_token_file_path = null;
public ?Meta $meta = null;
/*
* Error codes:
* 3XX: Pnut error
* - 300: Cannot fetch post creator
* - 301: Missing field of post creator (e.g. has no avatar)
* 4XX: User error (post not found, yada yada)
* - 400: Missing parameter
* - 404: Not found
* 5XX: Generic server error
*/
public static $ERROR_FETCHING_CREATOR = 300;
public static $ERROR_FETCHING_CREATOR_FIELD = 301;
public static $ERROR_MISSING_PARAMETER = 400;
public static $ERROR_NOT_FOUND = 404;
public static $ERROR_UNKNOWN_SERVER_ERROR = 500;
public static $POST_MAX_LENGTH;
public static $POST_MAX_LENGTH_REPOST;
public static $POST_SECONDS_BETWEEN_DUPLICATES;
public static $MESSAGE_MAX_LENGTH;
public static $RAW_MAX_LENGTH;
public static $USER_DESCRIPTION_MAX_LENGTH;
public static $USER_USERNAME_MAX_LENGTH;
public function __construct(?string $log_path = null)
{
$this->logger = empty($log_path) ? new NullLogger() : new Logger($this->app_name);
$this->token_session_key = $this->app_name.'access_token';
$handler = new RotatingFileHandler($log_path, 5, Logger::DEBUG, true);
$this->logger->pushHandler($handler);
$this->server_token = null;
$this->logger->debug('__construct API');
if (isset($_SESSION[$this->token_session_key])) {
$this->access_token = $_SESSION[$this->token_session_key];
$this->logger->debug('Access token in session');
} else {
$this->logger->debug('No access token in session');
}
}
}

14
src/Entities/Badge.php Normal file
View File

@ -0,0 +1,14 @@
<?php
namespace APnutI\Entities;
class Badge
{
public int $id;
public string $name;
public function __construct(array $data)
{
$this->id = (int)$data['id'];
$this->name = $data['name'];
}
}

22
src/Entities/Image.php Normal file
View File

@ -0,0 +1,22 @@
<?php
namespace APnutI\Entities;
class Image
{
public bool $is_default;
public int $height;
public int $width;
public string $link;
public function __construct(array $data)
{
$this->is_default = $data['is_default'];
$this->height = $data['height'];
$this->width = $data['width'];
if (isset($data['link'])) {
$this->link = $data['link']; //API v0
} else {
$this->link = $data['url']; //API v1
}
}
}

85
src/Entities/Post.php Normal file
View File

@ -0,0 +1,85 @@
<?php
namespace APnutI\Entities;
use APnutI\Entities\User;
use APnutI\Entities\Source;
use APnutI\Entities\PostCounts;
use APnutI\Entities\PostContent;
class Post
{
public DateTime $created_at;
public int $id;
public bool $is_deleted = false;
public bool $is_nsfw = false;
public bool $is_revised = false;
public ?int $revision = null;
public ?User $user = null;
public int $thread_id;
public ?int $reply_to = null;
public ?int $repost_of = null;
public ?PostCounts $counts = null;
public ?Source $source = null;
public ?PostContent $content = null;
public bool $you_bookmarked = false;
public bool $you_reposted = false;
public function __construct(array $data)
{
$this->created_at = new \DateTime($data['created_at']);
$this->id = (int)$data['id'];
if (!empty($data['is_deleted'])) {
$this->is_deleted = (bool)$data['is_deleted'];
}
if (!empty($data['is_nsfw'])) {
$this->is_nsfw = (bool)$data['is_nsfw'];
}
if (!empty($data['is_revised'])) {
$this->is_revised = (bool)$data['is_revised'];
}
if (!empty($data['revision'])) {
$this->revision = (bool)$data['revision'];
}
#file_put_contents(__DIR__."/post_log.log", json_encode($data['user']), FILE_APPEND | LOCK_EX);
if (!empty($data['user'])) {
$this->user = new User($data['user']);
}
#file_put_contents(__DIR__."/post_log.log", json_encode($this->user), FILE_APPEND | LOCK_EX);
$this->thread_id = (int)$data['thread_id'];
if (!empty($data['reply_to'])) {
$this->reply_to = (int)$data['reply_to'];
}
if (!empty($data['repost_of'])) {
$this->repost_of = (int)$data['repost_of'];
}
if (!empty($data['counts'])) {
$this->counts = new PostCounts($data['counts']);
}
if (!empty($data['source']) && is_array($data['source'])) {
$this->source = new Source($data['source']);
}
if (!empty($data['content'])) {
$this->content = new PostContent($data['content']);
}
if (!empty($data['you_bookmarked'])) {
$this->you_bookmarked = (bool)$data['you_bookmarked'];
}
if (!empty($data['you_reposted'])) {
$this->you_reposted = (bool)$data['you_reposted'];
}
}
public function getText(bool $html_if_present = true): string
{
$txt = '';
if (!empty($this->content)) {
if ($html_if_present && !empty($this->content->html)) {
$txt = $this->content->html;
} elseif (!empty($this->content->text)) {
$txt = $this->content->text;
}
}
return $txt;
}
}

View File

@ -0,0 +1,18 @@
<?php
namespace APnutI\Entities;
class PostCounts
{
public int $bookmarks;
public int $replies;
public int $reposts;
public int $threads;
public function __construct(array $data)
{
$this->bookmarks = (int)$data['bookmarks'];
$this->replies = (int)$data['replies'];
$this->reposts = (int)$data['reposts'];
$this->threads = (int)$data['threads'];
}
}

View File

@ -0,0 +1,22 @@
<?php
namespace APnutI\Entities;
class PostContent
{
public string $text;
public ?string $html = null;
public array $entities;
public bool $links_not_parsed = false;
public function __construct(array $data)
{
$this->text = $data['text'];
if (!empty($data['html'])) {
$this->html = $data['html'];
}
$this->entities = $data['entities'];
if (!empty($data['links_not_parsed'])) {
$this->links_not_parsed = (bool)$data['links_not_parsed'];
}
}
}

20
src/Entities/Source.php Normal file
View File

@ -0,0 +1,20 @@
<?php
namespace APnutI\Entities;
class Source
{
public string $name;
public string $link;
public int $id;
public function __construct($data)
{
$this->name = $data['name'];
if (isset($data['link'])) {
$this->link = $data['link']; //v0
} else {
$this->link = $data['url']; //v1
}
$this->id = (int)$data['id'];
}
}

62
src/Entities/User.php Normal file
View File

@ -0,0 +1,62 @@
<?php
namespace APnutI\Entities;
use APnutI\Entities\Image;
use APnutI\Entities\Badge;
class User
{
public ?Badge $badge = null;
public ?Image $avatar_image = null;
public ?Image $cover_image = null;
public int $id;
public string $username;
public ?string $name = null;
public ?bool $presence = null;
#private LoggerInterface $log;
public function __construct(array $data)
{
#$this->log = new Logger('User');
$this->id = (int)$data['id'];
$this->username = $data['username'];
if (!empty($data['badge'])) {
$this->badge = new Badge($data['badge']);
}
if (!empty($data['content'])) {
$this->avatar_image = new Image($data['content']['avatar_image']);
$this->cover_image = new Image($data['content']['cover_image']);
}
if (!empty($data['name'])) {
$this->name = $data['name'];
}
if (!empty($data['presence'])) {
if (is_string($data['presence']) || is_int($data['presence'])) {
$this->presence = !($data['presence'] == "offline" || $data['presence'] === 0);
}
}
}
public function getPresenceInt(): int
{
if ($this->presence === true) {
return 1;
} elseif ($this->presence === false) {
return 0;
} else {
return -1;
}
}
public function getPresenceString(): string
{
if ($this->presence === true) {
return "online";
} elseif ($this->presence === false) {
return "offline";
} else {
return "presence unknown";
}
}
}

View File

@ -0,0 +1,9 @@
<?php
namespace APnutI\Exceptions;
use APnutI\Exceptions\PnutException;
class HttpPnutException extends PnutException
{
}

View File

@ -0,0 +1,15 @@
<?php
namespace APnutI\Exceptions;
use APnutI\Exceptions\HttpPnutException;
class HttpPnutRedirectException extends HttpPnutException
{
public $response;
public function __construct($response)
{
parent::__construct("Redirect");
$this->response = $response;
}
}

View File

@ -0,0 +1,9 @@
<?php
namespace APnutI\Exceptions;
use APnutI\Exceptions\PnutException;
class NotAuthorizedException extends Exception
{
}

View File

@ -0,0 +1,9 @@
<?php
namespace APnutI\Exceptions;
use APnutI\Exceptions\PnutException;
class NotFoundException extends HttpPnutException
{
}

View File

@ -0,0 +1,7 @@
<?php
namespace APnutI\Exceptions;
class PnutException extends Exception
{
}

46
src/Meta.php Normal file
View File

@ -0,0 +1,46 @@
<?php
namespace APnutI;
use APnutI\Exceptions\PnutException;
use APnutI\Exceptions\NotAuthorizedException;
use APnutI\Exceptions\NotFoundException;
class Meta
{
public bool $more = false;
public ?int $max_id = null;
public ?int $min_id = null;
public ?int $code = -1;
public function __construct(array $json)
{
if (empty($json['meta'])) {
return;
}
$meta = (array)$json['meta'];
if (!empty($meta['more'])) {
$this->more = (bool)$meta['more'];
}
if (!empty($meta['max_id'])) {
$this->max_id = (int)$meta['max_id'];
}
if (!empty($meta['min_id'])) {
$this->min_id = (int)$meta['min_id'];
}
if (!empty($meta['code'])) {
$this->code = $meta['code'];
if ($this->code === 400) {
throw new PnutException($meta['error_message']);
}if ($this->code === 401) {
throw new NotAuthorizedException($meta['error_message']);
}
if ($this->code === 404) {
throw new NotFoundException();
}
if ($meta['code'] < 200 || $meta['code'] >= 300) {
throw new PnutException($meta['error_message']);
}
}
}
}