{"action":"created","comment":{"url":"https://api.github.com/repos/cuberite/cuberite/pulls/comments/130781373","pull_request_review_id":53693811,"id":130781373,"diff_hunk":"@@ -0,0 +1,366 @@\n+\n+// ForgeHandshake.cpp\n+\n+// Implements Forge protocol handshaking\n+\n+#include \"Globals.h\"\n+#include \"ForgeHandshake.h\"\n+#include \"json/json.h\"\n+#include \"../Server.h\"\n+#include \"../Bindings/PluginManager.h\"\n+#include \"../ClientHandle.h\"\n+#include \"../Root.h\"\n+\n+\n+/** Discriminator byte values prefixing the FML|HS packets to determine their type. */\n+namespace Discriminator\n+{\n+\tstatic const Int8 ServerHello = 0;\n+\tstatic const Int8 ClientHello = 1;\n+\tstatic const Int8 ModList = 2;\n+\tstatic const Int8 RegistryData = 3;\n+\t// static const Int8 HandshakeReset = -2;\n+\tstatic const Int8 HandshakeAck = -1;\n+}\n+\n+/** Client handshake state phases. */\n+namespace ClientPhase\n+{\n+\tstatic const Int8 WAITINGSERVERDATA = 2;\n+\tstatic const Int8 WAITINGSERVERCOMPLETE = 3;\n+\tstatic const Int8 PENDINGCOMPLETE = 4;\n+\t// static const Int8 COMPLETE = 5;\n+}\n+\n+/** Server handshake state phases. */\n+namespace ServerPhase\n+{\n+\tstatic const Int8 WAITINGCACK = 2;\n+\tstatic const Int8 COMPLETE = 3;\n+}\n+\n+\n+\n+\n+\n+cForgeHandshake::cForgeHandshake(cClientHandle *a_Client) :\n+\tm_IsForgeClient(false),\n+\tm_Errored(false),\n+\tm_Client(a_Client)\n+{\n+}\n+\n+\n+\n+\n+\n+void cForgeHandshake::AugmentServerListPing(Json::Value & a_ResponseValue)\n+{\n+\tUInt32 ProtocolVersion = m_Client->GetProtocolVersion();\n+\tAStringMap & Mods = cRoot::Get()->GetServer()->GetRegisteredForgeMods(ProtocolVersion);\n+\n+\tif (Mods.size() == 0)\n+\t{\n+\t\treturn;\n+\t}\n+\n+\tLOGD(\"Received server ping from version: %d\", ProtocolVersion);\n+\n+\tJson::Value Modinfo;\n+\tModinfo[\"type\"] = \"FML\";\n+\n+\tJson::Value ModList(Json::arrayValue);\n+\tfor (auto & item: Mods)\n+\t{\n+\t\tJson::Value Mod;\n+\t\tMod[\"modid\"] = item.first;\n+\t\tMod[\"version\"] = item.second;\n+\t\tModList.append(Mod);\n+\t}\n+\tModinfo[\"modList\"] = ModList;\n+\n+\ta_ResponseValue[\"modinfo\"] = Modinfo;\n+}\n+\n+\n+\n+\n+\n+void cForgeHandshake::BeginForgeHandshake(const AString & a_Name, const AString & a_UUID, const Json::Value & a_Properties)\n+{\n+\tASSERT(m_IsForgeClient);\n+\n+\tm_Name = a_Name;\n+\tm_UUID = a_UUID;\n+\tm_Properties = a_Properties;\n+\n+\tstatic const std::array<AString, 5> Channels{{ \"FML|HS\", \"FML\", \"FML|MP\", \"FML\", \"FORGE\" }};\n+\tAString ChannelsString;\n+\tfor (auto & Channel: Channels)\n+\t{\n+\t\tChannelsString.append(Channel);\n+\t\tChannelsString.push_back('\\0');\n+\t}\n+\n+\tm_Client->SendPluginMessage(\"REGISTER\", ChannelsString);\n+\tSendServerHello();\n+}\n+\n+\n+\n+\n+\n+void cForgeHandshake::SendServerHello()\n+{\n+\tAString Message;\n+\t// Discriminator | Byte | Always 0 for ServerHello\n+\tMessage.push_back(Discriminator::ServerHello);\n+\t// FML protocol Version | Byte | Determined from NetworkRegistery. Currently 2.\n+\tMessage.push_back('\\2');\n+\t// Dimension TODO\n+\tMessage.push_back('\\0');\n+\tMessage.push_back('\\0');\n+\tMessage.push_back('\\0');\n+\tMessage.push_back('\\0');\n+\n+\tm_Client->SendPluginMessage(\"FML|HS\", Message);\n+}\n+\n+\n+\n+\n+\n+AStringMap cForgeHandshake::ParseModList(const char * a_Data, size_t a_Size)\n+{\n+\tAStringMap Mods;\n+\n+\tif (a_Size < 4)\n+\t{\n+\t\tLOGD(\"ParseModList invalid packet, missing length (size=\" SIZE_T_FMT \")\", a_Size);\n+\t\treturn Mods;\n+\t}\n+\n+\tcByteBuffer Buf(a_Size);\n+\tBuf.Write(a_Data, a_Size);\n+\tUInt32 NumMods;\n+\tif (!Buf.ReadVarInt32(NumMods))\n+\t{\n+\t\tLOGD(\"ParseModList failed to read mod count\");\n+\t\treturn Mods;\n+\t}\n+\n+\tfor (size_t i = 0; i < NumMods; ++i)\n+\t{\n+\t\tAString Name, Version;\n+\t\tif (!Buf.ReadVarUTF8String(Name))\n+\t\t{\n+\t\t\tLOGD(\"ParseModList failed to read mod name at i=\" SIZE_T_FMT, i);\n+\t\t\tbreak;\n+\t\t}\n+\t\tif (!Buf.ReadVarUTF8String(Version))\n+\t\t{\n+\t\t\tLOGD(\"ParseModList failed to read mod version at i=\" SIZE_T_FMT, i);\n+\t\t\tbreak;\n+\t\t}\n+\t\tMods.insert({Name, Version});\n+\t}\n+\n+\treturn Mods;\n+}\n+\n+\n+\n+\n+void cForgeHandshake::HandleClientHello(cClientHandle * a_Client, const char * a_Data, size_t a_Size)\n+{\n+\tif (a_Size == 2)\n+\t{\n+\t\tint FmlProtocolVersion = a_Data[1];\n+\t\tLOGD(\"Received ClientHello with FML protocol version %d\", FmlProtocolVersion);\n+\t\tif (FmlProtocolVersion != 2)\n+\t\t{\n+\t\t\tLOG(\"Unsupported FML client protocol version received in ClientHello: %d\", FmlProtocolVersion);\n+\t\t\tSetError();\n+\t\t}\n+\t}\n+\telse\n+\t{\n+\t\tLOG(\"Unexpectedly short ClientHello received\");\n+\t\tSetError();\n+\t}\n+}\n+\n+\n+\n+\n+void cForgeHandshake::HandleModList(cClientHandle * a_Client, const char * a_Data, size_t a_Size)\n+{\n+\tLOGD(\"Received ModList\");\n+\n+\tAStringMap ClientMods = ParseModList(a_Data + 1, a_Size - 1);\n+\tAString ClientModsString;\n+\tfor (auto & item: ClientMods)\n+\t{\n+\t\tClientModsString.append(item.first);\n+\t\tClientModsString.append(\"@\");\n+\t\tClientModsString.append(item.second);\n+\t\tClientModsString.append(\", \");\n+\t}\n+\n+\tLOG(\"Client connected with \" SIZE_T_FMT \" mods: %s\", ClientMods.size(), ClientModsString.c_str());\n+\n+\tm_Client->m_ForgeMods = ClientMods;\n+\n+\t// Let the plugins know about this event, they may refuse the player:\n+\tif (cRoot::Get()->GetPluginManager()->CallHookLoginForge(*a_Client, ClientMods))\n+\t{\n+\t\tLOG(\"Modded client refused by plugin\");\n+\t\tSetError();\n+\t\treturn;\n+\t}\n+\n+\t// Send server ModList\n+\n+\t// TODO: max size?\n+\tcByteBuffer Buf(1024);\n+\n+\t// Send server-side Forge mods registered by plugins\n+\tauto &ServerMods = m_Client->GetForgeMods();\n+\n+\tsize_t ModCount = ServerMods.size();\n+\n+\tBuf.WriteBEInt8(Discriminator::ModList);\n+\tBuf.WriteVarInt32(static_cast<UInt32>(ModCount));\n+\tfor (auto & item: ServerMods)\n+\t{\n+\t\tconst AString & name = item.first;\n+\t\tconst AString & version = item.second;\n+\t\tBuf.WriteVarUTF8String(name);\n+\t\tBuf.WriteVarUTF8String(version);\n+\t}\n+\tAString ServerModList;\n+\tBuf.ReadAll(ServerModList);\n+\n+\tm_Client->SendPluginMessage(\"FML|HS\", ServerModList);\n+}\n+\n+\n+\n+\n+void cForgeHandshake::HandleHandshakeAck(cClientHandle * a_Client, const char * a_Data, size_t a_Size)\n+{\n+\tif (a_Size != 2)\n+\t{\n+\t\tLOG(\"Unexpected HandshakeAck packet length: \" SIZE_T_FMT \"\", a_Size);\n+\t\tSetError();\n+\t\treturn;\n+\t}\n+\n+\tint Phase = a_Data[1];\n+\tLOGD(\"Received client HandshakeAck with phase=%d\", Phase);\n+\n+\tswitch (Phase)\n+\t{\n+\t\tcase ClientPhase::WAITINGSERVERDATA:\n+\t\t{\n+\t\t\tcByteBuffer Buf(1024);\n+\t\t\tBuf.WriteBEInt8(Discriminator::RegistryData);\n+\n+\t\t\t// TODO: send real registry data\n+\t\t\tbool HasMore = false;\n+\t\t\tAString RegistryName = \"potions\";\n+\t\t\tUInt32 NumIDs = 0;\n+\t\t\tUInt32 NumSubstitutions = 0;\n+\t\t\tUInt32 NumDummies = 0;\n+\n+\t\t\tBuf.WriteBool(HasMore);\n+\t\t\tBuf.WriteVarUTF8String(RegistryName);\n+\t\t\tBuf.WriteVarInt32(NumIDs);\n+\t\t\tBuf.WriteVarInt32(NumSubstitutions);\n+\t\t\tBuf.WriteVarInt32(NumDummies);\n+\n+\t\t\tAString RegistryData;\n+\t\t\tBuf.ReadAll(RegistryData);\n+\t\t\tm_Client->SendPluginMessage(\"FML|HS\", RegistryData);\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tcase ClientPhase::WAITINGSERVERCOMPLETE:\n+\t\t{\n+\t\t\tLOGD(\"Client finished receiving registry data; acknowledging\");\n+\n+\t\t\tAString Ack;\n+\t\t\tAck.push_back(Discriminator::HandshakeAck);\n+\t\t\tAck.push_back(ServerPhase::WAITINGCACK);\n+\t\t\tm_Client->SendPluginMessage(\"FML|HS\", Ack);\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tcase ClientPhase::PENDINGCOMPLETE:\n+\t\t{\n+\t\t\tLOGD(\"Client is pending completion; sending complete ack\");\n+\n+\t\t\tAString Ack;\n+\t\t\tAck.push_back(Discriminator::HandshakeAck);\n+\t\t\tAck.push_back(ServerPhase::COMPLETE);\n+\t\t\tm_Client->SendPluginMessage(\"FML|HS\", Ack);\n+\n+\t\t\t// Now finish logging in\n+\t\t\tm_Client->FinishAuthenticate(m_Name, m_UUID, m_Properties);\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+}\n+\n+\n+\n+\n+\n+void cForgeHandshake::DataReceived(cClientHandle * a_Client, const char * a_Data, size_t a_Size)\n+{\n+\tif (!m_IsForgeClient)\n+\t{\n+\t\tLOG(\"Received unexpected Forge data from non-Forge client (\" SIZE_T_FMT \" bytes)\", a_Size);\n+\t\treturn;\n+\t}\n+\tLOGD(\"Received Forge data: \" SIZE_T_FMT \" bytes: %s\", a_Size, a_Data);\n+\n+\tif (a_Size <= 1)\n+\t{\n+\t\tLOG(\"Received unexpectedly short Forge data (\" SIZE_T_FMT \" bytes)\", a_Size);\n+\t\treturn;\n+\t}\n+\n+\tint Discriminator = a_Data[0];\n+\n+\tswitch (Discriminator)\n+\t{\n+\t\tcase Discriminator::ClientHello:\n+\t\t\tHandleClientHello(a_Client, a_Data, a_Size);\n+\t\t\tbreak;\n+\n+\t\tcase Discriminator::ModList:\n+\t\t\tHandleModList(a_Client, a_Data, a_Size);\n+\t\t\tbreak;\n+\n+\t\tcase Discriminator::HandshakeAck:\n+\t\t\tHandleHandshakeAck(a_Client, a_Data, a_Size);\n+\t\t\tbreak;\n+\n+\t\tdefault:\n+\t\t{\n+\t\t\tLOG(\"Unexpected Forge packet %d received\", Discriminator);\n+\t\t\tSetError();","path":"src/Protocol/ForgeHandshake.cpp","position":357,"original_position":353,"commit_id":"1a7f58777d4ed05f7213280f3416fdaa2920abdb","original_commit_id":"a51b6066806687a26085dd3b8563ba71e348ac15","user":{"login":"satoshinm","id":26856618,"avatar_url":"https://avatars2.githubusercontent.com/u/26856618?v=4","gravatar_id":"","url":"https://api.github.com/users/satoshinm","html_url":"https://github.com/satoshinm","followers_url":"https://api.github.com/users/satoshinm/followers","following_url":"https://api.github.com/users/satoshinm/following{/other_user}","gists_url":"https://api.github.com/users/satoshinm/gists{/gist_id}","starred_url":"https://api.github.com/users/satoshinm/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/satoshinm/subscriptions","organizations_url":"https://api.github.com/users/satoshinm/orgs","repos_url":"https://api.github.com/users/satoshinm/repos","events_url":"https://api.github.com/users/satoshinm/events{/privacy}","received_events_url":"https://api.github.com/users/satoshinm/received_events","type":"User","site_admin":false},"body":"Will need to see about how to wrap the variadic arguments of LOGD(...) in SetError","created_at":"2017-08-02T04:06:45Z","updated_at":"2017-08-02T04:06:45Z","html_url":"https://github.com/cuberite/cuberite/pull/3869#discussion_r130781373","pull_request_url":"https://api.github.com/repos/cuberite/cuberite/pulls/3869","_links":{"self":{"href":"https://api.github.com/repos/cuberite/cuberite/pulls/comments/130781373"},"html":{"href":"https://github.com/cuberite/cuberite/pull/3869#discussion_r130781373"},"pull_request":{"href":"https://api.github.com/repos/cuberite/cuberite/pulls/3869"}}},"pull_request":{"url":"https://api.github.com/repos/cuberite/cuberite/pulls/3869","id":131924501,"html_url":"https://github.com/cuberite/cuberite/pull/3869","diff_url":"https://github.com/cuberite/cuberite/pull/3869.diff","patch_url":"https://github.com/cuberite/cuberite/pull/3869.patch","issue_url":"https://api.github.com/repos/cuberite/cuberite/issues/3869","number":3869,"state":"open","locked":false,"title":"Implement Forge protocol handshake support","user":{"login":"satoshinm","id":26856618,"avatar_url":"https://avatars2.githubusercontent.com/u/26856618?v=4","gravatar_id":"","url":"https://api.github.com/users/satoshinm","html_url":"https://github.com/satoshinm","followers_url":"https://api.github.com/users/satoshinm/followers","following_url":"https://api.github.com/users/satoshinm/following{/other_user}","gists_url":"https://api.github.com/users/satoshinm/gists{/gist_id}","starred_url":"https://api.github.com/users/satoshinm/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/satoshinm/subscriptions","organizations_url":"https://api.github.com/users/satoshinm/orgs","repos_url":"https://api.github.com/users/satoshinm/repos","events_url":"https://api.github.com/users/satoshinm/events{/privacy}","received_events_url":"https://api.github.com/users/satoshinm/received_events","type":"User","site_admin":false},"body":"Adds initial support for the Forge handshake protocol, as documented on http://wiki.vg/Minecraft_Forge_Handshake\r\n\r\nIncludes a Lua API to allow plugins to register as server-side Forge mods, which are included in the server list ping and ModList packet in the handshake exchanged with Forge clients.\r\n\r\nNo support for adding new game content in this pull request, but this lays the groundwork, and even without it currently allows server plugins to be written to hook the login handshake to require or deny specific client mods (or Forge itself). Example/test plugin: https://github.com/satoshinm/SampleForgeMod.lua/blob/master/main.lua","created_at":"2017-07-23T07:00:49Z","updated_at":"2017-08-02T04:06:45Z","closed_at":null,"merged_at":null,"merge_commit_sha":"bef9d594b3088351ef6013e30ce1618fab2f2e93","assignee":null,"assignees":[],"requested_reviewers":[],"milestone":null,"commits_url":"https://api.github.com/repos/cuberite/cuberite/pulls/3869/commits","review_comments_url":"https://api.github.com/repos/cuberite/cuberite/pulls/3869/comments","review_comment_url":"https://api.github.com/repos/cuberite/cuberite/pulls/comments{/number}","comments_url":"https://api.github.com/repos/cuberite/cuberite/issues/3869/comments","statuses_url":"https://api.github.com/repos/cuberite/cuberite/statuses/1a7f58777d4ed05f7213280f3416fdaa2920abdb","head":{"label":"satoshinm:forge","ref":"forge","sha":"1a7f58777d4ed05f7213280f3416fdaa2920abdb","user":{"login":"satoshinm","id":26856618,"avatar_url":"https://avatars2.githubusercontent.com/u/26856618?v=4","gravatar_id":"","url":"https://api.github.com/users/satoshinm","html_url":"https://github.com/satoshinm","followers_url":"https://api.github.com/users/satoshinm/followers","following_url":"https://api.github.com/users/satoshinm/following{/other_user}","gists_url":"https://api.github.com/users/satoshinm/gists{/gist_id}","starred_url":"https://api.github.com/users/satoshinm/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/satoshinm/subscriptions","organizations_url":"https://api.github.com/users/satoshinm/orgs","repos_url":"https://api.github.com/users/satoshinm/repos","events_url":"https://api.github.com/users/satoshinm/events{/privacy}","received_events_url":"https://api.github.com/users/satoshinm/received_events","type":"User","site_admin":false},"repo":{"id":96649492,"name":"cuberite","full_name":"satoshinm/cuberite","owner":{"login":"satoshinm","id":26856618,"avatar_url":"https://avatars2.githubusercontent.com/u/26856618?v=4","gravatar_id":"","url":"https://api.github.com/users/satoshinm","html_url":"https://github.com/satoshinm","followers_url":"https://api.github.com/users/satoshinm/followers","following_url":"https://api.github.com/users/satoshinm/following{/other_user}","gists_url":"https://api.github.com/users/satoshinm/gists{/gist_id}","starred_url":"https://api.github.com/users/satoshinm/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/satoshinm/subscriptions","organizations_url":"https://api.github.com/users/satoshinm/orgs","repos_url":"https://api.github.com/users/satoshinm/repos","events_url":"https://api.github.com/users/satoshinm/events{/privacy}","received_events_url":"https://api.github.com/users/satoshinm/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/satoshinm/cuberite","description":"A lightweight, fast and extensible game server for Minecraft","fork":true,"url":"https://api.github.com/repos/satoshinm/cuberite","forks_url":"https://api.github.com/repos/satoshinm/cuberite/forks","keys_url":"https://api.github.com/repos/satoshinm/cuberite/keys{/key_id}","collaborators_url":"https://api.github.com/repos/satoshinm/cuberite/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/satoshinm/cuberite/teams","hooks_url":"https://api.github.com/repos/satoshinm/cuberite/hooks","issue_events_url":"https://api.github.com/repos/satoshinm/cuberite/issues/events{/number}","events_url":"https://api.github.com/repos/satoshinm/cuberite/events","assignees_url":"https://api.github.com/repos/satoshinm/cuberite/assignees{/user}","branches_url":"https://api.github.com/repos/satoshinm/cuberite/branches{/branch}","tags_url":"https://api.github.com/repos/satoshinm/cuberite/tags","blobs_url":"https://api.github.com/repos/satoshinm/cuberite/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/satoshinm/cuberite/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/satoshinm/cuberite/git/refs{/sha}","trees_url":"https://api.github.com/repos/satoshinm/cuberite/git/trees{/sha}","statuses_url":"https://api.github.com/repos/satoshinm/cuberite/statuses/{sha}","languages_url":"https://api.github.com/repos/satoshinm/cuberite/languages","stargazers_url":"https://api.github.com/repos/satoshinm/cuberite/stargazers","contributors_url":"https://api.github.com/repos/satoshinm/cuberite/contributors","subscribers_url":"https://api.github.com/repos/satoshinm/cuberite/subscribers","subscription_url":"https://api.github.com/repos/satoshinm/cuberite/subscription","commits_url":"https://api.github.com/repos/satoshinm/cuberite/commits{/sha}","git_commits_url":"https://api.github.com/repos/satoshinm/cuberite/git/commits{/sha}","comments_url":"https://api.github.com/repos/satoshinm/cuberite/comments{/number}","issue_comment_url":"https://api.github.com/repos/satoshinm/cuberite/issues/comments{/number}","contents_url":"https://api.github.com/repos/satoshinm/cuberite/contents/{+path}","compare_url":"https://api.github.com/repos/satoshinm/cuberite/compare/{base}...{head}","merges_url":"https://api.github.com/repos/satoshinm/cuberite/merges","archive_url":"https://api.github.com/repos/satoshinm/cuberite/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/satoshinm/cuberite/downloads","issues_url":"https://api.github.com/repos/satoshinm/cuberite/issues{/number}","pulls_url":"https://api.github.com/repos/satoshinm/cuberite/pulls{/number}","milestones_url":"https://api.github.com/repos/satoshinm/cuberite/milestones{/number}","notifications_url":"https://api.github.com/repos/satoshinm/cuberite/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/satoshinm/cuberite/labels{/name}","releases_url":"https://api.github.com/repos/satoshinm/cuberite/releases{/id}","deployments_url":"https://api.github.com/repos/satoshinm/cuberite/deployments","created_at":"2017-07-08T23:25:28Z","updated_at":"2017-07-09T01:50:05Z","pushed_at":"2017-08-02T03:58:09Z","git_url":"git://github.com/satoshinm/cuberite.git","ssh_url":"git@github.com:satoshinm/cuberite.git","clone_url":"https://github.com/satoshinm/cuberite.git","svn_url":"https://github.com/satoshinm/cuberite","homepage":"http://cuberite.org","size":82312,"stargazers_count":0,"watchers_count":0,"language":"C++","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":5,"forks":0,"open_issues":5,"watchers":0,"default_branch":"master"}},"base":{"label":"cuberite:master","ref":"master","sha":"0d0323608df044aebfc6d0f3ac2ca5e07512facc","user":{"login":"cuberite","id":8973961,"avatar_url":"https://avatars0.githubusercontent.com/u/8973961?v=4","gravatar_id":"","url":"https://api.github.com/users/cuberite","html_url":"https://github.com/cuberite","followers_url":"https://api.github.com/users/cuberite/followers","following_url":"https://api.github.com/users/cuberite/following{/other_user}","gists_url":"https://api.github.com/users/cuberite/gists{/gist_id}","starred_url":"https://api.github.com/users/cuberite/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/cuberite/subscriptions","organizations_url":"https://api.github.com/users/cuberite/orgs","repos_url":"https://api.github.com/users/cuberite/repos","events_url":"https://api.github.com/users/cuberite/events{/privacy}","received_events_url":"https://api.github.com/users/cuberite/received_events","type":"Organization","site_admin":false},"repo":{"id":11693709,"name":"cuberite","full_name":"cuberite/cuberite","owner":{"login":"cuberite","id":8973961,"avatar_url":"https://avatars0.githubusercontent.com/u/8973961?v=4","gravatar_id":"","url":"https://api.github.com/users/cuberite","html_url":"https://github.com/cuberite","followers_url":"https://api.github.com/users/cuberite/followers","following_url":"https://api.github.com/users/cuberite/following{/other_user}","gists_url":"https://api.github.com/users/cuberite/gists{/gist_id}","starred_url":"https://api.github.com/users/cuberite/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/cuberite/subscriptions","organizations_url":"https://api.github.com/users/cuberite/orgs","repos_url":"https://api.github.com/users/cuberite/repos","events_url":"https://api.github.com/users/cuberite/events{/privacy}","received_events_url":"https://api.github.com/users/cuberite/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/cuberite/cuberite","description":"A lightweight, fast and extensible game server for Minecraft","fork":false,"url":"https://api.github.com/repos/cuberite/cuberite","forks_url":"https://api.github.com/repos/cuberite/cuberite/forks","keys_url":"https://api.github.com/repos/cuberite/cuberite/keys{/key_id}","collaborators_url":"https://api.github.com/repos/cuberite/cuberite/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/cuberite/cuberite/teams","hooks_url":"https://api.github.com/repos/cuberite/cuberite/hooks","issue_events_url":"https://api.github.com/repos/cuberite/cuberite/issues/events{/number}","events_url":"https://api.github.com/repos/cuberite/cuberite/events","assignees_url":"https://api.github.com/repos/cuberite/cuberite/assignees{/user}","branches_url":"https://api.github.com/repos/cuberite/cuberite/branches{/branch}","tags_url":"https://api.github.com/repos/cuberite/cuberite/tags","blobs_url":"https://api.github.com/repos/cuberite/cuberite/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/cuberite/cuberite/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/cuberite/cuberite/git/refs{/sha}","trees_url":"https://api.github.com/repos/cuberite/cuberite/git/trees{/sha}","statuses_url":"https://api.github.com/repos/cuberite/cuberite/statuses/{sha}","languages_url":"https://api.github.com/repos/cuberite/cuberite/languages","stargazers_url":"https://api.github.com/repos/cuberite/cuberite/stargazers","contributors_url":"https://api.github.com/repos/cuberite/cuberite/contributors","subscribers_url":"https://api.github.com/repos/cuberite/cuberite/subscribers","subscription_url":"https://api.github.com/repos/cuberite/cuberite/subscription","commits_url":"https://api.github.com/repos/cuberite/cuberite/commits{/sha}","git_commits_url":"https://api.github.com/repos/cuberite/cuberite/git/commits{/sha}","comments_url":"https://api.github.com/repos/cuberite/cuberite/comments{/number}","issue_comment_url":"https://api.github.com/repos/cuberite/cuberite/issues/comments{/number}","contents_url":"https://api.github.com/repos/cuberite/cuberite/contents/{+path}","compare_url":"https://api.github.com/repos/cuberite/cuberite/compare/{base}...{head}","merges_url":"https://api.github.com/repos/cuberite/cuberite/merges","archive_url":"https://api.github.com/repos/cuberite/cuberite/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/cuberite/cuberite/downloads","issues_url":"https://api.github.com/repos/cuberite/cuberite/issues{/number}","pulls_url":"https://api.github.com/repos/cuberite/cuberite/pulls{/number}","milestones_url":"https://api.github.com/repos/cuberite/cuberite/milestones{/number}","notifications_url":"https://api.github.com/repos/cuberite/cuberite/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/cuberite/cuberite/labels{/name}","releases_url":"https://api.github.com/repos/cuberite/cuberite/releases{/id}","deployments_url":"https://api.github.com/repos/cuberite/cuberite/deployments","created_at":"2013-07-26T19:56:59Z","updated_at":"2017-08-02T02:05:29Z","pushed_at":"2017-08-02T03:58:10Z","git_url":"git://github.com/cuberite/cuberite.git","ssh_url":"git@github.com:cuberite/cuberite.git","clone_url":"https://github.com/cuberite/cuberite.git","svn_url":"https://github.com/cuberite/cuberite","homepage":"http://cuberite.org","size":80768,"stargazers_count":1904,"watchers_count":1904,"language":"C++","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":407,"mirror_url":null,"open_issues_count":550,"forks":407,"open_issues":550,"watchers":1904,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/cuberite/cuberite/pulls/3869"},"html":{"href":"https://github.com/cuberite/cuberite/pull/3869"},"issue":{"href":"https://api.github.com/repos/cuberite/cuberite/issues/3869"},"comments":{"href":"https://api.github.com/repos/cuberite/cuberite/issues/3869/comments"},"review_comments":{"href":"https://api.github.com/repos/cuberite/cuberite/pulls/3869/comments"},"review_comment":{"href":"https://api.github.com/repos/cuberite/cuberite/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/cuberite/cuberite/pulls/3869/commits"},"statuses":{"href":"https://api.github.com/repos/cuberite/cuberite/statuses/1a7f58777d4ed05f7213280f3416fdaa2920abdb"}}}}
{"actor":{"display_login":"satoshinm"}}