commit e57af3ad361c1c63b8c979739493aa03eca9b58c Author: Max Nuding Date: Thu May 15 14:25:39 2025 +0200 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..02b773c --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +history.json \ No newline at end of file diff --git a/Check_PCA.php b/Check_PCA.php new file mode 100644 index 0000000..c57a637 --- /dev/null +++ b/Check_PCA.php @@ -0,0 +1,735 @@ +'; + file_put_contents( + get_log_file(), + '[' . $level . '] ' . date('H:i:s') . ' ' . $string . PHP_EOL, + FILE_APPEND | LOCK_EX + ); +} + +function get_log_file() +{ + return realpath(__DIR__ . '/../pca_logs') . '/pca.log'; + //return realpath(__DIR__ . '/../pca_logs') . '/' . date('Y-m-d') . '.log'; +} + +class User +{ + public $user_name; + public $name; + public $number_posts; + public $following; + public $follows_bot; + public $last_club_notification = ''; + + private static function clean_api_response($api_reponse) + { + $data = []; + if (array_key_exists('data', $api_reponse)) { + $data = $api_reponse['data']; + } else { + $data = $api_reponse; + } + return $data; + } + + public function __construct($api_reponse) + { + $data = self::clean_api_response($api_reponse); + $this->user_name = '@' . $data['username']; + $this->number_posts = preg_replace('/\s+/', '', $data['counts']['posts']); + $this->follows_bot = $data['follows_you']; + $this->name = array_key_exists('name', $data) ? $data['name'] : ''; + } + + public static function get_users_from_api_reponse($api_reponse) + { + $data = self::clean_api_response($api_reponse); + $users = []; + // Single user + if (count(array_filter(array_keys($data), 'is_string')) > 0) { + $users[] = new User($data); + } else { + // Multiple users + foreach ($data as $key => $value) { + $users[] = new User($value); + } + } + return $users; + } + + public function get_highest_pca($clubs) + { + $key = $this->get_next_pca_key($clubs) - 1; + if ($key == -1) { + return []; + } + return $clubs[$key]; + } + + public function get_next_pca($clubs) + { + return $clubs[$this->get_next_pca_key($clubs)]; + } + + private function get_next_pca_key($clubs) + { + if (preg_replace('/\s+/', '', $clubs[0]['post_count']) > $this->number_posts + ) { + return 0; + } + foreach ($clubs as $key => $club) { + if (preg_replace('/\s+/', '', $club['post_count']) > $this->number_posts + ) { + return $key; + } + } + } +} + +class API +{ + public $access_token = ''; + private static $api_endpoint = 'https://api.pnut.io/v1'; + public $max_posttext_length = 256; + private static $settings_file_location = __DIR__ + . '/..' + . '/pca_settings/user_settings.json'; + private static $settings = []; + private static $default_notification_text = 'Congratulations {user.username}, you are now a member of #{pca.name} {pca.emoji} ({pca.postcount} posts)! Next: {nextpca.emoji} at {nextpca.postcount} posts'; + private static $notification_tokens = [ + '{user.username}', + '{user.name}', + '{pca.name}', + '{pca.emoji}', + '{pca.postcount}', + '{nextpca.name}', + '{nextpca.emoji}', + '{nextpca.postcount}', + '{posts_to_pca}', + ]; + public $me; + + public function init() + { + write_pca_log(''); + write_pca_log('================================='); + write_pca_log(''); + $this->max_posttext_length = $this->get_data( + 'https://api.pnut.io/v1/sys/config' + )['data']['post']['max_length']; + $this->me = User::get_users_from_api_reponse( + $this->get_data('https://api.pnut.io/v1/users/me') + )[0]; + write_pca_log('Hi, my name is ' . $this->me->user_name, 'DEBUG'); + $sapi_type = php_sapi_name(); + $mode = !empty($_SERVER['DOCUMENT_ROOT']) && + (substr($sapi_type, 0, 3) == 'cli' || empty($_SERVER['REMOTE_ADDR'])) + ? 'Debug' + : 'Production'; + write_pca_log('I am currently in ' . $mode . ' mode'); + if (!file_exists(self::$settings_file_location)) { + file_put_contents(self::$settings_file_location, json_encode([])); + write_pca_log( + 'No user settings file found. Creating empty one at ' . + self::$settings_file_location + ); + } + self::$settings = json_decode( + file_get_contents(self::$settings_file_location), + true + ); + write_pca_log('User settings file loaded'); + } + + private function get_data( + $endpoint, + $parameters = [], + $method = 'GET', + $contenttype = 'application/x-www-form-urlencoded', + $force = false + ) { + write_pca_log('Making request to pnut API at ' . $endpoint); + $postdata = $this->build_query_string($parameters); + if ($contenttype == 'application/json') { + $postdata = json_encode($parameters); + } + $context_options = [ + 'http' => [ + 'method' => $method, + 'header' => + 'Content-type: ' . + $contenttype . + "\r\n" . + 'Authorization: Bearer ' . + $this->access_token, + 'content' => $postdata, + ], + ]; + $sapi_type = php_sapi_name(); + if (!$force + && !empty($_SERVER['DOCUMENT_ROOT']) + && (substr($sapi_type, 0, 3) == 'cli' || empty($_SERVER['REMOTE_ADDR'])) + && $method == 'POST' + ) { + write_pca_log( + 'Running from shell instead of server. Debug mode assumed. Not submitting POST requests to server!', + 'DEBUG' + ); + write_pca_log( + 'Would have posted if run on a server: ' . $postdata, + 'DEBUG' + ); + return; + } + $context = stream_context_create($context_options); + $response = @file_get_contents($endpoint, false, $context); + if ($response === false) { + write_pca_log('Received no or invalid response from server', 'ERROR'); + return [ + 'meta' => ['code' => '401', 'error_message' => 'Invalid request'], + ]; + } + $resp_dict = json_decode($response, true); + write_pca_log( + 'Got server response. Meta: ' . json_encode($resp_dict['meta']) + ); + $response_code = $resp_dict['meta']['code']; + // Success + if ($response_code >= 200 && $response_code <= 208) { + return $resp_dict; + } else { + write_pca_log( + 'Received error-response from server. ' . + json_encode($resp_dict['meta']), + 'ERROR' + ); + // die(); + } + } + + public function get_messages($clubs) + { + $num_unread_endpoint = + self::$api_endpoint . '/users/me/channels/num_unread/pm'; + $num_unread_pms = $this->get_data($num_unread_endpoint)['data']; + $messages = []; //Keys: username, values: message-array of messages by the user + write_pca_log($num_unread_pms . ' unread PMs'); + if ($num_unread_pms > 0) { + $channels_endpoint = self::$api_endpoint . + '/users/me/channels/subscribed?include_read=0&channel_types=io.pnut.core.pm'; + $channels = $this->get_data($channels_endpoint); + foreach ($channels['data'] as $channel) { + write_pca_log('Channel: ' . $channel['id'], 'DEBUG'); + $messages_endpoint = + self::$api_endpoint . + '/channels/' . + $channel['id'] . + '/messages?include_deleted=0&include_html=0&include_client=0&include_marker=1'; + $unread_messages = $this->get_data($messages_endpoint); + $msg = []; + $sender = ''; + $last_read = $unread_messages['meta']['marker']['last_read_id']; + foreach ($unread_messages['data'] as $message) { + if ($message['id'] <= $last_read) { + //Stop when reaching already read messages + write_pca_log( + 'Message ' . + $message['id'] . + 'is <= last read message ' . + $last_read . + '. Skipping rest of the messages.', + 'DEBUG' + ); + break; + } + $sender_tmp = $message['user']['username']; + if (substr($this->me->user_name, 1) == $sender_tmp) { + //Ignore messages sent by the bot + write_pca_log( + 'Ignoring message ' . + $message['id'] . + ', because it was sent by myself', + 'DEBUG' + ); + continue; + } + $sender = $sender_tmp; + $message_text = $message['content']['text']; + $msg[] = $message['content']['text']; + write_pca_log( + 'Message from ' . $sender . ': ' . $message_text, + 'DEBUG' + ); + } + if ($sender != '') { + $messages[$sender] = $msg; + } + } + } + return $messages; + } + + public function send_message($user, $message) + { + $send_message_endpoint = + self::$api_endpoint . '/channels/pm/messages?update_marker=1'; + $parameters = ['text' => $message, 'destinations' => ['@' . $user]]; + $this->get_data( + $send_message_endpoint, + $parameters, + 'POST', + 'application/json', + $user == 'hutattedonmyarm' + ); + } + + public function set_notification_text_template($user_id, $text) + { + // echo json_encode(self::$settings); + self::$settings[$user_id] = $text; + $this->save_settings(); + } + + public function get_notification_text_template($user_id) + { + $notification_text = API::$default_notification_text; + if (array_key_exists($user_id, self::$settings)) { + write_pca_log( + $user_id . + ' has custom settings: ' . + json_encode(self::$settings[$user_id]) + ); + $notification_text = self::$settings[$user_id]; + } else { + write_pca_log($user_id . ' does not have custom settings!'); + } + return $notification_text; + } + + private function build_notification_text( + $user, + $clubs, + $current_pca, + $next_pca + ) { + $notification_text = $this->get_notification_text_template( + $user->user_name + ); + + $token_values = [ + $user->user_name, + $user->name, + $current_pca['pca'], + $current_pca['emoji'], + $current_pca['post_count'], + $next_pca['pca'], + $next_pca['emoji'], + $next_pca['post_count'], + $next_pca['post_count'] - $user->number_posts, + ]; + if (array_key_exists($user->user_name, self::$settings)) { + write_pca_log( + $user->user_name . + ' has custom settings: ' . + json_encode(self::$settings[$user->username]) + ); + $notification_text = self::$settings[$user->username]; + } else { + write_pca_log($user->user_name . ' does not have custom settings!'); + } + foreach (self::$notification_tokens as $index => $token) { + $notification_text = str_replace( + $token, + $token_values[$index], + $notification_text + ); + } + return $notification_text; + } + + public function reset_notification_text_for_user($user_id) + { + if (array_key_exists($user_id, self::$settings)) { + unset(self::$settings[$user_id]); + $this->save_settings(); + } + } + + private function save_settings() + { + file_put_contents( + self::$settings_file_location, + json_encode(self::$settings) + ); + } + public function write_post($posttext, $reply_to = -1) + { + $post_endpoint = self::$api_endpoint . '/posts'; + $txt = mb_strimwidth($posttext, 0, $this->max_posttext_length, ''); + // $txt = $posttext; + $parameters = ['text' => $txt]; + if ($reply_to != -1) { + $parameters['reply_to'] = $reply_to; + } + write_pca_log( + "Writing post: '" . $txt . "' => " . json_encode($parameters) + ); + $this->get_data($post_endpoint, $parameters, 'POST'); + } + + public function get_user($user_id) + { + if ($user_id[0] != '@') { + $user_id = '@' . $user_id; + } + $user_endpoint = self::$api_endpoint . '/users/' . $user_id; + return new User($this->get_data($user_endpoint)); + } + + public function get_bot_followers() + { + $before_id = null; + $users = []; + do { + write_pca_log('Getting followers before id: ' . $before_id); + $followers_endpoint = self::$api_endpoint . '/users/me/followers'; + if ($before_id != null) { + $followers_endpoint .= '?before_id=' . $before_id; + } + $followers_data = $this->get_data( + $followers_endpoint, [ + 'before_id' => $before_id, + ] + ); + $before_id = $followers_data['meta']['min_id']; + $users = array_merge( + $users, + User::get_users_from_api_reponse($followers_data) + ); + } while ($followers_data['meta']['more'] == '1'); + return $users; + } + + function build_query_string($array) + { + foreach ($array as $k => &$v) { + if ($v === true) { + $v = '1'; + } elseif ($v === false) { + $v = '0'; + } + unset($v); + } + return http_build_query($array); + } + + public function notify_user($user, $clubs) + { + // Get current PCA + $current_pca_dict = $user->get_highest_pca($clubs); + $current_pca = ''; + if (array_key_exists('pca', $current_pca_dict)) { + $current_pca = $current_pca_dict['pca']; + } else { + // If key doesn't exist, user hasn't reached a club yet + return null; + } + $last_notification = $user->last_club_notification; + $next_pca = $user->get_next_pca($clubs); + $next = [ + 'club' => $next_pca, + 'posts_until' => $next_pca['post_count'] - $user->number_posts + ]; + if ($current_pca != $last_notification) { + // Haven't notified about the current club yet + // Stitch together the post itself + $this->build_notification_text( + $user, + $clubs, + $current_pca_dict, + $next_pca + ); + $text_components = []; + $posttext = 'Congratulations ' . + $user->user_name . + ', you are now a member of #' . + preg_replace('/\s+/', '', $current_pca) . + ' ' . + $current_pca_dict['emoji']; + $text_components[] = ' (' + . $current_pca_dict['post_count'] + . '+ posts)!'; + $text_components[] = ' Next: ' . + $next_pca['emoji'] . + ' at ' . + $next_pca['post_count'] . + ' posts'; + foreach ($text_components as $component) { + $available_length = $this->max_posttext_length - strlen($component); + if (strlen($posttext) < $available_length) { + $posttext .= $component; + } + } + /* + * Steps to write post as a reply to the post which made a user enter a new club: + * 1) Get number of user posts (we already have that info) + * 2) Subtract pca post count value from number posts. This will give us an offset + * 3) Get their posts with ?count=OFFSET&include_html=0&include_counts=0&include_client=0 + * 4) Check if counts match. If not, get more of their posts with before_id set to the relevant ID + * 5) Repeat 4 until we have the correct post + * 6) Reply to it + */ + $pca_offset = $user->number_posts - $current_pca_dict['post_count'] + 1; + $before_id = -1; + $loop_counts = 10; + do { + $ep = self::$api_endpoint . + '/users/' . + $user->user_name . + '/posts?count=' . + $pca_offset . + '&include_html=0&include_counts=0&include_client=0&include_deleted=1'; + // write_pca_log($ep, "DEBUG"); + $response = $this->get_data($ep, []); + $pca_offset -= count($response['data']); + $before_id = $response['meta']['min_id']; + write_pca_log( + 'Received ' . count($response['data']) . ' posts', + 'DEBUG' + ); + $loop_counts--; + // write_pca_log("Remaining offset: ".$pca_offset); + } while ($pca_offset > 0 && $loop_counts > 0); + $reply_to = $response['data'][count($response['data']) - 1]['id']; + // reached mac loop iterations + if (!isset($reply_to) || $reply_to == 0 || $loop_counts <= 0) { + $reply_to = -1; + } + $del_ar = $response['data'][count($response['data']) - 1]; + $deleted = array_key_exists('is_deleted', $del_ar) && + $del_ar['is_deleted'] == 'true'; + $log_string = 'Post that made them reach ' . + preg_replace('/\s+/', '', $current_pca) . + ': ' . + $reply_to . + '. Deleted?: '; + $log_string .= $deleted ? 'yes' : 'no'; + write_pca_log($log_string); + $this->write_post($posttext, $reply_to); + $user->last_club_notification = $current_pca; + write_pca_log($posttext); + write_pca_log($log_string); + $now = \DateTime::createFromFormat('U.u', microtime(true)); + $recent_changes_dict = [ + 'date' => $response['data'][count($response['data']) - 1]['created_at'], + 'user' => $user->user_name, + 'pca' => $current_pca, + 'post_id' => $reply_to, + ]; + $history_file = 'history.json'; + $inp = file_get_contents($history_file); + $tempArray = json_decode($inp, true); + if ($tempArray == null) { + $tempArray = []; + } + array_push($tempArray, $recent_changes_dict); + file_put_contents($history_file, json_encode($tempArray)); + } else { + // Already notified, nothing to do + write_pca_log( + $user->user_name . + ' has already been notified for reaching ' . + $current_pca + ); + } + return $next; + } +} +// Get all clubs +write_pca_log(''); +write_pca_log(''); +write_pca_log(''); +write_pca_log(''); +write_pca_log('Log file: ' . get_log_file()); +$clubs = json_decode(file_get_contents('https://pca.phlaym.net/pca.php'), true); +write_pca_log('Found clubs: ' . implode(', ', array_column($clubs, 'pca'))); +// $last_notification_file = '../last_notification.json'; +$last_notification_file_dir = realpath(__DIR__ . '/../pca_settings'); +$last_notification_file_name = '/last_notification.json'; +$last_notification_file = $last_notification_file_dir +. $last_notification_file_name; +write_pca_log('Last notification file: ' . $last_notification_file); +$last_notification_dict = []; + +if (file_exists($last_notification_file)) { + $last_notification_dict = json_decode( + file_get_contents($last_notification_file), + true + ); + write_pca_log('Loaded last notification info from file'); +} else { + write_pca_log('Last notification info file could not be found'); +} + +// Get users +$access_token = '-1'; +$token_file = realpath(__DIR__ . '/../pca_settings/access_token'); +if ($token_file !== false) { + $access_token = file_get_contents($token_file); + write_pca_log('Using saved authentication'); + if ($access_token == false) { + write_pca_log('Re-authenticating'); + header('Location: http://pca.phlaym.net/pnutauth.php'); + } +} else { + write_pca_log('Re-authenticating'); + header('Location: http://pca.phlaym.net/pnutauth.php'); +} +$api = new API(); +$api->access_token = $access_token; +$api->init(); +$messages = $api->get_messages($clubs); +/* + * Message command syntax: + * "Help" or "?" => prints help + * "Get notification text" => replies with notification text + * "Reset notification text" => resets to default + * "Set notification text" followed by a space and then the text. Replies with sample text. Available tokens see class var + */ +foreach ($messages as $sender => $message) { + $sender_obj = $api->get_user($sender); + foreach ($message as $msg) { + if (mb_strtolower($msg) == '?' || mb_strtolower($msg) == 'help') { + $helptext = 'Welcome to ' . $api->me->user_name . "!\n"; + $helptext .= "The following commands are available: \n"; + $helptext .= + "- 'Get notification text', which prints the current notification text for you\n"; + $helptext .= + "- 'Set notification text', followed by a space and a custom notification text\n"; + $helptext .= + "- 'Reset notification text', resets the notification text to the default\n"; + $helptext .= "\n"; + $helptext .= " Available tokens for the custom notification text\n"; + $helptext .= " - '{user.username}', your username (including the @)\n"; + $helptext .= " - '{user.name}', your real name\n"; + $helptext .= + " - '{pca.name}', the name of the PCA you achieved, excluding the #\n"; + $helptext .= " - '{pca.emoji}', the emoji of the PCA you achieved\n"; + $helptext .= + " - '{pca.postcount}', the number of posts you need to achieve the PCA\n"; + $helptext .= " - '{nextpca.name}', the name of the next PCA\n"; + $helptext .= " - '{nextpca.emoji}', the emoji of the next PCA\n"; + $helptext .= + " - '{nextpca.postcount}', the total number of posts needed for the next PCA\n"; + $helptext .= + " - '{posts_to_pca}', the number of posts missing to the next PCA\n"; + $api->send_message($sender, $helptext); + //Print help text + } elseif (substr(mb_strtolower($msg), 0, 21) === 'get notification text') { + //Print current notification text for user + //TODO: build example notification text. Create user object and do 'get_highest_pca($clubs)' and 'get_next_pca($clubs)' + $api->send_message( + $sender, + "You current notification text is: \n" . + $api->get_notification_text_template($sender) + ); + } elseif (substr(mb_strtolower($msg), 0, 23) === 'reset notification text' + ) { + //reset notification text for user to default + $api->reset_notification_text_for_user($sender); + $api->send_message( + $sender, + "Your notification text has been reset back to the default: \n" . + $api->get_notification_text_template($sender) + ); + } elseif (substr(mb_strtolower($msg), 0, 21) === 'set notification text') { + $notification_text = substr($msg, 22); + $api->set_notification_text_template($sender, $notification_text); + $api->send_message( + $sender, + "Your notification text has been set to:\n" . + $api->get_notification_text_template($sender) + ); + } else { + write_pca_log( + "Unknown command: '" + . $msg + . "' from " + . $sender, + 'DEBUG' + ); + $api->send_message( + $sender, + "I'm sorry, I don't recognize that. Try PMing me 'help'" + ); + } + } +} +$followers = $api->get_bot_followers(); +// Check and notify. Also build notification dict +$next_club_infos = []; +foreach ($followers as $user) { + if ($last_notification_dict != null + && array_key_exists($user->user_name, $last_notification_dict) + ) { + write_pca_log( + $user->user_name . + ' has been notified in the past. Last time when reaching: ' . + $last_notification_dict[$user->user_name] + ); + $user->last_club_notification = $last_notification_dict[$user->user_name]; + } else { + write_pca_log($user->user_name . ' has never been notified in the past'); + } + $next_club_info = $api->notify_user($user, $clubs); + if ($next_club_info !== null) { + $next_club_info['user'] = $user->user_name; + $next_club_infos[] = $next_club_info; + } + if ($user->last_club_notification != '') { + write_pca_log( + 'Saving last notification ' . + $user->last_club_notification . + ' for user ' . + $user->user_name + ); + $last_notification_dict[$user->user_name] = $user->last_club_notification; + } else { + write_pca_log( + $user->user_name . + ' will not be saved, as they have not reached a club yet' + ); + } +} +usort( + $next_club_infos, + fn($a, $b) => intval($a['posts_until']) - intval($b['posts_until']) +); +foreach ($next_club_infos as $idx => $value) { + if ($idx >= 10) { + break; + } + write_pca_log( + 'Next club will be: ' + . $value['club']['pca'] + . ' for user ' + . $value['user'] + . ' in ' + . $value['posts_until'] + . ' posts' + ); +} + +// Save changes +write_pca_log('Saving updated notification info'); +$f = fopen($last_notification_file, 'w'); +fwrite($f, json_encode($last_notification_dict)); +fclose($f); + +?> diff --git a/README.md b/README.md new file mode 100644 index 0000000..0a3fe9f --- /dev/null +++ b/README.md @@ -0,0 +1,18 @@ +A small [https://pnut.io](https://pnut.io) bot. + +Can be pretty much cloned and run as-is, +with the only exception being a file called clientsecret in `../pca_settings/`, containing the client secret (duh). +If you don't have one, get it [here](https://pnut.io/dev) + +The bot is currently called roughly every 15 minutes by a cronjob, +so any follows/new club memberships might take a while to show up. + +Used to live at https://wedro.online/Check_PCA.php, +now lives at https://pca.phlaym.net/Check_PCA.php. + +Source used to be [on Github](https://github.com/hutattedonmyarm/pcabot), +but has now moved to [my Gitea instance](https://git.phlaym.net/phlaym/pca). +Without its history, mostly because the old repo contains a various assortment of Pnut related +stuff and this is much more split up now. + +Required PHP 8.x, tested with >=8.4 diff --git a/pca.json b/pca.json new file mode 100644 index 0000000..3177ee4 --- /dev/null +++ b/pca.json @@ -0,0 +1 @@ +[{"pca":"RollClub","emoji":"\ud83c\udf5e","post_count":"500","inventor":"@mlv"},{"pca":"MetalClub","emoji":"\ud83e\udd18","post_count":"666","inventor":"@hutattedonmyarm"},{"pca":"CrumpetClub","emoji":"\ud83c\udf70","post_count":"1000","inventor":"@irina"},{"pca":"NoonClub","emoji":"\ud83d\udd5b","post_count":"1200","inventor":"@samweinberg"},{"pca":"EnterpriseClub","emoji":"\ud83d\ude80","post_count":"1701","inventor":"@thedoctor,@ludolphus,@wife"},{"pca":"BitesizeCookieClub","emoji":"\ud83c\udf65","post_count":"2000","inventor":"@saket"},{"pca":"PnutClub","emoji":"\ud83e\udd5c","post_count":"2016","inventor":"@hutattedonmyarm"},{"pca":"CrunchClub","emoji":"\u260e\ufe0f","post_count":"2600","inventor":"@infodriveway"},{"pca":"MysteryScienceClub","emoji":"\ud83d\udce1","post_count":"3000","inventor":"@blt"},{"pca":"LDRClub","emoji":"\ud83d\udc5f","post_count":"5000","inventor":"@kym"},{"pca":"CPCClub","emoji":"\u2328\ufe0f","post_count":"6128","inventor":"@papierzeit"},{"pca":"IBMPCClub","emoji":"\ud83d\udcbb","post_count":"8088","inventor":"@duerig"},{"pca":"CookieClub","emoji":"\ud83c\udf6a","post_count":"10000","inventor":"@bondman"},{"pca":"SpinalTapClub","emoji":"\ud83d\udc89","post_count":"11000","inventor":"@paulyhedral"},{"pca":"BreakfastClub","emoji":"\ud83c\udf73","post_count":"20000","inventor":"@trine"},{"pca":"CaratClub","emoji":"\ud83d\udc8e","post_count":"24000","inventor":"@saket"},{"pca":"PeshawarClub","emoji":"\ud83c\udf5b","post_count":"25000","inventor":"@peemee"},{"pca":"MileHighClub","emoji":"\u2708\ufe0f","post_count":"30000","inventor":"@alicia"},{"pca":"PiClub","emoji":"\u2b55\ufe0f","post_count":"31416","inventor":"@kdfrawg"},{"pca":"TowelClub","emoji":"\ud83d\udc33","post_count":"42000","inventor":"@mps"},{"pca":"HitmanClub","emoji":"\ud83d\udd2a","post_count":"47000","inventor":"@remus"},{"pca":"BaconClub","emoji":"\ud83d\udc37","post_count":"50000","inventor":"@orangesn0w"},{"pca":"BrowncoatClub","emoji":"\ud83d\ude80","post_count":"57000","inventor":"@thedoctor"},{"pca":"CommodoreClub","emoji":"\ud83d\udd31","post_count":"64000","inventor":"@saket"},{"pca":"MotorolaClub","emoji":"\u24c2\ufe0f","post_count":"68000","inventor":"@kohlmannj"},{"pca":"TromboneClub","emoji":"\ud83c\udfb6","post_count":"76000","inventor":"@charlesg"},{"pca":"WiFiClub","emoji":"\ud83d\udcf6","post_count":"80211","inventor":"@rdo"},{"pca":"PajamaClub","emoji":"\ud83d\udcb7","post_count":"90000","inventor":"@mps"},{"pca":"TowerOfBabble","emoji":"\ud83d\uddfc","post_count":"100000","inventor":"@sham"},{"pca":"MacClub","emoji":"\ud83d\udcbb","post_count":"128000","inventor":"@fields"},{"pca":"TwitterLeaverClub","emoji":"","post_count":"140000","inventor":"@nhat"},{"pca":"GetALifeNoSrslyClub","emoji":"\ud83d\udc40","post_count":"200000","inventor":"@saket"},{"pca":"MeaninglessPostCountClub","emoji":"\ud83d\udcaf","post_count":"231568","inventor":"@cunarders"},{"pca":"ADNClub","emoji":"","post_count":"256000","inventor":"@trine"},{"pca":"PensionersClub","emoji":"\ud83c\udf97","post_count":"401000","inventor":"@sham"},{"pca":"MaglevClub","emoji":"\ud83d\ude84","post_count":"430000","inventor":"@remus"},{"pca":"LaughterClub","emoji":"\ud83e\udd23","post_count":"555000","inventor":"@saket"},{"pca":"GatesClub","emoji":"\ud83d\udcac","post_count":"640000","inventor":"@adiabatic"},{"pca":"JoyLuckClub","emoji":"\ud83c\udfa2","post_count":"888000","inventor":"@alicia"},{"pca":"MillionairesClub","emoji":"\ud83d\udcb0","post_count":"1000000","inventor":"@mps"},{"pca":"ToTheMoonClub","emoji":"\ud83d\udcc8","post_count":"21000000","inventor":"@blumenkraft"},{"pca":"GoogolplexClub","emoji":"\u03a9","post_count":"10000\u00ad000\u00ad\u00ad000000000\u00ad\u00ad000\u00ad000\u00ad000\u00ad\u00ad000\u00ad000\u00ad000\u00ad000\u00ad000\u00ad000\u00ad\u00ad000\u00ad000\u00ad000\u00ad\u00ad000\u00ad000000\u00ad\u00ad000\u00ad000\u00ad000\u00ad000\u00ad000\u00ad000\u00ad\u00ad000000\u00ad000\u00ad\u00ad000\u00ad000\u00ad000\u00ad\u00ad000","inventor":"@saket"}] \ No newline at end of file diff --git a/pca.php b/pca.php new file mode 100644 index 0000000..d341b3f --- /dev/null +++ b/pca.php @@ -0,0 +1,37 @@ +loadHTML($html); + $tables = $doc->getElementsByTagName('table'); + + $pca = array(); + + foreach ($tables as $table) { + if ($table->hasAttribute('class') && $table->getAttribute('class') == 'wikitable') { + foreach ($table->getElementsByTagName('tr') as $childNode) { + $entry = $childNode->getElementsByTagName('td'); + if ($entry->length > 0) { + $achievement["pca"] = preg_replace('/\s+/', '', $entry->item(0)->textContent); + $achievement["emoji"] = preg_replace('/\s+/', '', $entry->item(1)->textContent); + $achievement["post_count"] = preg_replace('/\s+/', '', $entry->item(2)->textContent); + $achievement["inventor"] = preg_replace('/\s+/', '', $entry->item(3)->textContent); + $pca[] = $achievement; + } + } + } + } + $myfile = fopen($filename, "w"); + $file_content = json_encode($pca); + fwrite($myfile, $file_content); + fclose($myfile); + echo $file_content; +} +?> diff --git a/pnutauth.php b/pnutauth.php new file mode 100644 index 0000000..eba53e5 --- /dev/null +++ b/pnutauth.php @@ -0,0 +1,39 @@ + $client_id, + 'client_secret' => $client_secret, + 'code' => $code, + 'redirect_uri' => $redirect_uri, + 'grant_type' => 'authorization_code' + ]; + + $ch = curl_init('https://api.pnut.io/v1/oauth/access_token'); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $post); + + // execute! + $response = curl_exec($ch); + + // close the connection, release resources used + curl_close($ch); + $resp = json_decode($response, true); + + file_put_contents('../pca_settings/access_token', $resp['access_token']); + header('Location: https://pca.phlaym.net/Check_PCA.php'); +} else { + header( + 'Location: https://pnut.io/oauth/authenticate?client_id=' + . $client_id + . '&redirect_uri=' + . urlencode($redirect_uri) + . '&scope=write_post,messages&response_type=code' + ); +} +?> \ No newline at end of file diff --git a/recent_progressions.php b/recent_progressions.php new file mode 100644 index 0000000..4eb23fd --- /dev/null +++ b/recent_progressions.php @@ -0,0 +1,204 @@ + + + + + Pnut.io PCA progression history + + + + +

Recent PCA progressions:

+ "; + die(); + } + $recent_changes = json_decode(file_get_contents($history_file), true); + if (count($recent_changes) == 0) { + echo "None :( "; + die(); + } + ?> + + + + + + + + + + + + + + + '.$entry['user'].''; + $pca = $entry['pca']; + $datetime = new DateTime($entry['date']); + $date = $datetime->format('Y-m-d H:i:s P'); + $post_id = ''.$entry['post_id'].''; + echo ''; + } + ?> + +
UserPCAPost IDDate
'.$user.''.$pca.''.$post_id.''.$date.'
+ + \ No newline at end of file diff --git a/styles/.htaccess b/styles/.htaccess new file mode 100644 index 0000000..b42966b --- /dev/null +++ b/styles/.htaccess @@ -0,0 +1,2 @@ + +Options All -Indexes diff --git a/styles/style_index.css b/styles/style_index.css new file mode 100644 index 0000000..b1f8e78 --- /dev/null +++ b/styles/style_index.css @@ -0,0 +1,36 @@ +body { + max-width: 700px; + margin: 0 auto; + padding: 10px; + font-family: "arial"; +} + +table, th, td { + border: 1px solid black; + border-collapse: collapse; +} + +th, td { + padding: 5px; +} + +th { + cursor: pointer; +} + +.arrow { + position: relative; +} +.arrow:after { + content: ""; + position: absolute; + top: 0; + right: 0; + width: 0; + height: 0; + display: block; + border-left: 5px solid transparent; + border-bottom: 5px solid transparent; + border-top: 5px solid #000; +} +