diff --git a/VKAPI/Exceptions/APIErrorException.php b/VKAPI/Exceptions/APIErrorException.php new file mode 100644 index 00000000..c570e0e9 --- /dev/null +++ b/VKAPI/Exceptions/APIErrorException.php @@ -0,0 +1,5 @@ +requireUser(); + + return (object) [ + "first_name" => $this->getUser()->getFirstName(), + "id" => $this->getUser()->getId(), + "last_name" => $this->getUser()->getLastName(), + "home_town" => $this->getUser()->getHometown(), + "status" => $this->getUser()->getStatus(), + "bdate" => "1.1.1970", // TODO + "bdate_visibility" => 0, // TODO + "phone" => "+420 ** *** 228", // TODO + "relation" => $this->getUser()->getMaritalStatus(), + "sex" => $this->getUser()->isFemale() ? 1 : 2 + ]; + } + + function getInfo(): object + { + $this->requireUser(); + + // Цiй метод є заглушка + + return (object) [ + "2fa_required" => 0, + "country" => "CZ", // TODO + "eu_user" => false, // TODO + "https_required" => 1, + "intro" => 0, + "community_comments" => false, + "is_live_streaming_enabled" => false, + "is_new_live_streaming_enabled" => false, + "lang" => 1, + "no_wall_replies" => 0, + "own_posts_default" => 0 + ]; + } + + function setOnline(): object + { + $this->requireUser(); + + $this->getUser()->setOnline(time()); + + return 1; + } + + function setOffline(): object + { + $this->requireUser(); + + // Цiй метод є заглушка + + return 1; + } + + function getAppPermissions(): object + { + return 9355263; + } + + function getCounters(string $filter = ""): object + { + return (object) [ + "friends" => $this->getUser()->getFollowersCount(), + "notifications" => $this->getUser()->getNotificationsCount() + ]; + + // TODO: Filter + } +} diff --git a/VKAPI/Handlers/Friends.php b/VKAPI/Handlers/Friends.php new file mode 100644 index 00000000..31a9f6d9 --- /dev/null +++ b/VKAPI/Handlers/Friends.php @@ -0,0 +1,154 @@ +requireUser(); + + foreach ($users->get($user_id)->getFriends($offset, $count) as $friend) { + $friends[$i] = $friend->getId(); + $i++; + } + + $response = $friends; + + $usersApi = new Users($this->getUser()); + + if (!is_null($fields)) { + $response = $usersApi->get(implode(',', $friends), $fields, 0, $count, true); // FIXME + } + + return (object) [ + "count" => $users->get($user_id)->getFriendsCount(), + "items" => $response + ]; + } + + function getLists(): object + { + $this->requireUser(); + + return (object) [ + "count" => 0, + "items" => (array)[] + ]; + } + + function deleteList(): int + { + $this->requireUser(); + + return 1; + } + + function edit(): int + { + $this->requireUser(); + + return 1; + } + + function editList(): int + { + $this->requireUser(); + + return 1; + } + + function add(string $user_id): int + { + $this->requireUser(); + + $users = new UsersRepo; + + $user = $users->get(intval($user_id)); + + switch ($user->getSubscriptionStatus($this->getUser())) { + case 0: + $user->toggleSubscription($this->getUser()); + return 1; + break; + + case 1: + $user->toggleSubscription($this->getUser()); + return 2; + break; + + case 3: + return 2; + break; + + default: + return 1; + break; + } + } + + function delete(string $user_id): int + { + $this->requireUser(); + + $users = new UsersRepo; + + $user = $users->get(intval($user_id)); + + switch ($user->getSubscriptionStatus($this->getUser())) { + case 3: + $user->toggleSubscription($this->getUser()); + return 1; + break; + + default: + fail(15, "Access denied: No friend or friend request found."); + break; + } + } + + function areFriends(string $user_ids): array + { + $this->requireUser(); + + $users = new UsersRepo; + + $friends = explode(',', $user_ids); + + $response = []; + + for ($i=0; $i < sizeof($friends); $i++) { + $friend = $users->get(intval($friends[$i])); + + $status = 0; + switch ($friend->getSubscriptionStatus($this->getUser())) { + case 3: + case 0: + $status = $friend->getSubscriptionStatus($this->getUser()); + break; + + case 1: + $status = 2; + break; + + case 2: + $status = 1; + break; + } + + $response[] = (object)[ + "friend_status" => $friend->getSubscriptionStatus($this->getUser()), + "user_id" => $friend->getId() + ]; + } + + return $response; + } +} \ No newline at end of file diff --git a/VKAPI/Handlers/Groups.php b/VKAPI/Handlers/Groups.php new file mode 100644 index 00000000..b531668f --- /dev/null +++ b/VKAPI/Handlers/Groups.php @@ -0,0 +1,83 @@ +requireUser(); + + $clubs = new ClubsRepo; + $clbs = explode(',', $group_ids); + $response; + + $ic = sizeof($clbs); + + if(sizeof($clbs) > $count) $ic = $count; + + $clbs = array_slice($clbs, $offset * $count); + + for ($i=0; $i < $ic; $i++) { + $usr = $clubs->get((int) $clbs[$i]); + if(is_null($usr)) + { + $response[$i] = (object)[ + "id" => $clbs[$i], + "first_name" => "DELETED", + "last_name" => "", + "deactivated" => "deleted" + ]; + }else if($clbs[$i] == null){ + + }else{ + $response[$i] = (object)[ + "id" => $usr->getId(), + "first_name" => $usr->getFirstName(), + "last_name" => $usr->getLastName(), + "is_closed" => false, + "can_access_closed" => true, + ]; + + $flds = explode(',', $fields); + + foreach($flds as $field) { + switch ($field) { + case 'verified': + $response[$i]->verified = intval($usr->isVerified()); + break; + case 'sex': + $response[$i]->sex = $this->getUser()->isFemale() ? 1 : 2; + break; + case 'has_photo': + $response[$i]->has_photo = is_null($usr->getAvatarPhoto()) ? 0 : 1; + break; + case 'photo_max_orig': + $response[$i]->photo_max_orig = $usr->getAvatarURL(); + break; + case 'photo_max': + $response[$i]->photo_max = $usr->getAvatarURL(); + break; + + } + } + + // НУЖЕН фикс - либо из-за моего дебилизма, либо из-за сегментации котлеток некоторые пользовали отображаются как онлайн, хотя лол, если зайти на страницу, то оный уже офлайн + if($online == true && $usr->getOnline()->timestamp() + 2505600 > time()) { + $response[$i]->online = 1; + }else{ + $response[$i]->online = 0; + } + + } + } + + return $response; + } +} diff --git a/VKAPI/Handlers/Messages.php b/VKAPI/Handlers/Messages.php new file mode 100644 index 00000000..2b76b85d --- /dev/null +++ b/VKAPI/Handlers/Messages.php @@ -0,0 +1,297 @@ + 0) + return NULL; + + return $peer_id; + } + + return $user_id; + } + + function getById(string $message_ids, int $preview_length = 0, int $extended = 0): object + { + $this->requireUser(); + + $msgs = new MSGRepo; + $ids = preg_split("%, ?%", $message_ids); + $items = []; + foreach($ids as $id) { + $message = $msgs->get((int) $id); + if(!$message) + continue; + else if($message->getSender()->getId() !== $this->getUser()->getId() && $message->getRecipient()->getId() !== $this->getUser()->getId()) + continue; + + $author = $message->getSender()->getId() === $this->getUser()->getId() ? $message->getRecipient()->getId() : $message->getSender()->getId(); + $rMsg = new APIMsg; + + $rMsg->id = $message->getId(); + $rMsg->user_id = $author; + $rMsg->from_id = $message->getSender()->getId(); + $rMsg->date = $message->getSendTime()->timestamp(); + $rMsg->read_state = 1; + $rMsg->out = (int) ($message->getSender()->getId() === $this->getUser()->getId()); + $rMsg->body = $message->getText(false); + $rMsg->emoji = true; + + if($preview_length > 0) + $rMsg->body = ovk_proc_strtr($rMsg->body, $preview_length); + + $items[] = $rMsg; + } + + return (object) [ + "count" => sizeof($items), + "items" => $items, + ]; + } + + function send(int $user_id = -1, int $peer_id = -1, string $domain = "", int $chat_id = -1, string $user_ids = "", string $message = "", int $sticker_id = -1) + { + $this->requireUser(); + + if($chat_id !== -1) + $this->fail(946, "Chats are not implemented"); + else if($sticker_id !== -1) + $this->fail(-151, "Stickers are not implemented"); + else if(empty($message)) + $this->fail(100, "Message text is empty or invalid"); + + # lol recursion + if(!empty($user_ids)) { + $rIds = []; + $ids = preg_split("%, ?%", $user_ids); + if(sizeof($ids) > 100) + $this->fail(913, "Too many recipients"); + + foreach($ids as $id) + $rIds[] = $this->send(-1, $id, "", -1, "", $message); + + return $rIds; + } + + if(!empty($domain)) { + $peer = (new USRRepo)->getByShortCode($domain); + } else { + $peer = $this->resolvePeer($user_id, $peer_id); + $peer = (new USRRepo)->get($peer); + } + + if(!$peer) + $this->fail(936, "There is no peer with this id"); + + if($peer->getSubscriptionStatus($this->getUser()) !== 3) + $this->fail(945, "This chat is disabled because of privacy settings"); + + # Finally we get to send a message! + $chat = new Correspondence($this->getUser(), $peer); + $msg = new Message; + $msg->setContent($message); + + $msg = $chat->sendMessage($msg, true); + if(!$msg) + $this->fail(950, "Internal error"); + else + return $msg->getId(); + } + + function delete(string $message_ids, int $spam = 0, int $delete_for_all = 0): object + { + $this->requireUser(); + + $msgs = new MSGRepo; + $ids = preg_split("%, ?%", $message_ids); + $items = []; + foreach($ids as $id) { + $message = $msgs->get((int) $id); + if(!$message || $message->getSender()->getId() !== $this->getUser()->getId() && $message->getRecipient()->getId() !== $this->getUser()->getId()) { + $items[$id] = 0; + } + + $message->delete(); + $items[$id] = 1; + } + + return (object) $items; + } + + function restore(int $message_id): int + { + $this->requireUser(); + + $msg = (new MSGRepo)->get($message_id); + if(!$msg) + return 0; + else if($msg->getSender()->getId() !== $this->getUser()->getId()) + return 0; + + $msg->undelete(); + return 1; + } + + function getConversations(int $offset = 0, int $count = 20, string $filter = "all", int $extended = 0): object + { + $this->requireUser(); + + $convos = (new MSGRepo)->getCorrespondencies($this->getUser(), -1, $count, $offset); + $list = []; + foreach($convos as $convo) { + $correspondents = $convo->getCorrespondents(); + if($correspondents[0]->getId() === $this->getUser()->getId()) + $peer = $correspondents[1]; + else + $peer = $correspondents[0]; + + $lastMessage = $convo->getPreviewMessage(); + + $listConvo = new APIConvo; + $listConvo->peer = [ + "id" => $peer->getId(), + "type" => "user", + "local_id" => $peer->getId(), + ]; + + $canWrite = $peer->getSubscriptionStatus($this->getUser()) === 3; + $listConvo->can_write = [ + "allowed" => $canWrite, + ]; + + $lastMessagePreview = NULL; + if(!is_null($lastMessage)) { + $listConvo->last_message_id = $lastMessage->getId(); + + if($lastMessage->getSender()->getId() === $this->getUser()->getId()) + $author = $lastMessage->getRecipient()->getId(); + else + $author = $lastMessage->getSender()->getId(); + + $lastMessagePreview = new APIMsg; + $lastMessagePreview->id = $lastMessage->getId(); + $lastMessagePreview->user_id = $author; + $lastMessagePreview->from_id = $lastMessage->getSender()->getId(); + $lastMessagePreview->date = $lastMessage->getSendTime()->timestamp(); + $lastMessagePreview->read_state = 1; + $lastMessagePreview->out = (int) ($lastMessage->getSender()->getId() === $this->getUser()->getId()); + $lastMessagePreview->body = $lastMessage->getText(false); + $lastMessagePreview->emoji = true; + } + + $list[] = [ + "conversation" => $listConvo, + "last_message" => $lastMessagePreview, + ]; + } + + return (object) [ + "count" => sizeof($list), + "items" => $list, + ]; + } + + function getHistory(int $offset = 0, int $count = 20, int $user_id = -1, int $peer_id = -1, int $start_message_id = 0, int $rev = 0, int $extended = 0): object + { + $this->requireUser(); + + if($user_id = $this->resolvePeer($user_id, $peer_id)) + $this->fail(-151, "Chats are not implemented"); + + $peer = (new USRRepo)->get($user_id); + if(!$peer) + $this->fail(1, "ошибка про то что пира нет"); + + $results = []; + $dialogue = new Correspondence($this->getUser(), $peer); + $iterator = $dialogue->getMessages(Correspondence::CAP_BEHAVIOUR_START_MESSAGE_ID, $start_message_id, $count, abs($offset), $rev === 1); + foreach($iterator as $message) { + $msgU = $message->unwrap(); # Why? As of OpenVK 2 Public Preview Two database layer doesn't work correctly and refuses to cache entities. + # UPDATE: the issue seems to be caused by debug mode and json_encode (bruh_encode). ~~Dorothy + + $rMsg = new APIMsg; + $rMsg->id = $msgU->id; + $rMsg->user_id = $msgU->sender_id === $this->getUser()->getId() ? $msgU->recipient_id : $msgU->sender_id; + $rMsg->from_id = $msgU->sender_id; + $rMsg->date = $msgU->created; + $rMsg->read_state = 1; + $rMsg->out = (int) ($msgU->sender_id === $this->getUser()->getId()); + $rMsg->body = $message->getText(false); + $rMsg->emoji = true; + + $results[] = $rMsg; + } + + return (object) [ + "count" => sizeof($results), + "items" => $results, + ]; + } + + function getLongPollHistory(int $ts = -1, int $preview_length = 0, int $events_limit = 1000, int $msgs_limit = 1000): object + { + $this->requireUser(); + + $res = [ + "history" => [], + "messages" => [], + "profiles" => [], + "new_pts" => 0, + ]; + + $manager = SignalManager::i(); + $events = $manager->getHistoryFor($this->getUser()->getId(), $ts === -1 ? NULL : $ts, min($events_limit, $msgs_limit)); + foreach($events as $event) { + if(!($event instanceof NewMessageEvent)) + continue; + + $message = $this->getById((string) $event->getLongPoolSummary()->message["uuid"], $preview_length, 1)->items[0]; + if(!$message) + continue; + + $res["messages"][] = $message; + $res["history"][] = $event->getVKAPISummary($this->getUser()->getId()); + } + + $res["messages"] = [ + "count" => sizeof($res["messages"]), + "items" => $res["messages"], + ]; + return (object) $res; + } + + function getLongPollServer(int $need_pts = 1, int $lp_version = 3, ?int $group_id = NULL): array + { + $this->requireUser(); + + if($group_id > 0) + $this->fail(-151, "Not implemented"); + + $url = "http" . ($_SERVER["HTTPS"] === "on" ? "s" : "") . "://$_SERVER[HTTP_HOST]/nim" . $this->getUser()->getId(); + $key = openssl_random_pseudo_bytes(8); + $key = bin2hex($key) . bin2hex($key ^ ( ~CHANDLER_ROOT_CONF["security"]["secret"] | ((string) $this->getUser()->getId()) )); + $res = [ + "key" => $key, + "server" => $url, + "ts" => time(), + ]; + + if($need_pts === 1) + $res["pts"] = -1; + + return $res; + } +} diff --git a/VKAPI/Handlers/Ovk.php b/VKAPI/Handlers/Ovk.php new file mode 100644 index 00000000..42243cf1 --- /dev/null +++ b/VKAPI/Handlers/Ovk.php @@ -0,0 +1,24 @@ + $this->userAuthorized(), + "auth_with" => $_GET["auth_mechanism"] ?? "access_token", + "version" => VKAPI_DECL_VER, + ]; + } + + function chickenWings(): string + { + return "крылышки"; + } +} diff --git a/VKAPI/Handlers/Users.php b/VKAPI/Handlers/Users.php new file mode 100644 index 00000000..4e4c2b0b --- /dev/null +++ b/VKAPI/Handlers/Users.php @@ -0,0 +1,123 @@ +requireUser(); + + $users = new UsersRepo; + $usrs = explode(',', $user_ids); + $response; + + $ic = sizeof($usrs); + + if(sizeof($usrs) > $count) $ic = $count; + + $usrs = array_slice($usrs, $offset * $count); + + for ($i=0; $i < $ic; $i++) { + $usr = $users->get((int) $usrs[$i]); + if(is_null($usr)) + { + $response[$i] = (object)[ + "id" => $usrs[$i], + "first_name" => "DELETED", + "last_name" => "", + "deactivated" => "deleted" + ]; + }else if($usrs[$i] == null){ + + }else{ + $response[$i] = (object)[ + "id" => $usr->getId(), + "first_name" => $usr->getFirstName(), + "last_name" => $usr->getLastName(), + "is_closed" => false, + "can_access_closed" => true, + ]; + + $flds = explode(',', $fields); + + foreach($flds as $field) { + switch ($field) { + case 'verified': + $response[$i]->verified = intval($usr->isVerified()); + break; + case 'sex': + $response[$i]->sex = $this->getUser()->isFemale() ? 1 : 2; + break; + case 'has_photo': + $response[$i]->has_photo = is_null($usr->getAvatarPhoto()) ? 0 : 1; + break; + case 'photo_max_orig': + $response[$i]->photo_max_orig = $usr->getAvatarURL(); + break; + case 'photo_max': + $response[$i]->photo_max = $usr->getAvatarURL(); + break; + + } + } + + // НУЖЕН фикс - либо из-за моего дебилизма, либо из-за сегментации котлеток некоторые пользовали отображаются как онлайн, хотя лол, если зайти на страницу, то оный уже офлайн + if($online == true && $usr->getOnline()->timestamp() + 2505600 > time()) { + $response[$i]->online = 1; + }else{ + $response[$i]->online = 0; + } + + } + } + + return $response; + } + + /* private function getUsersById(string $user_ids, string $fields = "", int $offset = 0, int $count = PHP_INT_MAX){ + + } */ + + function getFollowers(int $user_id, string $fields = "", int $offset = 0, int $count = 100): object + { + $offset++; + $followers = []; + + $users = new UsersRepo; + + $this->requireUser(); + + foreach ($users->get($user_id)->getFollowers($offset, $count) as $follower) { + $followers[] = $follower->getId(); + } + + $response = $followers; + + if (!is_null($fields)) { + $response = $this->get(implode(',', $followers), $fields, 0, $count); + } + + return (object) [ + "count" => $users->get($user_id)->getFollowersCount(), + "items" => $response + ]; + } + + function search(string $q, string $fields = '', int $offset = 0, int $count = 100) + { + $users = new UsersRepo; + + $array = []; + + foreach ($users->find($q) as $user) { + $array[] = $user->getId(); + } + + return (object)[ + "count" => $users->getFoundCount($q), + "items" => $this->get(implode(',', $array), $fields, $offset, $count) + ]; + } +} diff --git a/VKAPI/Handlers/VKAPIRequestHandler.php b/VKAPI/Handlers/VKAPIRequestHandler.php new file mode 100644 index 00000000..79cfb41b --- /dev/null +++ b/VKAPI/Handlers/VKAPIRequestHandler.php @@ -0,0 +1,35 @@ +user = $user; + } + + protected function fail(int $code, string $message): void + { + throw new APIErrorException($message, $code); + } + + protected function getUser(): ?User + { + return $this->user; + } + + protected function userAuthorized(): bool + { + return !is_null($this->getUser()); + } + + protected function requireUser(): void + { + if(!$this->userAuthorized()) + $this->fail(5, "User authorization failed: no access_token passed."); + } +} diff --git a/VKAPI/Handlers/Wall.php b/VKAPI/Handlers/Wall.php new file mode 100644 index 00000000..4c361705 --- /dev/null +++ b/VKAPI/Handlers/Wall.php @@ -0,0 +1,64 @@ +getPostsFromUsersWall((int)$owner_id) as $post) { + $items[] = (object)[ + "id" => $post->getVirtualId(), + "from_id" => $post->getOwner()->getId(), + "owner_id" => $post->getTargetWall(), + "date" => $post->getPublicationTime()->timestamp(), + "post_type" => "post", + "text" => $post->getText(), + "can_edit" => 0, // TODO + "can_delete" => $post->canBeDeletedBy($this->getUser()), + "can_pin" => 0, // TODO + "can_archive" => false, // TODO MAYBE + "is_archived" => false, + "post_source" => (object)["type" => "vk"], + "comments" => (object)[ + "count" => $post->getCommentsCount(), + "can_post" => 1 + ], + "likes" => (object)[ + "count" => $post->getLikesCount(), + "user_likes" => (int) $post->hasLikeFrom($this->getUser()), + "can_like" => 1, + "can_publish" => 1, + ], + "reposts" => (object)[ + "count" => $post->getRepostCount(), + "user_reposted" => 0 + ] + ]; + } + + $profiles = []; + $groups = []; + + $groups[0] = 'lol'; + $groups[2] = 'cec'; + + if($extended == 1) + return (object)[ + "items" => (array)$items, + "cock" => (array)$groups + ]; + else + return (object)[ + "items" => (array)$items + ]; + } +} diff --git a/VKAPI/README.textile b/VKAPI/README.textile new file mode 100644 index 00000000..b003daa0 --- /dev/null +++ b/VKAPI/README.textile @@ -0,0 +1,31 @@ +h1. VK API Compatability layer for OpenVK + +This directory contains VK api handlers, structures and relared exceptions. It is still a work-in-progress functionality. +**Note**: requests to api are routed through openvk.Web.Presenters.VKAPIPresenter, this dir contains only handlers. + +h2. Implementing API methods + +VK API methods have names like this: %example.test%. To implement a method like this you will need to create a class %Example% in the Handlers subdirectory. This class **must** extend VKAPIHandler and be final. +Next step is to create %test% method. It **must** have a type hint that is not %void%. Everything else is fine, the return value of method will be authomatically converted to JSON and sent back to client. + +h3. Parameters + +Method arguments are parameters. To declare a parameter just create an argument with the same name. You should also provide correct type hints for them. Type conversion is done automatically if possible. If not possible error №1 will be returned. +If parameter is not passed by client then router will pass default value to argument. If there is no default value but argument accepts NULL then NULL will be passed. If NULL is not acceptable, default value is undefined and parameter is not passed, API will return missing parameter error to client. + +h3. Returning errors + +To return an error, call %fail% method like this: %$this->fail(5, "error")% (first argument is error code and second is error message). You can also throw the exception manually: %throw new APIErrorException("error", 5)% (class: openvk.VKAPI.Exceptions.APIErrorException). +If you throw any exception that does not inherit APIErrorException then API will return error №1 (unknown error) to client. + +h3. Refering to user + +To get user use %getUser% method: %$this->getUser()%. Keep in mind it will return NULL if user is undefined (no access_token passed or it is invalid/expired or roaming authentification failed). +If you need to check whether user is defined use %userAuthorized%. This method returns true if user is present and false if not. +If your method can't work without user context call %requireUser% and it will automatically return unauthorized error. + +h3. Working with data + +You can use OpenVK models for that. However, **do not** return them (either you will leak data or JSON conversion will fail). It is better to create a response object and return it. It is also a good idea to define a structure in Structures subdirectory. + +Have a lot of fun ^^ diff --git a/VKAPI/Structures/Conversation.php b/VKAPI/Structures/Conversation.php new file mode 100644 index 00000000..ad8951f3 --- /dev/null +++ b/VKAPI/Structures/Conversation.php @@ -0,0 +1,13 @@ +get($this->getRecord()->user); + } + + function getSecret(): string + { + return $this->getRecord()->secret; + } + + function getFormattedToken(): string + { + return $this->getId() . "-" . chunk_split($this->getSecret(), 8, "-") . "jill"; + } + + function isRevoked(): bool + { + return $this->isDeleted(); + } + + function setUser(User $user): void + { + $this->stateChanges("user", $user->getId()); + } + + function setSecret(string $secret): void + { + throw new ISE("Setting secret manually is prohbited"); + } + + function revoke(): void + { + $this->delete(); + } + + function save(): void + { + if(is_null($this->getRecord())) + $this->stateChanges("secret", bin2hex(openssl_random_pseudo_bytes(36))); + + parent::save(); + } +} diff --git a/Web/Models/Repositories/APITokens.php b/Web/Models/Repositories/APITokens.php new file mode 100644 index 00000000..592688a8 --- /dev/null +++ b/Web/Models/Repositories/APITokens.php @@ -0,0 +1,26 @@ +get((int) $id); + if(!$token) + return NULL; + else if($token->getSecret() !== $secret) + return NULL; + else if($token->isRevoked() && !$withRevoked) + return NULL; + + return $token; + } +} diff --git a/Web/Presenters/VKAPIPresenter.php b/Web/Presenters/VKAPIPresenter.php new file mode 100644 index 00000000..c82f9b37 --- /dev/null +++ b/Web/Presenters/VKAPIPresenter.php @@ -0,0 +1,154 @@ + $code, + "error_msg" => $message, + "request_params" => [ + [ + "key" => "method", + "value" => "$object.$method", + ], + [ + "key" => "oauth", + "value" => 1, + ], + ], + ]; + + foreach($_GET as $key => $value) + array_unshift($payload["request_params"], [ "key" => $key, "value" => $value ]); + + exit(json_encode($payload)); + } + + private function badMethod(string $object, string $method): void + { + $this->fail(3, "Unknown method passed.", $object, $method); + } + + private function badMethodCall(string $object, string $method, string $param): void + { + $this->fail(100, "Required parameter '$param' missing.", $object, $method); + } + + function renderRoute(string $object, string $method): void + { + $authMechanism = $this->queryParam("auth_mechanism") ?? "token"; + if($authMechanism === "roaming") { + if(!$this->user->identity) + $this->fail(5, "User authorization failed: roaming mechanism is selected, but user is not logged in.", $object, $method); + else + $identity = $this->user->identity; + } else { + if(is_null($this->requestParam("access_token"))) { + $identity = NULL; + } else { + $token = (new APITokens)->getByCode($this->requestParam("access_token")); + if(!$token) + $identity = NULL; + else + $identity = $token->getUser(); + } + } + + $object = ucfirst(strtolower($object)); + $handlerClass = "openvk\\VKAPI\\Handlers\\$object"; + if(!class_exists($handlerClass)) + $this->badMethod($object, $method); + + $handler = new $handlerClass($identity); + if(!is_callable([$handler, $method])) + $this->badMethod($object, $method); + + $route = new \ReflectionMethod($handler, $method); + $params = []; + foreach($route->getParameters() as $parameter) { + $val = $this->requestParam($parameter->getName()); + if(is_null($val)) { + if($parameter->allowsNull()) + $val = NULL; + else if($parameter->isDefaultValueAvailable()) + $val = $parameter->getDefaultValue(); + else if($parameter->isOptional()) + $val = NULL; + else + $this->badMethodCall($object, $method, $parameter->getName()); + } + + settype($val, $parameter->getType()->getName()); + $params[] = $val; + } + + define("VKAPI_DECL_VER", $this->requestParam("v") ?? "4.100", false); + + try { + $res = $handler->{$method}(...$params); + } catch(APIErrorException $ex) { + $this->fail($ex->getCode(), $ex->getMessage(), $object, $method); + } + + $result = json_encode([ + "response" => $res, + ]); + + $size = strlen($result); + header("Content-Type: application/json"); + header("Content-Length: $size"); + exit($result); + } + + function renderTokenLogin(): void + { + if($this->requestParam("grant_type") !== "password") + $this->fail(7, "Invalid grant type", "internal", "acquireToken"); + else if(is_null($this->requestParam("username")) || is_null($this->requestParam("password"))) + $this->fail(100, "Password and username not passed", "internal", "acquireToken"); + + $chUser = DB::i()->getContext()->table("ChandlerUsers")->where("login", $this->requestParam("username"))->fetch(); + if(!$chUser) + $this->fail(28, "Invalid username or password", "internal", "acquireToken"); + + $auth = Authenticator::i(); + if(!$auth->verifyCredentials($chUser->id, $this->requestParam("password"))) + $this->fail(28, "Invalid username or password", "internal", "acquireToken"); + + $uId = $chUser->related("profiles.user")->fetch()->id; + $user = (new Users)->get($uId); + + $token = new APIToken; + $token->setUser($user); + $token->save(); + + $payload = json_encode([ + "access_token" => $token->getFormattedToken(), + "expires_in" => 0, + "user_id" => $uId, + ]); + + $size = strlen($payload); + header("Content-Type: application/json"); + header("Content-Length: $size"); + exit($payload); + } +} diff --git a/Web/di.yml b/Web/di.yml index c1704dcc..2a904fc3 100644 --- a/Web/di.yml +++ b/Web/di.yml @@ -20,6 +20,7 @@ services: - openvk\Web\Presenters\AdminPresenter - openvk\Web\Presenters\MessengerPresenter - openvk\Web\Presenters\ThemepacksPresenter + - openvk\Web\Presenters\VKAPIPresenter - openvk\Web\Models\Repositories\Users - openvk\Web\Models\Repositories\Posts - openvk\Web\Models\Repositories\Photos diff --git a/Web/routes.yml b/Web/routes.yml index 86bea16e..438c8e23 100644 --- a/Web/routes.yml +++ b/Web/routes.yml @@ -163,6 +163,8 @@ routes: handler: "Messenger->app" - url: "/im{num}" handler: "Messenger->events" + - url: "/nim{num}" + handler: "Messenger->VKEvents" - url: "/im/api/messages{num}/{num}.json" handler: "Messenger->apiGetMessages" - url: "/im/api/messages{num}/create.json" @@ -195,6 +197,10 @@ routes: handler: "Admin->quickBan" - url: "/admin/warn.pl/{num}" handler: "Admin->quickWarn" + - url: "/method/{text}.{text}" + handler: "VKAPI->route" + - url: "/token" + handler: "VKAPI->tokenLogin" - url: "/sandbox_cocksex" handler: "About->sandbox" - url: "/{?shortCode}"