mirror of
https://github.com/openvk/openvk
synced 2024-11-13 10:39:24 +03:00
[WIP] Add VK API compat layer
This commit is contained in:
parent
73ae87d580
commit
e18a661507
17 changed files with 1166 additions and 0 deletions
5
VKAPI/Exceptions/APIErrorException.php
Normal file
5
VKAPI/Exceptions/APIErrorException.php
Normal file
|
@ -0,0 +1,5 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\VKAPI\Exceptions;
|
||||
|
||||
class APIErrorException extends \Exception
|
||||
{}
|
77
VKAPI/Handlers/Account.php
Normal file
77
VKAPI/Handlers/Account.php
Normal file
|
@ -0,0 +1,77 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\VKAPI\Handlers;
|
||||
|
||||
final class Account extends VKAPIRequestHandler
|
||||
{
|
||||
function getProfileInfo(): object
|
||||
{
|
||||
$this->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
|
||||
}
|
||||
}
|
154
VKAPI/Handlers/Friends.php
Normal file
154
VKAPI/Handlers/Friends.php
Normal file
|
@ -0,0 +1,154 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\VKAPI\Handlers;
|
||||
use openvk\Web\Models\Entities\User;
|
||||
use openvk\Web\Models\Repositories\Users as UsersRepo;
|
||||
|
||||
final class Friends extends VKAPIRequestHandler
|
||||
{
|
||||
function get(int $user_id, string $fields = "", int $offset = 0, int $count = 100): object
|
||||
{
|
||||
$i = 0;
|
||||
$offset++;
|
||||
$friends = [];
|
||||
|
||||
$users = new UsersRepo;
|
||||
|
||||
$this->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;
|
||||
}
|
||||
}
|
83
VKAPI/Handlers/Groups.php
Normal file
83
VKAPI/Handlers/Groups.php
Normal file
|
@ -0,0 +1,83 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\VKAPI\Handlers;
|
||||
use openvk\Web\Models\Entities\User;
|
||||
use openvk\Web\Models\Repositories\clubs as clubsRepo;
|
||||
use openvk\Web\Models\Entities\Clubs;
|
||||
use openvk\Web\Models\Repositories\Clubs as ClubsRepo;
|
||||
use openvk\Web\Models\Entities\Post;
|
||||
use openvk\Web\Models\Entities\Postable;
|
||||
use openvk\Web\Models\Repositories\Posts as PostsRepo;
|
||||
|
||||
final class Groups extends VKAPIRequestHandler
|
||||
{
|
||||
function get(string $group_ids, string $fields = "", int $offset = 0, int $count = 100, bool $online = false): array
|
||||
{
|
||||
$this->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;
|
||||
}
|
||||
}
|
297
VKAPI/Handlers/Messages.php
Normal file
297
VKAPI/Handlers/Messages.php
Normal file
|
@ -0,0 +1,297 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\VKAPI\Handlers;
|
||||
use openvk\Web\Events\NewMessageEvent;
|
||||
use openvk\Web\Models\Entities\{Correspondence, Message};
|
||||
use openvk\Web\Models\Repositories\{Messages as MSGRepo, Users as USRRepo};
|
||||
use openvk\VKAPI\Structures\{Message as APIMsg, Conversation as APIConvo};
|
||||
use Chandler\Signaling\SignalManager;
|
||||
|
||||
final class Messages extends VKAPIRequestHandler
|
||||
{
|
||||
private function resolvePeer(int $user_id = -1, int $peer_id = -1): ?int
|
||||
{
|
||||
if($user_id === -1) {
|
||||
if($peer_id === -1)
|
||||
return NULL;
|
||||
else if($peer_id < 0)
|
||||
return NULL;
|
||||
else if(($peer_id - 2000000000) > 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;
|
||||
}
|
||||
}
|
24
VKAPI/Handlers/Ovk.php
Normal file
24
VKAPI/Handlers/Ovk.php
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\VKAPI\Handlers;
|
||||
|
||||
final class Ovk extends VKAPIRequestHandler
|
||||
{
|
||||
function version(): string
|
||||
{
|
||||
return OPENVK_VERSION;
|
||||
}
|
||||
|
||||
function test(): object
|
||||
{
|
||||
return (object) [
|
||||
"authorized" => $this->userAuthorized(),
|
||||
"auth_with" => $_GET["auth_mechanism"] ?? "access_token",
|
||||
"version" => VKAPI_DECL_VER,
|
||||
];
|
||||
}
|
||||
|
||||
function chickenWings(): string
|
||||
{
|
||||
return "крылышки";
|
||||
}
|
||||
}
|
123
VKAPI/Handlers/Users.php
Normal file
123
VKAPI/Handlers/Users.php
Normal file
|
@ -0,0 +1,123 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\VKAPI\Handlers;
|
||||
use openvk\Web\Models\Entities\User;
|
||||
use openvk\Web\Models\Repositories\Users as UsersRepo;
|
||||
|
||||
final class Users extends VKAPIRequestHandler
|
||||
{
|
||||
function get(string $user_ids, string $fields = "", int $offset = 0, int $count = 100, bool $online = false): array
|
||||
{
|
||||
$this->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)
|
||||
];
|
||||
}
|
||||
}
|
35
VKAPI/Handlers/VKAPIRequestHandler.php
Normal file
35
VKAPI/Handlers/VKAPIRequestHandler.php
Normal file
|
@ -0,0 +1,35 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\VKAPI\Handlers;
|
||||
use openvk\VKAPI\Exceptions\APIErrorException;
|
||||
use openvk\Web\Models\Entities\User;
|
||||
|
||||
abstract class VKAPIRequestHandler
|
||||
{
|
||||
protected $user;
|
||||
|
||||
function __construct(?User $user = NULL)
|
||||
{
|
||||
$this->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.");
|
||||
}
|
||||
}
|
64
VKAPI/Handlers/Wall.php
Normal file
64
VKAPI/Handlers/Wall.php
Normal file
|
@ -0,0 +1,64 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\VKAPI\Handlers;
|
||||
use openvk\Web\Models\Entities\User;
|
||||
use openvk\Web\Models\Repositories\Users as UsersRepo;
|
||||
use openvk\Web\Models\Entities\Post;
|
||||
use openvk\Web\Models\Entities\Postable;
|
||||
use openvk\Web\Models\Repositories\Posts as PostsRepo;
|
||||
|
||||
final class Wall extends VKAPIRequestHandler
|
||||
{
|
||||
function get(string $owner_id, string $domain = "", int $offset = 0, int $count = 30, int $extended = 0): object
|
||||
{
|
||||
$posts = new PostsRepo;
|
||||
|
||||
$items = [];
|
||||
|
||||
foreach ($posts->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
|
||||
];
|
||||
}
|
||||
}
|
31
VKAPI/README.textile
Normal file
31
VKAPI/README.textile
Normal file
|
@ -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 ^^
|
13
VKAPI/Structures/Conversation.php
Normal file
13
VKAPI/Structures/Conversation.php
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\VKAPI\Structures;
|
||||
|
||||
final class Conversation
|
||||
{
|
||||
public $peer;
|
||||
public $last_message_id;
|
||||
public $in_read = 1;
|
||||
public $out_read = 1;
|
||||
public $is_marked_unread = false;
|
||||
public $important = true;
|
||||
public $can_write;
|
||||
}
|
20
VKAPI/Structures/Message.php
Normal file
20
VKAPI/Structures/Message.php
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\VKAPI\Structures;
|
||||
|
||||
final class Message
|
||||
{
|
||||
public $id;
|
||||
public $user_id;
|
||||
public $from_id;
|
||||
public $date;
|
||||
public $read_state;
|
||||
public $out;
|
||||
public $title = "";
|
||||
public $body;
|
||||
public $attachments = [];
|
||||
public $fwd_messages = [];
|
||||
public $emoji;
|
||||
public $important = 1;
|
||||
public $deleted = 0;
|
||||
public $random_id = NULL;
|
||||
}
|
53
Web/Models/Entities/APIToken.php
Normal file
53
Web/Models/Entities/APIToken.php
Normal file
|
@ -0,0 +1,53 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\Web\Models\Entities;
|
||||
use openvk\Web\Models\RowModel;
|
||||
use openvk\Web\Models\Repositories\Users;
|
||||
use Nette\InvalidStateException as ISE;
|
||||
|
||||
class APIToken extends RowModel
|
||||
{
|
||||
protected $tableName = "api_tokens";
|
||||
|
||||
function getUser(): User
|
||||
{
|
||||
return (new Users)->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();
|
||||
}
|
||||
}
|
26
Web/Models/Repositories/APITokens.php
Normal file
26
Web/Models/Repositories/APITokens.php
Normal file
|
@ -0,0 +1,26 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\Web\Models\Repositories;
|
||||
use openvk\Web\Models\Entities\APIToken;
|
||||
|
||||
class APITokens extends Repository
|
||||
{
|
||||
protected $tableName = "api_tokens";
|
||||
protected $modelName = "APIToken";
|
||||
|
||||
function getByCode(string $code, bool $withRevoked = false): ?APIToken
|
||||
{
|
||||
$parts = explode("-", $code);
|
||||
$id = $parts[0];
|
||||
$secret = implode("", array_slice($parts, 1, 9));
|
||||
|
||||
$token = $this->get((int) $id);
|
||||
if(!$token)
|
||||
return NULL;
|
||||
else if($token->getSecret() !== $secret)
|
||||
return NULL;
|
||||
else if($token->isRevoked() && !$withRevoked)
|
||||
return NULL;
|
||||
|
||||
return $token;
|
||||
}
|
||||
}
|
154
Web/Presenters/VKAPIPresenter.php
Normal file
154
Web/Presenters/VKAPIPresenter.php
Normal file
|
@ -0,0 +1,154 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\Web\Presenters;
|
||||
use Chandler\Security\Authenticator;
|
||||
use Chandler\Database\DatabaseConnection as DB;
|
||||
use openvk\VKAPI\Exceptions\APIErrorException;
|
||||
use openvk\Web\Models\Entities\{User, APIToken};
|
||||
use openvk\Web\Models\Repositories\{Users, APITokens};
|
||||
|
||||
final class VKAPIPresenter extends OpenVKPresenter
|
||||
{
|
||||
private function logRequest(string $object, string $method): void
|
||||
{
|
||||
$date = date(DATE_COOKIE);
|
||||
$params = json_encode($_REQUEST);
|
||||
$log = "[$date] $object.$method called with $params";
|
||||
file_put_contents(OPENVK_ROOT . "/VKAPI/debug.log", $log, FILE_APPEND | LOCK_EX);
|
||||
}
|
||||
|
||||
private function fail(int $code, string $message, string $object, string $method): void
|
||||
{
|
||||
header("HTTP/1.1 400 Bad API Call");
|
||||
header("Content-Type: application/json");
|
||||
|
||||
$payload = [
|
||||
"error_code" => $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);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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}"
|
||||
|
|
Loading…
Reference in a new issue