Compare commits

..

13 Commits

Author SHA1 Message Date
6346aae533 Decrese log level padding 2025-05-24 13:21:15 +02:00
d6c646e45c improve log level formtting 2025-05-23 08:20:23 +02:00
d7030d3dd4 allow strings for user ids 2025-05-22 04:57:51 +02:00
2ab290c712 allow username in getUser 2025-05-20 20:31:04 +02:00
b18e2ceea2 remove commented out code 2025-05-20 19:46:39 +02:00
56078fabad fixed poll parsing 2025-05-20 19:46:05 +02:00
5d009163f0 add debug logging 2025-05-20 18:59:17 +02:00
4ceeba7f4b updated poll parsing 2025-05-20 18:57:01 +02:00
7f0f8ac95b Add User::getPresenceString 2025-05-20 17:51:25 +02:00
848c1ae91e update dependencies 2025-05-15 13:50:37 +02:00
3cc7c812e8 add more logging options 2025-05-13 07:13:04 +02:00
77ae2cc627 set app name before creating logger 2025-05-06 21:44:57 +02:00
62820fd272 fix missing / 2025-05-06 20:34:40 +02:00
7 changed files with 415 additions and 334 deletions

View File

@ -3,7 +3,7 @@
"ignoredFilePatterns" : [
"logs"
],
"remotePath" : "\/var\/www\/html\/Dragonpolls\/vendor\/hutattedonmyarm\/apnuti",
"server" : "Phlaym",
"remotePath" : "\/var\/www\/dragonpolls\/vendor\/hutattedonmyarm\/apnuti",
"server" : "Rabenberger Photos",
"usesPublishing" : true
}

View File

@ -6,9 +6,9 @@
"keywords": ["pnut", "api"],
"license": "MIT",
"require": {
"monolog/monolog": "^2.0",
"psr/log": "^1.1",
"php": "^7.4|^8.0",
"monolog/monolog": "^3.0",
"psr/log": "^2.0 || ^3.0",
"php": "^8.3",
"ext-pdo": "*",
"ext-curl": "*",
"ext-json": "*",

83
composer.lock generated
View File

@ -4,63 +4,70 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "3c041ce633113232f43e991a77d802f9",
"content-hash": "fd959c4dda47577b06ccb0f69ab89de8",
"packages": [
{
"name": "monolog/monolog",
"version": "2.2.0",
"version": "3.9.0",
"source": {
"type": "git",
"url": "https://github.com/Seldaek/monolog.git",
"reference": "1cb1cde8e8dd0f70cc0fe51354a59acad9302084"
"reference": "10d85740180ecba7896c87e06a166e0c95a0e3b6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Seldaek/monolog/zipball/1cb1cde8e8dd0f70cc0fe51354a59acad9302084",
"reference": "1cb1cde8e8dd0f70cc0fe51354a59acad9302084",
"url": "https://api.github.com/repos/Seldaek/monolog/zipball/10d85740180ecba7896c87e06a166e0c95a0e3b6",
"reference": "10d85740180ecba7896c87e06a166e0c95a0e3b6",
"shasum": ""
},
"require": {
"php": ">=7.2",
"psr/log": "^1.0.1"
"php": ">=8.1",
"psr/log": "^2.0 || ^3.0"
},
"provide": {
"psr/log-implementation": "1.0.0"
"psr/log-implementation": "3.0.0"
},
"require-dev": {
"aws/aws-sdk-php": "^2.4.9 || ^3.0",
"aws/aws-sdk-php": "^3.0",
"doctrine/couchdb": "~1.0@dev",
"elasticsearch/elasticsearch": "^7",
"graylog2/gelf-php": "^1.4.2",
"elasticsearch/elasticsearch": "^7 || ^8",
"ext-json": "*",
"graylog2/gelf-php": "^1.4.2 || ^2.0",
"guzzlehttp/guzzle": "^7.4.5",
"guzzlehttp/psr7": "^2.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"
"php-amqplib/php-amqplib": "~2.4 || ^3",
"php-console/php-console": "^3.1.8",
"phpstan/phpstan": "^2",
"phpstan/phpstan-deprecation-rules": "^2",
"phpstan/phpstan-strict-rules": "^2",
"phpunit/phpunit": "^10.5.17 || ^11.0.7",
"predis/predis": "^1.1 || ^2",
"rollbar/rollbar": "^4.0",
"ruflin/elastica": "^7 || ^8",
"symfony/mailer": "^5.4 || ^6",
"symfony/mime": "^5.4 || ^6"
},
"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-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler",
"ext-mbstring": "Allow to work properly with unicode symbols",
"ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)",
"ext-openssl": "Required to send log messages using SSL",
"ext-sockets": "Allow sending log messages to a Syslog server (via UDP 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"
"dev-main": "3.x-dev"
}
},
"autoload": {
@ -88,7 +95,7 @@
],
"support": {
"issues": "https://github.com/Seldaek/monolog/issues",
"source": "https://github.com/Seldaek/monolog/tree/2.2.0"
"source": "https://github.com/Seldaek/monolog/tree/3.9.0"
},
"funding": [
{
@ -100,34 +107,34 @@
"type": "tidelift"
}
],
"time": "2020-12-14T13:15:25+00:00"
"time": "2025-03-24T10:02:05+00:00"
},
{
"name": "psr/log",
"version": "1.1.3",
"version": "3.0.2",
"source": {
"type": "git",
"url": "https://github.com/php-fig/log.git",
"reference": "0f73288fd15629204f9d42b7055f72dacbe811fc"
"reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/log/zipball/0f73288fd15629204f9d42b7055f72dacbe811fc",
"reference": "0f73288fd15629204f9d42b7055f72dacbe811fc",
"url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3",
"reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
"php": ">=8.0.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.1.x-dev"
"dev-master": "3.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Log\\": "Psr/Log/"
"Psr\\Log\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
@ -137,7 +144,7 @@
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
"homepage": "https://www.php-fig.org/"
}
],
"description": "Common interface for logging libraries",
@ -148,24 +155,24 @@
"psr-3"
],
"support": {
"source": "https://github.com/php-fig/log/tree/1.1.3"
"source": "https://github.com/php-fig/log/tree/3.0.2"
},
"time": "2020-03-23T09:12:05+00:00"
"time": "2024-09-11T13:17:53+00:00"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"stability-flags": {},
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
"php": "^7.4|^8.0",
"php": "^8.3",
"ext-pdo": "*",
"ext-curl": "*",
"ext-json": "*",
"ext-mbstring": "*"
},
"platform-dev": [],
"plugin-api-version": "2.0.0"
"platform-dev": {},
"plugin-api-version": "2.6.0"
}

View File

@ -14,8 +14,12 @@ use APnutI\Exceptions\NotSupportedPollException;
use APnutI\Exceptions\HttpPnutForbiddenException;
use APnutI\Exceptions\PollAccessRestrictedException;
use APnutI\Meta;
use APnutI\Logger\LevelNamePaddingProcessor;
use Monolog\Logger;
use Monolog\Handler\RotatingFileHandler;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\SyslogHandler;
use Monolog\Formatter\LineFormatter;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
@ -78,8 +82,15 @@ class APnutI
?string $app_name = null,
?string $redirect_uri = null,
?string $log_path = null,
$log_level = null
string|int|null $log_level = null,
bool $use_rotating_file_handler = true,
bool $use_stream_handler = false,
bool $use_syslog_handler = false,
) {
if (!empty($app_name)) {
$this->app_name = $app_name;
}
if (empty($log_level)) {
$log_level = Logger::INFO;
} elseif (is_string($log_level)) {
@ -90,8 +101,26 @@ class APnutI
: new Logger($this->app_name);
$this->token_session_key = $this->app_name . 'access_token';
$this->token_redirect_after_auth = $this->app_name . 'redirect_after_auth';
$this->logger->pushProcessor(new LevelNamePaddingProcessor());
$formatter = new LineFormatter("[%datetime%] %channel%.%extra.level_padded%: %message% %context% %extra%\n");
if ($use_rotating_file_handler) {
$handler = new RotatingFileHandler($log_path, 5, $log_level, true);
$handler->setFormatter($formatter);
$this->logger->pushHandler($handler);
}
if ($use_stream_handler) {
$handler = new StreamHandler($log_path, $log_level, true);
$handler->setFormatter($formatter);
$this->logger->pushHandler($handler);
}
if ($use_syslog_handler) {
$handler = new SyslogHandler($this->app_name, \LOG_USER, $log_level, true);
$handler->setFormatter($formatter);
$this->logger->pushHandler($handler);
}
$this->server_token = null;
$this->logger->debug('__construct API');
if (isset($_SESSION[$this->token_session_key])) {
@ -112,9 +141,6 @@ class APnutI
if (!empty($redirect_uri)) {
$this->redirect_uri = $redirect_uri;
}
if (!empty($app_name)) {
$this->app_name = $app_name;
}
}
/**
@ -485,7 +511,7 @@ class APnutI
}
// TODO Maybe support additional polls?
public function getPollsFromUser(int $user_id, array $params = []): array
public function getPollsFromUser(int|string $user_id, array $params = []): array
{
$parameters = [
'raw_types' => 'io.pnut.core.poll-notice',
@ -501,16 +527,23 @@ class APnutI
foreach ($params as $param => $value) {
$parameters[$param] = $value;
}
$response = $this->get('posts/search', $parameters);
$response = $this->get('/posts/search', $parameters);
if (count($response) === 0) {
return [];
}
$polls = [];
$this->logger->debug('Parsing ' . count($response) . ' results');
foreach ($response as $post) {
if (!empty($post['raw'])) {
foreach ($post['raw'] as $raw) {
if (Poll::$notice_type === $raw['type']) {
$polls[] = $this->getPoll($raw['value']['poll_id']);
foreach ($post['raw'] as $raw_type => $raw) {
if ($raw_type === Poll::$notice_type) {
try {
$this->logger->debug('Parsing poll from raw', $raw);
$polls[] = new Poll($post, $this);
} catch (NotSupportedPollException) {
$this->logger->warning('Parsing poll from raw failed. Loading from poll id');
$polls[] = $this->getPoll($raw[Poll::$notice_type]['poll_id']);
}
}
}
}
@ -527,10 +560,10 @@ class APnutI
$this->logger->error('Poll not supported: ' . json_encode($res));
throw $e;
} catch (HttpPnutForbiddenException $fe) {
$this->logger->error('Poll token required and not provided!');
$this->logger->error('Poll token required and not provided!', ['ex' => $fe]);
throw new PollAccessRestrictedException();
} catch (NotAuthorizedException $nauth) {
$this->logger->error('Not authorized when fetching poll');
$this->logger->error('Not authorized when fetching poll', ['ex' => $nauth]);
throw new PollAccessRestrictedException();
}
}
@ -640,7 +673,8 @@ class APnutI
return $this->current_user;
}
public function getUser(int $user_id, array $args = [])
// User ID or user name
public function getUser(int|string $user_id, array $args = [])
{
return new User($this->get('/users/' . $user_id, $args), $this);
}
@ -674,7 +708,7 @@ class APnutI
}
}
public function getAvatar(int $user_id, array $args = []): string
public function getAvatar(int|string $user_id, array $args = []): string
{
//get returns an array with the url at idx 0
$r = null;
@ -695,7 +729,7 @@ class APnutI
}
public function getAvatarUrl(
int $user_id,
int|string $user_id,
?int $width = null,
?int $height = null
): string {

View File

@ -1,4 +1,5 @@
<?php
namespace APnutI\Entities;
use APnutI\Entities\PollOption;
@ -17,7 +18,7 @@ class Poll
public bool $is_public = false;
public array $options = [];
public ?string $token = null;
public string $prompt = "";
public string $prompt = '';
public ?User $user = null;
public ?Source $source = null;
public string $type;
@ -30,7 +31,7 @@ class Poll
'net.unsweets.beta',
'io.pnut.core.poll',
'io.broadsword.poll',
'nl.chimpnut.quizbot.attachment.poll'
'nl.chimpnut.quizbot.attachment.poll',
];
public function __construct(array $data, APnutI $api)
@ -38,18 +39,24 @@ class Poll
$this->api = $api;
$this->options = [];
$type = '';
if (array_key_exists('type', $data) && $data['type'] === Poll::$notice_type) {
if (array_key_exists('type', $data) &&
$data['type'] === Poll::$notice_type
) {
$val = $data['value'];
$this->closed_at = new \DateTime($val['closed_at']);
foreach ($val['options'] as $option) {
$this->options[] = new PollOption($option);
}
$this->id = (int)$val['poll_id'];
$this->id = (int) $val['poll_id'];
$this->token = $val['poll_token'];
$this->prompt = $val['prompt'];
} elseif (array_key_exists('type', $data) &&in_array($data['type'], Poll::$poll_types)) {
} elseif (array_key_exists('type', $data) &&
in_array($data['type'], Poll::$poll_types)
) {
$this->parsePoll($data);
} elseif (array_key_exists('type', $data) &&strpos($data['type'], '.poll') !== false) {
} elseif (array_key_exists('type', $data) &&
strpos($data['type'], '.poll') !== false
) {
// Try parsing unknown types if they *might* be a poll
try {
$this->parsePoll($data);
@ -61,10 +68,12 @@ class Poll
count($data['raw'][Poll::$notice_type]) > 0
) {
$poll_data = $data['raw'][Poll::$notice_type][0];
if (!empty($data['source'])) { #Source is attached to post, not to poll raw
if (!empty($data['source'])) {
#Source is attached to post, not to poll raw
$poll_data['source'] = $data['source'];
}
if (!empty($data['user'])) { #User is attached to post, not to poll raw
if (!empty($data['user'])) {
#User is attached to post, not to poll raw
$poll_data['user'] = $data['user'];
}
$type = Poll::$notice_type;
@ -79,10 +88,16 @@ class Poll
{
$this->created_at = new \DateTime($data['created_at']);
$this->closed_at = new \DateTime($data['closed_at']);
$this->id = (int)$data['id'];
$this->is_anonymous = array_key_exists('is_anonymous', $data) ? (bool)$data['is_anonymous'] : false;
$this->max_options = array_key_exists('max_options', $data) ? (int)$data['max_options'] : 1;
$this->is_public = array_key_exists('is_public', $data) ? (bool)$data['is_public'] : false;
$this->id = (int) $data['id'];
$this->is_anonymous = array_key_exists('is_anonymous', $data)
? (bool) $data['is_anonymous']
: false;
$this->max_options = array_key_exists('max_options', $data)
? (int) $data['max_options']
: 1;
$this->is_public = array_key_exists('is_public', $data)
? (bool) $data['is_public']
: false;
foreach ($data['options'] as $option) {
$this->options[] = new PollOption($option);
}
@ -171,12 +186,9 @@ class Poll
bool $is_public
): Poll {
$options = array_filter($options);
$options = array_map(
function ($v) {
$options = array_map(function ($v) {
return ['text' => $v];
},
$options
);
}, $options);
$params = [
'duration' => $duration_minutes,
'options' => array_filter($options), #filters empty options
@ -184,7 +196,7 @@ class Poll
'type' => 'io.pnut.core.poll',
'is_anonymous' => $is_anonymous,
'is_public' => $is_public,
'max_options' => $max_options
'max_options' => $max_options,
];
$api->logger->debug('Creating poll');
$api->logger->debug(json_encode($params));
@ -200,10 +212,10 @@ class Poll
[
'+io.pnut.core.poll' => [
'poll_id' => $poll_id,
'poll_token' => $poll_token
]
]
]
'poll_token' => $poll_token,
],
],
],
];
}
@ -215,10 +227,10 @@ class Poll
} else {
$str = 'Unknown user';
}
return $str
. " asked: '"
. $this->prompt
. "', closed at "
. $this->closed_at->format('Y-m-d H:i:s T');
return $str .
" asked: '" .
$this->prompt .
"', closed at " .
$this->closed_at->format('Y-m-d H:i:s T');
}
}

View File

@ -52,6 +52,17 @@ class User
}
}
public function getPresenceString()
{
if ($this->presence === true) {
return "online";
} elseif ($this->presence === false) {
return "offline";
} else {
return "presence unknown";
}
}
public function getAvatarUrl(
?int $width = null,
?int $height = null

View File

@ -0,0 +1,17 @@
<?php
namespace APnutI\Logger;
use Monolog\Processor\ProcessorInterface;
use Monolog\LogRecord;
// See: https://stackoverflow.com/a/78901964
class LevelNamePaddingProcessor implements ProcessorInterface
{
public function __invoke(LogRecord $record): LogRecord
{
$record->extra['level_padded'] = str_pad($record->level->getName(), 5, ' ', STR_PAD_RIGHT);
return $record;
}
}