├── .gitignore ├── LICENSE ├── README.md ├── WebContent ├── admin │ ├── index.html │ └── index.js ├── cardcast │ ├── index.css │ ├── index.html │ └── index.js ├── chat │ ├── index.css │ ├── index.html │ └── index.js ├── css │ ├── cards.css │ ├── game.css │ ├── images │ │ ├── favicon.ico │ │ ├── icon-128x128.png │ │ ├── icon-144x144.png │ │ ├── icon-152x152.png │ │ ├── icon-16x16.png │ │ ├── icon-192x192.png │ │ ├── icon-32x32.png │ │ ├── icon-384x384.png │ │ ├── icon-512x512.png │ │ ├── icon-72x72.png │ │ ├── icon-96x96.png │ │ └── no-profile.svg │ ├── pyx-reloaded.css │ └── theming.css ├── game.html ├── game_options │ ├── dialog.html │ └── dialog.js ├── games │ ├── index.css │ ├── index.html │ └── index.js ├── index.css ├── index.html ├── index.js ├── js │ ├── bottom_sheet.js │ ├── cardcast.helper.js │ ├── communication.helper.js │ ├── events.helper.js │ ├── game.js │ ├── game.other_stuff.js │ ├── tabs.helper.js │ ├── theming.js │ └── wheel.js ├── kicked │ └── index.html ├── manifest.json ├── sitemap.xml └── users │ ├── index.html │ └── index.js ├── dist ├── start.bat ├── start.sh ├── update.bat └── update.sh ├── icons ├── icon.ai └── icon.png ├── pom.xml ├── preferences.json.default ├── pyx.sqlite ├── screenshots ├── screen1.png ├── screen2.png ├── screen3.png ├── screen4.png ├── screen5.png ├── screen6.png ├── screen7.png ├── screen8.png └── screen9.png ├── server.sqlite.default └── src ├── assembly ├── jar.xml └── zip.xml ├── main └── java │ └── com │ └── gianlu │ └── pyxreloaded │ ├── Consts.java │ ├── Server.java │ ├── Updater.java │ ├── Utils.java │ ├── cardcast │ ├── CardcastBlackCard.java │ ├── CardcastDeck.java │ ├── CardcastService.java │ ├── CardcastWhiteCard.java │ └── FailedLoadingSomeCardcastDecks.java │ ├── cards │ ├── BlackCard.java │ ├── BlackDeck.java │ ├── BlankWhiteCard.java │ ├── CardSet.java │ ├── PyxBlackCard.java │ ├── PyxCardSet.java │ ├── PyxWhiteCard.java │ ├── WhiteCard.java │ └── WhiteDeck.java │ ├── data │ ├── EventWrapper.java │ ├── JsonWrapper.java │ ├── QueuedMessage.java │ ├── User.java │ └── accounts │ │ ├── FacebookAccount.java │ │ ├── GithubAccount.java │ │ ├── GoogleAccount.java │ │ ├── PasswordAccount.java │ │ ├── TwitterAccount.java │ │ └── UserAccount.java │ ├── game │ ├── Game.java │ ├── GameOptions.java │ ├── OutOfCardsException.java │ ├── Player.java │ ├── PlayerPlayedCardsTracker.java │ └── SuggestedGameOptions.java │ ├── handlers │ ├── BanHandler.java │ ├── BaseHandler.java │ ├── CardcastAddCardsetHandler.java │ ├── CardcastListCardsetsHandler.java │ ├── CardcastRemoveCardsetHandler.java │ ├── ChangeGameOptionsHandler.java │ ├── ChatHandler.java │ ├── CreateAccountHandler.java │ ├── CreateGameHandler.java │ ├── DislikeGameHandler.java │ ├── FirstLoadHandler.java │ ├── GameChatHandler.java │ ├── GameHandler.java │ ├── GameListHandler.java │ ├── GameOptionsSuggestionDecisionHandler.java │ ├── GameWithPlayerHandler.java │ ├── GetCardsHandler.java │ ├── GetGameInfoHandler.java │ ├── GetMeHandler.java │ ├── GetSuggestedGameOptionsHandler.java │ ├── GetUserPreferencesHandler.java │ ├── JoinGameHandler.java │ ├── JudgeSelectHandler.java │ ├── KickHandler.java │ ├── LeaveGameHandler.java │ ├── LikeGameHandler.java │ ├── LogoutHandler.java │ ├── NamesHandler.java │ ├── PlayCardHandler.java │ ├── PongHandler.java │ ├── PrepareShutdownHandler.java │ ├── RegisterHandler.java │ ├── SetUserPreferencesHandler.java │ ├── SpectateGameHandler.java │ ├── StartGameHandler.java │ └── StopGameHandler.java │ ├── paths │ ├── AjaxPath.java │ ├── EventsPath.java │ ├── GithubCallbackPath.java │ ├── TwitterCallbackPath.java │ ├── TwitterStartAuthFlowPath.java │ ├── VerifyEmailPath.java │ ├── VersionPath.java │ └── WebManifestPath.java │ ├── server │ ├── Annotations.java │ ├── BaseCahHandler.java │ ├── BaseJsonHandler.java │ ├── CustomResourceHandler.java │ ├── HttpsRedirect.java │ ├── Parameters.java │ └── Provider.java │ ├── singletons │ ├── BanList.java │ ├── ConnectedUsers.java │ ├── Emails.java │ ├── GamesManager.java │ ├── Handlers.java │ ├── LoadedCards.java │ ├── Preferences.java │ ├── PreparingShutdown.java │ ├── Providers.java │ ├── ServerDatabase.java │ ├── Sessions.java │ ├── SocialLogin.java │ └── UsersWithAccount.java │ ├── socials │ ├── facebook │ │ ├── FacebookAuthHelper.java │ │ ├── FacebookEmailNotVerifiedException.java │ │ ├── FacebookOAuthException.java │ │ ├── FacebookProfileInfo.java │ │ └── FacebookToken.java │ ├── github │ │ ├── GithubAuthHelper.java │ │ ├── GithubEmails.java │ │ ├── GithubException.java │ │ └── GithubProfileInfo.java │ ├── google │ │ ├── GoogleApacheHttpRequest.java │ │ ├── GoogleApacheHttpResponse.java │ │ └── GoogleApacheHttpTransport.java │ └── twitter │ │ ├── TwitterAuthHelper.java │ │ ├── TwitterEmailNotVerifiedException.java │ │ └── TwitterProfileInfo.java │ └── task │ ├── BroadcastGameListUpdateTask.java │ ├── SafeTimerTask.java │ └── UserPingTask.java └── test └── java └── com └── gianlu └── pyxreloaded └── ConstantsTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .update/ 3 | .idea/ 4 | .backup/ 5 | WebContent/node_modules/ 6 | WebContent/.backup/ 7 | .old/ 8 | preferences.json 9 | server.sqlite 10 | -------------------------------------------------------------------------------- /WebContent/admin/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Admin panel - PYX-Reloaded 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 |
20 |
21 |
22 | Admin panel - PYX Reloaded 23 |
24 |
25 |
26 | 27 |
28 | 29 |
30 |
31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /WebContent/admin/index.js: -------------------------------------------------------------------------------- 1 | class AdminPanel { 2 | constructor() { 3 | this.root = $('main'); 4 | this.prepareShutdown = this.root.find('#prepareShutdown'); 5 | this.prepareShutdown.on('click', () => { 6 | Requester.request("ps", {}, () => { 7 | Notifier.timeout(Notifier.SUCCESS, "Server is preparing for shutdown."); 8 | }, (error) => { 9 | Notifier.error("Failed preparing for shutdown.", error); 10 | }) 11 | }); 12 | } 13 | } 14 | 15 | function load() { 16 | Requester.request("gme", {}, (data) => { 17 | if (data.a === undefined || !data.a.ia) { 18 | Notifier.debug("Not admin: " + JSON.stringify(data), true); 19 | window.location = "/"; 20 | } 21 | 22 | Notifier.debug(data); 23 | new AdminPanel(); 24 | }, (error) => { 25 | Notifier.error("Failed loading!", error); 26 | }) 27 | } -------------------------------------------------------------------------------- /WebContent/cardcast/index.css: -------------------------------------------------------------------------------- 1 | /*noinspection CssUnresolvedCustomProperty*/ 2 | .br-theme-fontawesome-stars-o .br-widget a.br-active:after { 3 | color: var(--mdc-theme-secondary) !important; 4 | } 5 | 6 | /*noinspection CssUnresolvedCustomProperty*/ 7 | .br-theme-fontawesome-stars-o .br-widget a.br-selected:after { 8 | color: var(--mdc-theme-secondary) !important; 9 | } 10 | 11 | /*noinspection CssUnresolvedCustomProperty*/ 12 | .br-theme-fontawesome-stars-o .br-widget a.br-fractional:after { 13 | color: var(--mdc-theme-secondary) !important; 14 | } -------------------------------------------------------------------------------- /WebContent/chat/index.css: -------------------------------------------------------------------------------- 1 | main { 2 | height: 100%; 3 | display: flex; 4 | flex-direction: column; 5 | } 6 | 7 | main > .mdc-list { 8 | flex-grow: 1; 9 | overflow-y: auto; 10 | } 11 | 12 | main > .mdc-text-field { 13 | flex-shrink: 0; 14 | flex-grow: 0; 15 | width: 98%; 16 | margin: 1%; 17 | } -------------------------------------------------------------------------------- /WebContent/chat/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Global chat - PYX-Reloaded 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 |
22 |
23 | Global chat - PYX Reloaded 24 |
25 |
26 |
27 | 28 |
29 |

There are no messages.

30 | 31 |
33 | 34 | 35 | send 36 |
37 | 38 | 39 | 40 |
41 |
42 |
43 |
44 |
45 | 46 |
47 |
  • 48 | 49 | 50 | {{Sender}} 51 | {{Message}} 52 | 53 |
  • 54 |
    55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /WebContent/chat/index.js: -------------------------------------------------------------------------------- 1 | class ChatManager { 2 | constructor() { 3 | this._main = $('main'); 4 | this.chat = new List(this._main[0], { 5 | item: 'chatMessageTemplate', 6 | valueNames: ['_text', '_nick', {attr: 'src', name: '_img'}] 7 | }); 8 | this.chat.clear(); 9 | 10 | this.noMessages = this._main.find('.message'); 11 | 12 | this._chatMessage = this._main.find('#chatMessage'); 13 | this._chatMessage.on('keydown', (ev) => this._handleSendChatMessage(ev)); 14 | this._chatMessage.parent().find('.mdc-text-field__icon').on('click', () => this._handleSendChatMessage(undefined)); 15 | 16 | eventsReceiver.register("GLOBAL_CHAT", (data) => { 17 | Notifier.debug(data, false); 18 | if (data.E === "C" && data.gid === undefined) { 19 | this._handleChatMessage(data); 20 | } 21 | }); 22 | } 23 | 24 | /** 25 | * @param {String} data.m - Message 26 | * @param {String} data.f - Sender 27 | * @param {String} data.p - Profile picture 28 | * @private 29 | */ 30 | _handleChatMessage(data) { 31 | this.chat.add({ 32 | '_nick': data.f, 33 | '_img': data.p === null || data.p === undefined ? "/css/images/no-profile.svg" : data.p, 34 | '_text': data.m 35 | }); 36 | 37 | this.noMessages.hide(); 38 | this.chat.list.scrollTop = this.chat.list.scrollHeight 39 | } 40 | 41 | _handleSendChatMessage(ev) { 42 | if (ev !== undefined && ev.keyCode !== 13) return; 43 | 44 | const msg = this._chatMessage.val(); 45 | if (msg.length === 0) return; 46 | 47 | this.sendGameChatMessage(msg); 48 | } 49 | 50 | sendGameChatMessage(msg) { 51 | Requester.request("c", {"m": msg}, () => { 52 | this._chatMessage.next().removeClass("mdc-floating-label--float-above"); 53 | this._chatMessage.val(""); 54 | this._chatMessage.blur(); 55 | }, (error) => { 56 | switch (error.ec) { 57 | case "tf": 58 | Notifier.timeout(Notifier.WARN, "You are chatting too fast. Calm down."); 59 | break; 60 | case "anv": 61 | Notifier.error("You must be registered (and have verified your email) to send messages in the global chat.", error); 62 | break; 63 | default: 64 | Notifier.error("Failed sending the message!", error); 65 | break; 66 | } 67 | }); 68 | } 69 | } -------------------------------------------------------------------------------- /WebContent/css/game.css: -------------------------------------------------------------------------------- 1 | #gameLayout { 2 | width: 100%; 3 | } 4 | 5 | #gameLayout > .__inner { 6 | width: 100%; 7 | display: flex; 8 | flex-flow: row; 9 | } 10 | 11 | #gameLayout > .message { 12 | margin-top: 48px; 13 | } 14 | 15 | #whiteCards { 16 | flex-grow: 1; 17 | } 18 | 19 | #hand > .list > .mdc-card.pyx-card { 20 | flex-shrink: 0; 21 | } 22 | 23 | #drawer > .mdc-drawer__drawer { 24 | padding: 16px; 25 | display: flex; 26 | flex-direction: column; 27 | } 28 | 29 | #scoreboard { 30 | flex-shrink: 1; 31 | flex-basis: auto; 32 | } 33 | 34 | #scoreboard > .list { 35 | overflow-y: auto; 36 | max-height: 25vh; 37 | } 38 | 39 | #chat { 40 | display: flex; 41 | flex-grow: 1; 42 | flex-direction: column; 43 | } 44 | 45 | #chat > .list { 46 | flex-grow: 1; 47 | overflow-wrap: break-word; 48 | padding-top: 0.25rem; 49 | overflow-y: auto; 50 | } 51 | 52 | #chat > .mdc-text-field { 53 | width: 100%; 54 | flex-shrink: 0; 55 | } 56 | 57 | .error::before { 58 | font-family: 'Material Icons', serif; 59 | font-weight: normal; 60 | font-style: normal; 61 | font-size: 24px; /* Preferred icon size */ 62 | display: inline-block; 63 | line-height: 1; 64 | text-transform: none; 65 | letter-spacing: normal; 66 | word-wrap: normal; 67 | white-space: nowrap; 68 | direction: ltr; 69 | 70 | /* Support for all WebKit browsers. */ 71 | -webkit-font-smoothing: antialiased; 72 | /* Support for Safari and Chrome. */ 73 | text-rendering: optimizeLegibility; 74 | 75 | /* Support for Firefox. */ 76 | -moz-osx-font-smoothing: grayscale; 77 | 78 | /* Support for IE. */ 79 | font-feature-settings: 'liga'; 80 | 81 | content: 'error'; 82 | vertical-align: middle; 83 | padding-right: 8px; 84 | color: #D32F2F; /* Red 700 */ 85 | } -------------------------------------------------------------------------------- /WebContent/css/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devgianlu/PYX-Reloaded/22b9a80af86149d53c6f47b33f5a129d9ffa4ea0/WebContent/css/images/favicon.ico -------------------------------------------------------------------------------- /WebContent/css/images/icon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devgianlu/PYX-Reloaded/22b9a80af86149d53c6f47b33f5a129d9ffa4ea0/WebContent/css/images/icon-128x128.png -------------------------------------------------------------------------------- /WebContent/css/images/icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devgianlu/PYX-Reloaded/22b9a80af86149d53c6f47b33f5a129d9ffa4ea0/WebContent/css/images/icon-144x144.png -------------------------------------------------------------------------------- /WebContent/css/images/icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devgianlu/PYX-Reloaded/22b9a80af86149d53c6f47b33f5a129d9ffa4ea0/WebContent/css/images/icon-152x152.png -------------------------------------------------------------------------------- /WebContent/css/images/icon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devgianlu/PYX-Reloaded/22b9a80af86149d53c6f47b33f5a129d9ffa4ea0/WebContent/css/images/icon-16x16.png -------------------------------------------------------------------------------- /WebContent/css/images/icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devgianlu/PYX-Reloaded/22b9a80af86149d53c6f47b33f5a129d9ffa4ea0/WebContent/css/images/icon-192x192.png -------------------------------------------------------------------------------- /WebContent/css/images/icon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devgianlu/PYX-Reloaded/22b9a80af86149d53c6f47b33f5a129d9ffa4ea0/WebContent/css/images/icon-32x32.png -------------------------------------------------------------------------------- /WebContent/css/images/icon-384x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devgianlu/PYX-Reloaded/22b9a80af86149d53c6f47b33f5a129d9ffa4ea0/WebContent/css/images/icon-384x384.png -------------------------------------------------------------------------------- /WebContent/css/images/icon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devgianlu/PYX-Reloaded/22b9a80af86149d53c6f47b33f5a129d9ffa4ea0/WebContent/css/images/icon-512x512.png -------------------------------------------------------------------------------- /WebContent/css/images/icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devgianlu/PYX-Reloaded/22b9a80af86149d53c6f47b33f5a129d9ffa4ea0/WebContent/css/images/icon-72x72.png -------------------------------------------------------------------------------- /WebContent/css/images/icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devgianlu/PYX-Reloaded/22b9a80af86149d53c6f47b33f5a129d9ffa4ea0/WebContent/css/images/icon-96x96.png -------------------------------------------------------------------------------- /WebContent/css/images/no-profile.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /WebContent/css/theming.css: -------------------------------------------------------------------------------- 1 | #themingWheel svg { 2 | width: 100%; 3 | height: 100% 4 | } 5 | 6 | #themingWheel g[data-color] { 7 | opacity: 1; 8 | transition: opacity .2s cubic-bezier(.4, 0, 1, 1) 9 | } 10 | 11 | #themingWheel .selector { 12 | opacity: 0; 13 | transition: opacity .4s cubic-bezier(.4, 0, 1, 1); 14 | fill: #BDBDBD 15 | } 16 | 17 | #themingWheel .selected .selector { 18 | opacity: 1 19 | } 20 | 21 | #themingWheel .label { 22 | text-anchor: middle; 23 | opacity: 0; 24 | transition: opacity .4s cubic-bezier(.4, 0, 1, 1); 25 | fill: #fff; 26 | font-size: 24px 27 | } 28 | 29 | #themingWheel .selected--1 .label--1, #themingWheel .selected--2 .label--2 { 30 | opacity: 1 31 | } 32 | 33 | #themingWheel svg.hide-nonaccents g[data-color="Blue Grey"]:not(.selected), #themingWheel svg.hide-nonaccents g[data-color="Brown"]:not(.selected), #themingWheel svg.hide-nonaccents g[data-color="Grey"]:not(.selected) { 34 | opacity: .12 35 | } 36 | 37 | #themingWheel .selected { 38 | opacity: 1 !important 39 | } -------------------------------------------------------------------------------- /WebContent/games/index.css: -------------------------------------------------------------------------------- 1 | #drawer .mdc-drawer__header-content { 2 | flex-direction: column; 3 | align-items: flex-start; 4 | } 5 | 6 | #drawer .mdc-drawer__header-content .details__container { 7 | overflow: hidden; 8 | width: 100%; 9 | margin: auto 0; 10 | } 11 | 12 | #drawer .mdc-drawer__header-content .details__container > * { 13 | margin: 0; 14 | overflow: hidden; 15 | white-space: nowrap; 16 | text-overflow: ellipsis; 17 | } 18 | 19 | #drawer .mdc-drawer__header-content img.details--profile { 20 | border-radius: 50%; 21 | height: 100%; 22 | width: auto; 23 | max-height: 72px; 24 | max-width: 72px; 25 | } 26 | 27 | /* 600px (same as drawer) ---------- */ 28 | @media only screen and (min-width: 600px) { 29 | #drawer .mdc-drawer__header-content img.details--profile { 30 | max-height: 96px; 31 | max-width: 96px; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /WebContent/index.css: -------------------------------------------------------------------------------- 1 | #socialLogin .mdc-card__supporting-text { 2 | padding-bottom: 16px; 3 | } 4 | 5 | #socialLogin .mdc-card__supporting-text > .mdc-button { 6 | margin: 4px; 7 | } -------------------------------------------------------------------------------- /WebContent/js/bottom_sheet.js: -------------------------------------------------------------------------------- 1 | class BottomSheet { 2 | constructor(elem) { 3 | this.elem = $(elem); 4 | } 5 | 6 | get open() { 7 | return this.elem.hasClass('bottom-sheet--open'); 8 | } 9 | 10 | set open(open) { 11 | if (open) this.elem.addClass('bottom-sheet--open'); 12 | else this.elem.removeClass('bottom-sheet--open'); 13 | } 14 | } -------------------------------------------------------------------------------- /WebContent/js/cardcast.helper.js: -------------------------------------------------------------------------------- 1 | class Cardcast { 2 | /** 3 | * @param {string} code - Deck code 4 | */ 5 | constructor(code) { 6 | this.code = code; 7 | } 8 | 9 | static get base_url() { 10 | return "https://api.cardcastgame.com/v1/"; 11 | } 12 | 13 | /** 14 | * @callback decksCallback 15 | * @param {object} data.results - Results object 16 | * @param {int} data.total - Total decks in the system 17 | * @param {int} data.results.count - Result count 18 | * @param {object[]} data.results.data[] - Actual results 19 | * @param {string} data.results.data[].code - Deck code 20 | * @param {string} data.results.data[].category - Deck category 21 | * @param {object[]} data.results.data[].sample_calls - Actual results 22 | * @param {object[]} data.results.data[].sample_responses - Actual results 23 | * @param {int} data.results.data[].rating - Deck rating 24 | * @param {object} data.results.data[].author - Deck author 25 | * @param {string} data.results.data[].author.username - Deck author username 26 | */ 27 | 28 | /** 29 | * @callback infoCallback 30 | * @param {string} data.description - Deck description 31 | * @param {int} data.call_count - Call count 32 | * @param {int} data.response_count - Response count 33 | */ 34 | 35 | /** 36 | * @param {string} query 37 | * @param {string} category 38 | * @param {string} direction 39 | * @param {int} limit 40 | * @param {boolean} nsfw 41 | * @param {int} offset 42 | * @param {string} sort 43 | * @param {decksCallback} listener 44 | */ 45 | static decks(query, category, direction, limit, nsfw, offset, sort, listener) { 46 | $.get(Cardcast.base_url + "decks?category=" + category + "&direction=" + direction + "&limit=" + limit + "&nsfw=" + nsfw + "&offset=" + offset + "&sort=" + sort + (query !== null ? "&search=" + query : "")).fail(function (data) { 47 | listener(null, data); 48 | }).done(function (data) { 49 | listener(data, null); 50 | }); 51 | } 52 | 53 | /** 54 | * @param {infoCallback} listener 55 | */ 56 | info(listener) { 57 | $.get(Cardcast.base_url + "decks/" + this.code).fail(function (data) { 58 | listener(null, data); 59 | }).done(function (data) { 60 | data.call_count = parseInt(data.call_count); 61 | data.response_count = parseInt(data.response_count); 62 | listener(data, null); 63 | }); 64 | } 65 | 66 | calls(listener) { 67 | $.get(Cardcast.base_url + "decks/" + this.code + "/calls").fail(function (data) { 68 | listener(null, data); 69 | }).done(function (data) { 70 | listener(data, null); 71 | }); 72 | } 73 | 74 | responses(listener) { 75 | $.get(Cardcast.base_url + "decks/" + this.code + "/responses").fail(function (data) { 76 | listener(null, data); 77 | }).done(function (data) { 78 | listener(data, null); 79 | }); 80 | } 81 | } -------------------------------------------------------------------------------- /WebContent/js/events.helper.js: -------------------------------------------------------------------------------- 1 | class EventsReceiver { 2 | 3 | constructor() { 4 | this.ws = new WebSocket((location.protocol === "https:" ? "wss" : "ws") + "://" + location.hostname + (location.port ? (":" + location.port) : "") + "/Events"); 5 | this.ws.onmessage = (msg) => this.handleMessage(msg); 6 | this.eventListeners = {}; 7 | } 8 | 9 | /** 10 | * @callback eventCallback 11 | * @param {string} data.E - Event code 12 | */ 13 | 14 | /** 15 | * @param {String} key 16 | * @param {eventCallback} listener 17 | */ 18 | register(key, listener) { 19 | this.eventListeners[key] = listener; 20 | } 21 | 22 | handleMessage(event) { 23 | /** @param {object[]} data.Es - Events **/ 24 | const data = JSON.parse(event.data); 25 | if ("e" in data) { 26 | Notifier.debug(data, true); 27 | if (data.ec === "nr" || data.ec === "se") window.location = "/"; 28 | return; 29 | } 30 | 31 | const events = data.Es; 32 | if (events.length === 0) return; 33 | 34 | this.processEvents(events); 35 | } 36 | 37 | /** 38 | * @param {string} events[].E - Event code 39 | */ 40 | processEvents(events) { 41 | for (let i = 0; i < events.length; i++) { 42 | const event = events[i]; 43 | switch (event.E) { 44 | case "pp": 45 | Requester.request("PP", {}); 46 | continue; 47 | case "PS": 48 | /** @param {int} event.bs - Time before shutdown */ 49 | let bs; 50 | if (event.bs >= 60000) bs = Math.ceil(event.bs / 60000) + " minutes"; 51 | else bs = Math.ceil(event.bs / 1000) + " seconds"; 52 | 53 | Notifier.show(Notifier.WARN, "The server is preparing for shutdown. The server will be shutdown in " + bs, 20, false); 54 | continue; 55 | case "SS": 56 | redirectToStatusPage(); 57 | continue; 58 | case "B&": 59 | case "kk": 60 | window.location = "/kicked/"; 61 | continue; 62 | default: 63 | for (const key in this.eventListeners) { 64 | if (this.eventListeners.hasOwnProperty(key)) 65 | this.eventListeners[key](event); 66 | } 67 | } 68 | } 69 | } 70 | 71 | close() { 72 | this.ws.close(1000); 73 | } 74 | } 75 | 76 | window.eventsReceiver = new EventsReceiver(); 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /WebContent/js/tabs.helper.js: -------------------------------------------------------------------------------- 1 | class TabsManager { 2 | constructor(id, indexToId, defaultIndex = 0) { 3 | this.indexToElm = TabsManager._createIndexToElement(indexToId); 4 | 5 | this.tabs = new mdc.tabs.MDCTabBar(document.getElementById(id)); 6 | this.tabs.listen('MDCTabBar:change', ({detail: tabs}) => { 7 | this._handleTabChange(tabs.activeTabIndex); 8 | }); 9 | 10 | this._handleTabChange(defaultIndex) 11 | } 12 | 13 | static _createIndexToElement(indexToId) { 14 | const indexToElm = []; 15 | for (let i = 0; i < indexToId.length; i++) indexToElm[i] = $('#' + indexToId[i]); 16 | return indexToElm; 17 | } 18 | 19 | static init(id, indexToId, defaultIndex = 0) { 20 | new TabsManager(id, indexToId, defaultIndex); 21 | } 22 | 23 | _handleTabChange(index) { 24 | for (let i = 0; i < this.indexToElm.length; i++) { 25 | const elm = this.indexToElm[i]; 26 | if (i === index) elm.show(); 27 | else elm.hide(); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /WebContent/js/theming.js: -------------------------------------------------------------------------------- 1 | class Theming { 2 | 3 | static get DEFAULT_PRIMARY() { 4 | return "rgb(103,58,183)" 5 | } 6 | 7 | static get DEFAULT_SECONDARY() { 8 | return "rgb(0,150,136)" 9 | } 10 | 11 | static applyKnown(primary, secondary, save = true) { 12 | document.documentElement.style.setProperty('--mdc-theme-primary', primary); 13 | document.documentElement.style.setProperty('--mdc-theme-secondary', secondary); 14 | 15 | let themeColor = document.head.querySelector('meta[name=theme-color]'); 16 | if (themeColor === null) { 17 | themeColor = document.createElement('meta'); 18 | document.head.appendChild(themeColor); 19 | } 20 | 21 | themeColor.name = "theme-color"; 22 | themeColor.content = primary; 23 | 24 | const appleMobileCapable = document.createElement("meta"); 25 | appleMobileCapable.name = "apple-mobile-web-app-capable"; 26 | appleMobileCapable.content = "yes"; 27 | document.head.appendChild(appleMobileCapable); 28 | 29 | const appleStatusBar = document.createElement("meta"); 30 | appleStatusBar.name = "apple-apple-mobile-web-app-status-bar-style-web-app-capable"; 31 | appleStatusBar.content = "black-translucent"; 32 | document.head.appendChild(appleStatusBar); 33 | 34 | if (save) { 35 | Cookies.set("PYX-Theme-Primary", primary); 36 | Cookies.set("PYX-Theme-Secondary", secondary); 37 | 38 | Requester.request("sup", { 39 | "up": {"TpC": primary, "TsC": secondary} 40 | }) 41 | } 42 | } 43 | 44 | /** 45 | * @callback colorsCallback 46 | * @param {string} primary 47 | * @param {string} secondary 48 | */ 49 | 50 | /** 51 | * @param {colorsCallback} listener 52 | */ 53 | static get(listener) { 54 | const xmlhttp = new XMLHttpRequest(); 55 | xmlhttp.onreadystatechange = function () { 56 | if (xmlhttp.readyState === XMLHttpRequest.DONE) { 57 | if (xmlhttp.status === 200) { 58 | const data = JSON.parse(xmlhttp.responseText); 59 | if ("TpC" in data && "TsC" in data) listener(data.TpC, data.TsC); 60 | else listener(Theming.DEFAULT_PRIMARY, Theming.DEFAULT_SECONDARY); 61 | } else { 62 | console.debug(xmlhttp); 63 | const cookies = Theming.getFromCookies(); 64 | listener(cookies[0], cookies[1]); 65 | } 66 | } 67 | }; 68 | 69 | xmlhttp.open("POST", "/AjaxServlet", true); 70 | xmlhttp.send("o=gup&up=TpC,TsC"); 71 | } 72 | 73 | static getFromCookies() { 74 | const primaryCookie = Cookies.getJSON("PYX-Theme-Primary"); 75 | const secondaryCookie = Cookies.getJSON("PYX-Theme-Secondary"); 76 | if (primaryCookie === undefined || secondaryCookie === undefined) return [Theming.DEFAULT_PRIMARY, Theming.DEFAULT_SECONDARY]; 77 | else return [primaryCookie, secondaryCookie]; 78 | } 79 | 80 | static apply() { 81 | const cookies = Theming.getFromCookies(); 82 | Theming.applyKnown(cookies[0], cookies[1], false); 83 | 84 | Theming.get((primary, secondary) => { 85 | Theming.applyKnown(primary, secondary, false); 86 | }); 87 | } 88 | 89 | static clear() { 90 | Cookies.remove("PYX-Theme-Primary"); 91 | Cookies.remove("PYX-Theme-Secondary"); 92 | } 93 | } 94 | 95 | Theming.apply(); -------------------------------------------------------------------------------- /WebContent/kicked/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Kicked - PYX-Reloaded 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
    18 |
    19 |
    20 |
    21 | Kicked - PYX Reloaded 22 |
    23 |
    24 |
    25 | 26 |
    27 |

    You have been kicked from the server. In 28 | the worst case you have also been banned. There's not much you can do to join the server again.

    29 |
    30 |
    31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /WebContent/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "PYX-Reloaded", 3 | "short_name": "PYX-Reloaded", 4 | "theme_color": "#673ab7", 5 | "background_color": "#ffffff", 6 | "display": "standalone", 7 | "scope": "/", 8 | "start_url": "/", 9 | "icons": [ 10 | { 11 | "src": "css/images/icon-72x72.png", 12 | "sizes": "72x72", 13 | "type": "image/png" 14 | }, 15 | { 16 | "src": "css/images/icon-96x96.png", 17 | "sizes": "96x96", 18 | "type": "image/png" 19 | }, 20 | { 21 | "src": "css/images/icon-128x128.png", 22 | "sizes": "128x128", 23 | "type": "image/png" 24 | }, 25 | { 26 | "src": "css/images/icon-144x144.png", 27 | "sizes": "144x144", 28 | "type": "image/png" 29 | }, 30 | { 31 | "src": "css/images/icon-152x152.png", 32 | "sizes": "152x152", 33 | "type": "image/png" 34 | }, 35 | { 36 | "src": "css/images/icon-192x192.png", 37 | "sizes": "192x192", 38 | "type": "image/png" 39 | }, 40 | { 41 | "src": "css/images/icon-384x384.png", 42 | "sizes": "384x384", 43 | "type": "image/png" 44 | }, 45 | { 46 | "src": "css/images/icon-512x512.png", 47 | "sizes": "512x512", 48 | "type": "image/png" 49 | } 50 | ], 51 | "splash_pages": null 52 | } -------------------------------------------------------------------------------- /WebContent/sitemap.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | https://pyx.gianlu.xyz/ 6 | 7 | -------------------------------------------------------------------------------- /WebContent/users/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Users - PYX-Reloaded 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
    19 |
    20 |
    21 |
    22 | Users - PYX Reloaded 23 |
    24 |
    25 | 26 |
    27 |
    28 |
    29 | 30 |
    31 |

    There are no users.

    32 | 33 |
    34 |
    35 | 36 |
    37 |
  • 38 | 39 | 40 | {{Nickname}} 41 | {{Role}} 42 | 43 |
  • 44 |
    45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /WebContent/users/index.js: -------------------------------------------------------------------------------- 1 | class UsersManager { 2 | constructor() { 3 | this.main = $('main'); 4 | 5 | this.message = this.main.find('.message'); 6 | this.users = new List(this.main[0], { 7 | item: 'userTemplate', 8 | valueNames: ['_nick', '_role', {attr: 'src', name: '_img'}] 9 | }); 10 | 11 | this.refresh = $('#refresh'); 12 | this.refresh.on('click', () => this.loadUsers()); 13 | } 14 | 15 | loadUsers() { 16 | Requester.request("gn", {}, (data) => { 17 | /** 18 | * @param {object[]} data.nl - Names list 19 | * @param {string} data.nl[].n - Nickname 20 | * @param {string} data.nl[].ia - Whether the user is an admin 21 | * @param {string} data.nl[].ha - Whether the user has an account 22 | * @param {string} data.nl[].p - Profile picture 23 | */ 24 | 25 | this.users.clear(); 26 | const list = data.nl; 27 | for (let i = 0; i < list.length; i++) { 28 | const user = list[i]; 29 | this.users.add({ 30 | "_nick": user.n, 31 | "_role": user.ia ? "Admin" : (user.ha ? "Registered" : "Guest"), 32 | "_img": user.p === undefined || user.p === null ? "/css/images/no-profile.svg" : user.p 33 | }); 34 | } 35 | 36 | if (list.length === 0) this.message.show(); 37 | else this.message.hide(); 38 | }, (error) => { 39 | Notifier.error("Failed loading users!", error); 40 | }); 41 | } 42 | } -------------------------------------------------------------------------------- /dist/start.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | pushd %~dp0 3 | 4 | java -jar ./PYX-Reloaded.jar 5 | pause 6 | 7 | popd -------------------------------------------------------------------------------- /dist/start.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | cd "$(dirname "$0")" 3 | sudo java -jar ./PYX-Reloaded.jar 4 | -------------------------------------------------------------------------------- /dist/update.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | pushd %~dp0 3 | 4 | ren PYX-Reloaded.jar PYX-Reloaded.old.jar 5 | java -jar ./PYX-Reloaded.old.jar --update 6 | del PYX-Reloaded.old.jar 7 | pause 8 | 9 | popd -------------------------------------------------------------------------------- /dist/update.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | cd "$(dirname "$0")" 3 | sudo mv ./PYX-Reloaded.jar ./PYX-Reloaded.old.jar 4 | sudo java -jar ./PYX-Reloaded.old.jar --update 5 | sudo rm ./PYX-Reloaded.old.jar -------------------------------------------------------------------------------- /icons/icon.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devgianlu/PYX-Reloaded/22b9a80af86149d53c6f47b33f5a129d9ffa4ea0/icons/icon.ai -------------------------------------------------------------------------------- /icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devgianlu/PYX-Reloaded/22b9a80af86149d53c6f47b33f5a129d9ffa4ea0/icons/icon.png -------------------------------------------------------------------------------- /preferences.json.default: -------------------------------------------------------------------------------- 1 | { 2 | "port": 80, 3 | "host": "0.0.0.0", 4 | "secure": false, 5 | "securePort": 443, 6 | "trustStorePath": "", 7 | "trustStorePassword": "", 8 | "keyStorePath": "", 9 | "keyStorePassword": "", 10 | "maxGames": 400, 11 | "maxUsers": 100, 12 | "cacheEnabled": true, 13 | "webContent": "./WebContent", 14 | "pyxDbUrl": "jdbc:sqlite:pyx.sqlite", 15 | "serverStatusPage": "http://example.com", 16 | "chat": { 17 | "registeredOnly": false, 18 | "floodCount": 4, 19 | "floodTime": 30 20 | }, 21 | "serverDb": { 22 | "url": "jdbc:sqlite:server.sqlite", 23 | "username": "", 24 | "password": "" 25 | }, 26 | "emails": { 27 | "senderEmail": "", 28 | "senderName": "PYX-Reloaded", 29 | "smtpServer": "", 30 | "smtpPort": 587, 31 | "smtpUsername": "", 32 | "smtpPassword": "", 33 | "verifyCallback": "" 34 | }, 35 | "socials": { 36 | "googleClientId": "", 37 | "facebookAppId": "", 38 | "facebookAppSecret": "", 39 | "githubAppId": "", 40 | "githubAppSecret": "", 41 | "twitterAppId": "", 42 | "twitterAppSecret": "", 43 | "twitterCallback": "" 44 | }, 45 | "game": { 46 | "maxSkipsBeforeKick": 2, 47 | "roundIntermission": 8, 48 | "minBlackCards": 50, 49 | "minWhiteCardsPerPlayer": 20, 50 | "playTimeoutBase": 45, 51 | "judgeTimeoutBase": 40, 52 | "playTimeoutPerCard": 15, 53 | "judgeTimeoutPerCard": 7, 54 | "scoreLimit": { 55 | "min": 4, 56 | "default": 8, 57 | "max": 69 58 | }, 59 | "playerLimit": { 60 | "min": 3, 61 | "default": 10, 62 | "max": 20 63 | }, 64 | "spectatorLimit": { 65 | "min": 0, 66 | "default": 10, 67 | "max": 20 68 | }, 69 | "blankCardsLimit": { 70 | "min": 0, 71 | "default": 0, 72 | "max": 30 73 | }, 74 | "winBy": { 75 | "min": 0, 76 | "default": 0, 77 | "max": 5 78 | } 79 | } 80 | } -------------------------------------------------------------------------------- /pyx.sqlite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devgianlu/PYX-Reloaded/22b9a80af86149d53c6f47b33f5a129d9ffa4ea0/pyx.sqlite -------------------------------------------------------------------------------- /screenshots/screen1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devgianlu/PYX-Reloaded/22b9a80af86149d53c6f47b33f5a129d9ffa4ea0/screenshots/screen1.png -------------------------------------------------------------------------------- /screenshots/screen2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devgianlu/PYX-Reloaded/22b9a80af86149d53c6f47b33f5a129d9ffa4ea0/screenshots/screen2.png -------------------------------------------------------------------------------- /screenshots/screen3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devgianlu/PYX-Reloaded/22b9a80af86149d53c6f47b33f5a129d9ffa4ea0/screenshots/screen3.png -------------------------------------------------------------------------------- /screenshots/screen4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devgianlu/PYX-Reloaded/22b9a80af86149d53c6f47b33f5a129d9ffa4ea0/screenshots/screen4.png -------------------------------------------------------------------------------- /screenshots/screen5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devgianlu/PYX-Reloaded/22b9a80af86149d53c6f47b33f5a129d9ffa4ea0/screenshots/screen5.png -------------------------------------------------------------------------------- /screenshots/screen6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devgianlu/PYX-Reloaded/22b9a80af86149d53c6f47b33f5a129d9ffa4ea0/screenshots/screen6.png -------------------------------------------------------------------------------- /screenshots/screen7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devgianlu/PYX-Reloaded/22b9a80af86149d53c6f47b33f5a129d9ffa4ea0/screenshots/screen7.png -------------------------------------------------------------------------------- /screenshots/screen8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devgianlu/PYX-Reloaded/22b9a80af86149d53c6f47b33f5a129d9ffa4ea0/screenshots/screen8.png -------------------------------------------------------------------------------- /screenshots/screen9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devgianlu/PYX-Reloaded/22b9a80af86149d53c6f47b33f5a129d9ffa4ea0/screenshots/screen9.png -------------------------------------------------------------------------------- /server.sqlite.default: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devgianlu/PYX-Reloaded/22b9a80af86149d53c6f47b33f5a129d9ffa4ea0/server.sqlite.default -------------------------------------------------------------------------------- /src/assembly/jar.xml: -------------------------------------------------------------------------------- 1 | 4 | jar-with-dependencies 5 | 6 | jar 7 | 8 | false 9 | 10 | 11 | / 12 | true 13 | true 14 | runtime 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/assembly/zip.xml: -------------------------------------------------------------------------------- 1 | 4 | deploy 5 | /PYX-Reloaded-${project.version} 6 | 7 | tar.gz 8 | tar.bz2 9 | zip 10 | 11 | 12 | 13 | / 14 | ${project.basedir}/target/PYX-Reloaded-jar-with-dependencies.jar 15 | PYX-Reloaded.jar 16 | 17 | 18 | / 19 | ${project.basedir}/pyx.sqlite 20 | 21 | 22 | / 23 | ${project.basedir}/preferences.json.default 24 | preferences.json 25 | 26 | 27 | / 28 | ${project.basedir}/server.sqlite.default 29 | server.sqlite 30 | 31 | 32 | 33 | 34 | ${project.basedir} 35 | / 36 | 37 | README* 38 | LICENSE* 39 | NOTICE* 40 | 41 | 42 | 43 | ${project.basedir}/dist 44 | / 45 | 46 | 47 | ${project.basedir}/WebContent 48 | /WebContent 49 | 50 | 51 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/Utils.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded; 2 | 3 | import com.gianlu.pyxreloaded.cards.WhiteCard; 4 | import com.google.gson.JsonArray; 5 | import com.google.gson.JsonObject; 6 | import io.undertow.server.HttpServerExchange; 7 | import org.apache.http.NameValuePair; 8 | import org.jetbrains.annotations.NotNull; 9 | import org.jetbrains.annotations.Nullable; 10 | 11 | import java.util.*; 12 | import java.util.concurrent.ThreadLocalRandom; 13 | 14 | public class Utils { 15 | private static final String ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; 16 | 17 | public static boolean isVersionNewer(String older, String newer) { 18 | String[] olderSplit = older.split("\\."); 19 | String[] newerSplit = newer.split("\\."); 20 | 21 | for (int i = 0; i < olderSplit.length && i < newerSplit.length; i++) { 22 | int olderNum = Integer.parseInt(olderSplit[i]); 23 | int newerNum = Integer.parseInt(newerSplit[i]); 24 | if (newerNum > olderNum) return true; 25 | } 26 | 27 | return false; 28 | } 29 | 30 | public static JsonArray toIntsJsonArray(Collection items) { 31 | JsonArray jsonArray = new JsonArray(items.size()); 32 | for (int item : items) jsonArray.add(item); 33 | return jsonArray; 34 | } 35 | 36 | @NotNull 37 | public static String getServerVersion(Package pkg) { 38 | String version = pkg.getImplementationVersion(); 39 | if (version == null) version = pkg.getSpecificationVersion(); 40 | if (version == null) version = System.getenv("version"); 41 | if (version == null) version = "debug"; 42 | return version; 43 | } 44 | 45 | public static JsonArray toStringsJsonArray(Collection items) { 46 | JsonArray jsonArray = new JsonArray(items.size()); 47 | for (String item : items) jsonArray.add(item); 48 | return jsonArray; 49 | } 50 | 51 | public static Map toMap(JsonObject obj) { 52 | HashMap map = new HashMap<>(); 53 | for (String key : obj.keySet()) map.put(key, obj.get(key).getAsString()); 54 | return map; 55 | } 56 | 57 | public static boolean contains(String[] array, String val) { 58 | for (String str : array) 59 | if (str.equals(val)) return true; 60 | return false; 61 | } 62 | 63 | @Nullable 64 | public static String extractParam(HttpServerExchange exchange, String key) { 65 | Deque deque = exchange.getQueryParameters().get(key); 66 | return deque == null || deque.isEmpty() ? null : deque.getFirst(); 67 | } 68 | 69 | @NotNull 70 | public static String joinCardIds(Collection items, String separator) { 71 | if (items == null) return ""; 72 | 73 | StringBuilder builder = new StringBuilder(); 74 | boolean first = true; 75 | for (WhiteCard item : items) { 76 | if (!first) builder.append(separator); 77 | builder.append(item.getId()); 78 | first = false; 79 | } 80 | 81 | return builder.toString(); 82 | } 83 | 84 | @NotNull 85 | public static String generateAlphanumericString(int length) { 86 | StringBuilder builder = new StringBuilder(); 87 | Random random = ThreadLocalRandom.current(); 88 | for (int i = 0; i < length; i++) { 89 | if (random.nextBoolean()) builder.append(String.valueOf(random.nextInt(10))); 90 | else builder.append(ALPHABET.charAt(random.nextInt(26))); 91 | } 92 | 93 | return builder.toString(); 94 | } 95 | 96 | @Nullable 97 | public static String get(List pairs, String name) { 98 | for (NameValuePair pair : pairs) 99 | if (Objects.equals(pair.getName(), name)) 100 | return pair.getValue(); 101 | 102 | return null; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/cardcast/CardcastBlackCard.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.cardcast; 2 | 3 | import com.gianlu.pyxreloaded.cards.BlackCard; 4 | 5 | 6 | public class CardcastBlackCard extends BlackCard { 7 | private final int id; 8 | private final String text; 9 | private final int draw; 10 | private final int pick; 11 | private final String deckId; 12 | 13 | CardcastBlackCard(int id, String text, int draw, int pick, String deckId) { 14 | this.id = id; 15 | this.text = text; 16 | this.draw = draw; 17 | this.pick = pick; 18 | this.deckId = deckId; 19 | } 20 | 21 | @Override 22 | public int getId() { 23 | return id; 24 | } 25 | 26 | @Override 27 | public String getText() { 28 | return text; 29 | } 30 | 31 | @Override 32 | public String getWatermark() { 33 | return deckId; 34 | } 35 | 36 | @Override 37 | public int getDraw() { 38 | return draw; 39 | } 40 | 41 | @Override 42 | public int getPick() { 43 | return pick; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/cardcast/CardcastDeck.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.cardcast; 2 | 3 | import com.gianlu.pyxreloaded.cards.CardSet; 4 | 5 | import java.util.HashSet; 6 | import java.util.Set; 7 | 8 | 9 | public class CardcastDeck extends CardSet { 10 | private final String name; 11 | private final String code; 12 | private final String description; 13 | private final Set blackCards = new HashSet<>(); 14 | private final Set whiteCards = new HashSet<>(); 15 | 16 | CardcastDeck(String name, String code, String description) { 17 | this.name = name; 18 | this.code = code; 19 | this.description = description; 20 | } 21 | 22 | @Override 23 | public int getId() { 24 | return -Integer.parseInt(code, 36); 25 | } 26 | 27 | @Override 28 | public String getName() { 29 | return name; 30 | } 31 | 32 | @Override 33 | public String getDescription() { 34 | return description; 35 | } 36 | 37 | @Override 38 | public boolean isActive() { 39 | return true; 40 | } 41 | 42 | @Override 43 | public boolean isBaseDeck() { 44 | return false; 45 | } 46 | 47 | @Override 48 | public int getWeight() { 49 | return Integer.MAX_VALUE; 50 | } 51 | 52 | @Override 53 | public Set getBlackCards() { 54 | return blackCards; 55 | } 56 | 57 | @Override 58 | public Set getWhiteCards() { 59 | return whiteCards; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/cardcast/CardcastWhiteCard.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.cardcast; 2 | 3 | import com.gianlu.pyxreloaded.cards.WhiteCard; 4 | 5 | 6 | public class CardcastWhiteCard extends WhiteCard { 7 | private final int id; 8 | private final String text; 9 | private final String deckId; 10 | 11 | CardcastWhiteCard(final int id, final String text, final String deckId) { 12 | this.id = id; 13 | this.text = text; 14 | this.deckId = deckId; 15 | } 16 | 17 | @Override 18 | public int getId() { 19 | return id; 20 | } 21 | 22 | @Override 23 | public String getText() { 24 | return text; 25 | } 26 | 27 | @Override 28 | public String getWatermark() { 29 | return deckId; 30 | } 31 | 32 | @Override 33 | public boolean isWriteIn() { 34 | return false; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/cardcast/FailedLoadingSomeCardcastDecks.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.cardcast; 2 | 3 | import com.google.gson.JsonArray; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | public class FailedLoadingSomeCardcastDecks extends Exception { 9 | public final List failedDecks = new ArrayList<>(); 10 | 11 | public FailedLoadingSomeCardcastDecks() { 12 | } 13 | 14 | public JsonArray getFailedJson() { 15 | JsonArray array = new JsonArray(failedDecks.size()); 16 | for (String id : failedDecks) array.add(id); 17 | return array; 18 | } 19 | } -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/cards/BlackCard.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.cards; 2 | 3 | import com.gianlu.pyxreloaded.Consts; 4 | import com.gianlu.pyxreloaded.data.JsonWrapper; 5 | 6 | public abstract class BlackCard { 7 | 8 | public abstract int getId(); 9 | 10 | public abstract String getText(); 11 | 12 | public abstract String getWatermark(); 13 | 14 | public abstract int getDraw(); 15 | 16 | public abstract int getPick(); 17 | 18 | @Override 19 | public final boolean equals(final Object other) { 20 | return other instanceof BlackCard && ((BlackCard) other).getId() == getId(); 21 | } 22 | 23 | @Override 24 | public final int hashCode() { 25 | return getId(); 26 | } 27 | 28 | public final JsonWrapper getClientDataJson() { 29 | JsonWrapper obj = new JsonWrapper(); 30 | obj.add(Consts.GeneralKeys.CARD_ID, getId()); 31 | obj.add(Consts.BlackCardData.TEXT, getText()); 32 | obj.add(Consts.BlackCardData.DRAW, getDraw()); 33 | obj.add(Consts.BlackCardData.PICK, getPick()); 34 | obj.add(Consts.BlackCardData.WATERMARK, getWatermark()); 35 | return obj; 36 | } 37 | 38 | @Override 39 | public String toString() { 40 | return String.format("%s %s (id:%d, draw:%d, pick:%d, watermark:%s)", getClass().getName(), getText(), getId(), getDraw(), getPick(), getWatermark()); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/cards/BlackDeck.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.cards; 2 | 3 | import com.gianlu.pyxreloaded.game.OutOfCardsException; 4 | 5 | import java.util.*; 6 | 7 | 8 | /** 9 | * Deck of Black Cards. 10 | *

    11 | * This class is thread-safe. 12 | * 13 | * @author Andy Janata (ajanata@socialgamer.net) 14 | */ 15 | public class BlackDeck { 16 | private final List deck; 17 | private final List discard; 18 | 19 | /** 20 | * Create a new black card deck, loading the cards from the database and shuffling them. 21 | */ 22 | public BlackDeck(Collection cardSets) { 23 | Set allCards = new HashSet<>(); 24 | for (CardSet cardSet : cardSets) allCards.addAll(cardSet.getBlackCards()); 25 | deck = new ArrayList<>(allCards); 26 | Collections.shuffle(deck); 27 | discard = new ArrayList<>(deck.size()); 28 | } 29 | 30 | /** 31 | * Get the next card from the top of deck. 32 | * 33 | * @return The next card. 34 | * @throws OutOfCardsException There are no more cards in the deck. 35 | */ 36 | public synchronized BlackCard getNextCard() throws OutOfCardsException { 37 | if (deck.size() == 0) throw new OutOfCardsException(); 38 | // we have an ArrayList here, so this is faster 39 | return deck.remove(deck.size() - 1); 40 | } 41 | 42 | /** 43 | * Add a card to the discard pile. 44 | * 45 | * @param card Card to add to discard pile. 46 | */ 47 | public synchronized void discard(final BlackCard card) { 48 | if (card != null) discard.add(card); 49 | } 50 | 51 | /** 52 | * Shuffles the discard pile and puts the cards under the cards remaining in the deck. 53 | */ 54 | public synchronized void reshuffle() { 55 | Collections.shuffle(discard); 56 | deck.addAll(0, discard); 57 | discard.clear(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/cards/BlankWhiteCard.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.cards; 2 | 3 | import org.jetbrains.annotations.Nullable; 4 | 5 | public class BlankWhiteCard extends WhiteCard { 6 | private static final String BLANK_TEXT = "____"; 7 | private final int id; // Always negative 8 | private String text = null; 9 | 10 | public BlankWhiteCard(int id) { 11 | this.id = id; 12 | clear(); 13 | } 14 | 15 | @Override 16 | public int getId() { 17 | return id; 18 | } 19 | 20 | @Override 21 | @Nullable 22 | public String getText() { 23 | return text; 24 | } 25 | 26 | public void setText(@Nullable String text) { 27 | this.text = text; 28 | } 29 | 30 | public void clear() { 31 | setText(BLANK_TEXT); 32 | } 33 | 34 | @Override 35 | public String getWatermark() { 36 | return "____"; 37 | } 38 | 39 | @Override 40 | public boolean isWriteIn() { 41 | return true; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/cards/CardSet.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.cards; 2 | 3 | import com.gianlu.pyxreloaded.Consts; 4 | import com.gianlu.pyxreloaded.data.JsonWrapper; 5 | 6 | import java.util.Set; 7 | 8 | 9 | public abstract class CardSet { 10 | 11 | public abstract int getId(); 12 | 13 | public abstract String getName(); 14 | 15 | public abstract String getDescription(); 16 | 17 | public abstract boolean isActive(); 18 | 19 | public abstract boolean isBaseDeck(); 20 | 21 | public abstract int getWeight(); 22 | 23 | public abstract Set getBlackCards(); 24 | 25 | public abstract Set getWhiteCards(); 26 | 27 | public final JsonWrapper getClientMetadataJson() { 28 | JsonWrapper obj = new JsonWrapper(); 29 | obj.add(Consts.CardSetData.ID, getId()); 30 | obj.add(Consts.CardSetData.CARD_SET_NAME, getName()); 31 | obj.add(Consts.CardSetData.CARD_SET_DESCRIPTION, getDescription()); 32 | obj.add(Consts.CardSetData.WEIGHT, getWeight()); 33 | obj.add(Consts.CardSetData.BASE_DECK, isBaseDeck()); 34 | obj.add(Consts.CardSetData.BLACK_CARDS_IN_DECK, getBlackCards().size()); 35 | obj.add(Consts.CardSetData.WHITE_CARDS_IN_DECK, getWhiteCards().size()); 36 | return obj; 37 | } 38 | 39 | @Override 40 | public String toString() { 41 | return String.format("%s[name=%s, base=%b, id=%d, active=%b, weight=%d, black=%d, white=%d]", 42 | getClass().getName(), getName(), isBaseDeck(), getId(), isActive(), getWeight(), 43 | getBlackCards().size(), getWhiteCards().size()); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/cards/PyxBlackCard.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.cards; 2 | 3 | import java.sql.ResultSet; 4 | import java.sql.SQLException; 5 | 6 | public class PyxBlackCard extends BlackCard { 7 | private final int id; 8 | private final String text; 9 | private final int draw; 10 | private final int pick; 11 | private final String watermark; 12 | 13 | public PyxBlackCard(ResultSet resultSet) throws SQLException { 14 | id = resultSet.getInt("id"); 15 | text = resultSet.getString("text"); 16 | draw = resultSet.getInt("draw"); 17 | pick = resultSet.getInt("pick"); 18 | watermark = resultSet.getString("watermark"); 19 | } 20 | 21 | @Override 22 | public int getId() { 23 | return id; 24 | } 25 | 26 | /** 27 | * @return Card text. HTML is allowed and entities are required. 28 | */ 29 | @Override 30 | public String getText() { 31 | return text; 32 | } 33 | 34 | @Override 35 | public int getDraw() { 36 | return draw; 37 | } 38 | 39 | @Override 40 | public int getPick() { 41 | return pick; 42 | } 43 | 44 | @Override 45 | public String getWatermark() { 46 | return watermark == null ? "" : watermark; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/cards/PyxCardSet.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.cards; 2 | 3 | import com.gianlu.pyxreloaded.singletons.LoadedCards; 4 | 5 | import java.sql.ResultSet; 6 | import java.sql.SQLException; 7 | import java.util.ArrayList; 8 | import java.util.HashSet; 9 | import java.util.List; 10 | import java.util.Set; 11 | 12 | public class PyxCardSet extends CardSet { 13 | private final Set blackCards; 14 | private final Set whiteCards; 15 | private final int id; 16 | private final String name; 17 | private final String description; 18 | private final boolean active; 19 | private final boolean base_deck; 20 | private final int weight; 21 | 22 | public PyxCardSet(ResultSet resultSet) throws SQLException { 23 | id = resultSet.getInt("id"); 24 | name = resultSet.getString("name"); 25 | active = resultSet.getInt("active") == 1; 26 | base_deck = resultSet.getInt("base_deck") == 1; 27 | description = resultSet.getString("description"); 28 | weight = resultSet.getInt("weight"); 29 | 30 | blackCards = new HashSet<>(); 31 | whiteCards = new HashSet<>(); 32 | } 33 | 34 | public static List loadCardSets(LoadedCards loadedCards, Set ids) { 35 | List sets = new ArrayList<>(); 36 | for (PyxCardSet set : loadedCards.getLoadedSets()) 37 | if (ids.contains(set.id)) sets.add(set); 38 | 39 | return sets; 40 | } 41 | 42 | @Override 43 | public String getName() { 44 | return name; 45 | } 46 | 47 | @Override 48 | public boolean isActive() { 49 | return active; 50 | } 51 | 52 | @Override 53 | public int getId() { 54 | return id; 55 | } 56 | 57 | @Override 58 | public Set getBlackCards() { 59 | return blackCards; 60 | } 61 | 62 | @Override 63 | public Set getWhiteCards() { 64 | return whiteCards; 65 | } 66 | 67 | @Override 68 | public boolean isBaseDeck() { 69 | return base_deck; 70 | } 71 | 72 | @Override 73 | public String getDescription() { 74 | return description; 75 | } 76 | 77 | @Override 78 | public int getWeight() { 79 | return weight; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/cards/PyxWhiteCard.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.cards; 2 | 3 | import java.sql.ResultSet; 4 | import java.sql.SQLException; 5 | 6 | public class PyxWhiteCard extends WhiteCard { 7 | private final int id; 8 | private final String text; 9 | private final String watermark; 10 | 11 | public PyxWhiteCard(ResultSet resultSet) throws SQLException { 12 | id = resultSet.getInt("id"); 13 | text = resultSet.getString("text"); 14 | watermark = resultSet.getString("watermark"); 15 | } 16 | 17 | @Override 18 | public int getId() { 19 | return id; 20 | } 21 | 22 | /** 23 | * @return Card text. HTML is allowed and entities are required. 24 | */ 25 | @Override 26 | public String getText() { 27 | return text; 28 | } 29 | 30 | @Override 31 | public String getWatermark() { 32 | return watermark == null ? "" : watermark; 33 | } 34 | 35 | @Override 36 | public boolean isWriteIn() { 37 | return false; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/cards/WhiteCard.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.cards; 2 | 3 | import com.gianlu.pyxreloaded.Consts; 4 | import com.gianlu.pyxreloaded.data.JsonWrapper; 5 | 6 | 7 | public abstract class WhiteCard { 8 | public static JsonWrapper getFaceDownCardClientDataJson() { 9 | JsonWrapper obj = new JsonWrapper(); 10 | obj.add(Consts.GeneralKeys.CARD_ID, -1); 11 | obj.add(Consts.WhiteCardData.TEXT, ""); 12 | obj.add(Consts.WhiteCardData.WATERMARK, ""); 13 | obj.add(Consts.WhiteCardData.WRITE_IN, false); 14 | return obj; 15 | } 16 | 17 | public abstract int getId(); 18 | 19 | public abstract String getText(); 20 | 21 | public abstract String getWatermark(); 22 | 23 | public abstract boolean isWriteIn(); 24 | 25 | @Override 26 | public final boolean equals(final Object other) { 27 | return other instanceof WhiteCard && ((WhiteCard) other).getId() == getId(); 28 | } 29 | 30 | @Override 31 | public final int hashCode() { 32 | return getId(); 33 | } 34 | 35 | public final JsonWrapper getClientDataJson() { 36 | JsonWrapper obj = new JsonWrapper(); 37 | obj.add(Consts.GeneralKeys.CARD_ID, getId()); 38 | obj.add(Consts.WhiteCardData.TEXT, getText()); 39 | obj.add(Consts.WhiteCardData.WATERMARK, getWatermark()); 40 | obj.add(Consts.WhiteCardData.WRITE_IN, isWriteIn()); 41 | return obj; 42 | } 43 | 44 | @Override 45 | public String toString() { 46 | return String.format("%s %s (id:%d, watermark:%s)", getClass().getName(), getText(), getId(), getWatermark()); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/cards/WhiteDeck.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.cards; 2 | 3 | import com.gianlu.pyxreloaded.game.OutOfCardsException; 4 | 5 | import java.util.*; 6 | 7 | 8 | /** 9 | * Deck of White Cards. 10 | *

    11 | * This class is thread-safe. 12 | * 13 | * @author Andy Janata (ajanata@socialgamer.net) 14 | */ 15 | public class WhiteDeck { 16 | private final List deck; 17 | private final List discard; 18 | private int lastBlankCardId = -1; 19 | 20 | /** 21 | * Create a new white card deck, loading the cards from the database and shuffling them. 22 | */ 23 | public WhiteDeck(Collection cardSets, int numBlanks) { 24 | Set allCards = new HashSet<>(); 25 | for (CardSet cardSet : cardSets) allCards.addAll(cardSet.getWhiteCards()); 26 | deck = new ArrayList<>(allCards); 27 | for (int i = 0; i < numBlanks; i++) deck.add(createBlankCard()); 28 | Collections.shuffle(deck); 29 | discard = new ArrayList<>(deck.size()); 30 | } 31 | 32 | /** 33 | * Checks if a particular card is a blank card. 34 | * 35 | * @param card Card to check. 36 | * @return True if the card is a blank card. 37 | */ 38 | public static boolean isBlankCard(WhiteCard card) { 39 | return card instanceof BlankWhiteCard; 40 | } 41 | 42 | /** 43 | * Get the next card from the top of deck. 44 | * 45 | * @return The next card. 46 | * @throws OutOfCardsException There are no more cards in the deck. 47 | */ 48 | public synchronized WhiteCard getNextCard() throws OutOfCardsException { 49 | if (deck.size() == 0) throw new OutOfCardsException(); 50 | // we have an ArrayList here, so this is faster 51 | return deck.remove(deck.size() - 1); 52 | } 53 | 54 | /** 55 | * Add a card to the discard pile. 56 | * 57 | * @param card Card to add to discard pile. 58 | */ 59 | public synchronized void discard(WhiteCard card) { 60 | if (card != null) { 61 | // clear any player text 62 | if (isBlankCard(card)) ((BlankWhiteCard) card).clear(); 63 | 64 | discard.add(card); 65 | } 66 | } 67 | 68 | /** 69 | * Shuffles the discard pile and puts the cards under the cards remaining in the deck. 70 | */ 71 | public synchronized void reshuffle() { 72 | Collections.shuffle(discard); 73 | deck.addAll(0, discard); 74 | discard.clear(); 75 | } 76 | 77 | /** 78 | * Creates a new blank card. 79 | * 80 | * @return A newly created blank card. 81 | */ 82 | public WhiteCard createBlankCard() { 83 | return new BlankWhiteCard(--lastBlankCardId); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/data/EventWrapper.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.data; 2 | 3 | import com.gianlu.pyxreloaded.Consts; 4 | import com.gianlu.pyxreloaded.game.Game; 5 | 6 | public class EventWrapper extends JsonWrapper { 7 | 8 | public EventWrapper(Game game, Consts.Event event) { 9 | this(event); 10 | add(Consts.GeneralKeys.GAME_ID, game.getId()); 11 | } 12 | 13 | public EventWrapper(Consts.Event event) { 14 | add(Consts.GeneralKeys.EVENT, event.toString()); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/data/JsonWrapper.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.data; 2 | 3 | import com.gianlu.pyxreloaded.Consts; 4 | import com.gianlu.pyxreloaded.Utils; 5 | import com.google.gson.JsonElement; 6 | import com.google.gson.JsonObject; 7 | import org.jetbrains.annotations.NotNull; 8 | import org.jetbrains.annotations.Nullable; 9 | 10 | import java.util.Map; 11 | 12 | public class JsonWrapper { 13 | public static final JsonWrapper EMPTY = new JsonWrapper(); 14 | private final JsonObject obj; 15 | 16 | public JsonWrapper() { 17 | obj = new JsonObject(); 18 | } 19 | 20 | public JsonWrapper(Consts.ReturnableKey data, JsonElement element) { 21 | this(); 22 | add(data, element); 23 | } 24 | 25 | public JsonWrapper(Consts.ReturnableKey data, int i) { 26 | this(); 27 | add(data, i); 28 | } 29 | 30 | public JsonWrapper(Consts.ReturnableKey data, String str) { 31 | this(); 32 | add(data, str); 33 | } 34 | 35 | public JsonWrapper(Consts.ErrorCode code) { 36 | this(); 37 | add(Consts.GeneralKeys.ERROR, true); 38 | add(Consts.GeneralKeys.ERROR_CODE, code.toString()); 39 | } 40 | 41 | @NotNull 42 | public static JsonWrapper from(@NotNull Map map, @Nullable String[] keys) { 43 | JsonWrapper wrapper = new JsonWrapper(); 44 | for (String key : map.keySet()) { 45 | if (keys == null || Utils.contains(keys, key)) 46 | wrapper.obj.addProperty(key, map.get(key)); 47 | } 48 | return wrapper; 49 | } 50 | 51 | public JsonObject obj() { 52 | return obj; 53 | } 54 | 55 | public JsonWrapper add(Consts.ReturnableKey data, JsonElement element) { 56 | obj.add(data.toString(), element); 57 | return this; 58 | } 59 | 60 | public JsonWrapper add(Consts.ReturnableKey data, JsonWrapper wrapper) { 61 | add(data, wrapper == null ? null : wrapper.obj()); 62 | return this; 63 | } 64 | 65 | public JsonWrapper add(Consts.ReturnableKey data, boolean bool) { 66 | obj.addProperty(data.toString(), bool); 67 | return this; 68 | } 69 | 70 | public JsonWrapper add(Consts.ReturnableKey data, Number i) { 71 | obj.addProperty(data.toString(), i); 72 | return this; 73 | } 74 | 75 | public JsonWrapper add(Consts.ReturnableKey data, String str) { 76 | obj.addProperty(data.toString(), str); 77 | return this; 78 | } 79 | 80 | @Override 81 | public String toString() { 82 | return obj.toString(); 83 | } 84 | 85 | public void addAll(JsonWrapper data) { 86 | if (data == null) return; 87 | for (String key : data.obj.keySet()) obj.add(key, data.obj.get(key)); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/data/QueuedMessage.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.data; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | public class QueuedMessage implements Comparable { 6 | private final MessageType messageType; 7 | private final EventWrapper ev; 8 | 9 | /** 10 | * Create a new queued message. 11 | * 12 | * @param messageType Type of message to be queued. The type influences the priority in returning messages 13 | * to the client. 14 | * @param ev The data of the message to be queued. 15 | */ 16 | public QueuedMessage(MessageType messageType, EventWrapper ev) { 17 | this.messageType = messageType; 18 | this.ev = ev; 19 | } 20 | 21 | /** 22 | * @return The data in the message. 23 | */ 24 | public EventWrapper getData() { 25 | return ev; 26 | } 27 | 28 | /** 29 | * This is not guaranteed to be consistent with .equals() since we do not care about the data for 30 | * ordering. 31 | */ 32 | @Override 33 | public int compareTo(@NotNull QueuedMessage qm) { 34 | return this.messageType.getWeight() - qm.messageType.getWeight(); 35 | } 36 | 37 | @Override 38 | public String toString() { 39 | return messageType.toString() + "_" + ev.toString(); 40 | } 41 | 42 | /** 43 | * Types of messages that can be queued. The numerical value is the priority that this message 44 | * should be delivered (lower = more important) compared to other queued messages. 45 | */ 46 | public enum MessageType { 47 | SERVER(1), KICKED(2), PLAYER_EVENT(3), GAME_EVENT(4), GAME_PLAYER_EVENT(5), CHAT(6); 48 | 49 | private final int weight; 50 | 51 | MessageType(int weight) { 52 | this.weight = weight; 53 | } 54 | 55 | public int getWeight() { 56 | return weight; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/data/accounts/FacebookAccount.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.data.accounts; 2 | 3 | import com.gianlu.pyxreloaded.Consts; 4 | import com.gianlu.pyxreloaded.socials.facebook.FacebookProfileInfo; 5 | import com.gianlu.pyxreloaded.socials.facebook.FacebookToken; 6 | 7 | import java.sql.ResultSet; 8 | import java.sql.SQLException; 9 | import java.text.ParseException; 10 | 11 | public class FacebookAccount extends UserAccount { 12 | public final String userId; 13 | 14 | public FacebookAccount(ResultSet user) throws SQLException, ParseException { 15 | super(user, true); // Cannot even register without a verified email 16 | 17 | userId = user.getString("facebook_user_id"); 18 | } 19 | 20 | public FacebookAccount(String nickname, FacebookToken token, FacebookProfileInfo info) { 21 | super(nickname, info.email, Consts.AuthType.FACEBOOK, true, info.pictureUrl); 22 | 23 | userId = token.userId; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/data/accounts/GithubAccount.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.data.accounts; 2 | 3 | import com.gianlu.pyxreloaded.Consts; 4 | import com.gianlu.pyxreloaded.socials.github.GithubProfileInfo; 5 | 6 | import java.sql.ResultSet; 7 | import java.sql.SQLException; 8 | import java.text.ParseException; 9 | 10 | public class GithubAccount extends UserAccount { 11 | public final String id; 12 | 13 | public GithubAccount(ResultSet user, GithubProfileInfo info) throws SQLException, ParseException { 14 | super(user, info.emails.isPrimaryEmailVerified()); 15 | 16 | id = user.getString("github_user_id"); 17 | } 18 | 19 | public GithubAccount(String nickname, GithubProfileInfo info) { 20 | super(nickname, info.email, Consts.AuthType.GITHUB, info.emails.isPrimaryEmailVerified(), info.avatarUrl); 21 | 22 | id = info.id; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/data/accounts/GoogleAccount.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.data.accounts; 2 | 3 | import com.gianlu.pyxreloaded.Consts; 4 | import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken; 5 | 6 | import java.sql.ResultSet; 7 | import java.sql.SQLException; 8 | import java.text.ParseException; 9 | 10 | public class GoogleAccount extends UserAccount { 11 | public final String subject; 12 | 13 | public GoogleAccount(ResultSet user, GoogleIdToken.Payload token) throws SQLException, ParseException { 14 | super(user, token.getEmailVerified()); 15 | 16 | subject = user.getString("google_sub"); 17 | } 18 | 19 | public GoogleAccount(String nickname, GoogleIdToken.Payload token) { 20 | super(nickname, token.getEmail(), Consts.AuthType.GOOGLE, token.getEmailVerified(), (String) token.getOrDefault("picture", null)); 21 | 22 | this.subject = token.getSubject(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/data/accounts/PasswordAccount.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.data.accounts; 2 | 3 | import com.gianlu.pyxreloaded.Consts; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | import java.sql.ResultSet; 7 | import java.sql.SQLException; 8 | import java.text.ParseException; 9 | 10 | public class PasswordAccount extends UserAccount { 11 | public final String hashedPassword; 12 | 13 | public PasswordAccount(ResultSet user) throws SQLException, ParseException { 14 | super(user, user.getBoolean("email_verified")); 15 | 16 | hashedPassword = user.getString("password"); 17 | } 18 | 19 | public PasswordAccount(String username, String email, boolean emailVerified, @NotNull String hashedPassword) { 20 | super(username, email, Consts.AuthType.PASSWORD, emailVerified, null); // TODO: Avatar 21 | 22 | this.hashedPassword = hashedPassword; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/data/accounts/TwitterAccount.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.data.accounts; 2 | 3 | import com.gianlu.pyxreloaded.Consts; 4 | import com.gianlu.pyxreloaded.socials.twitter.TwitterProfileInfo; 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | import java.sql.ResultSet; 8 | import java.sql.SQLException; 9 | import java.text.ParseException; 10 | 11 | public class TwitterAccount extends UserAccount { 12 | public final String id; 13 | 14 | public TwitterAccount(ResultSet user) throws SQLException, ParseException { 15 | super(user, true); // Cannot even register without a verified email 16 | 17 | id = user.getString("twitter_user_id"); 18 | } 19 | 20 | public TwitterAccount(String nickname, @NotNull TwitterProfileInfo info) { 21 | super(nickname, info.email, Consts.AuthType.TWITTER, true, info.avatarUrl); 22 | 23 | id = info.id; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/data/accounts/UserAccount.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.data.accounts; 2 | 3 | import com.gianlu.pyxreloaded.Consts; 4 | import com.gianlu.pyxreloaded.data.JsonWrapper; 5 | import org.jetbrains.annotations.NotNull; 6 | import org.jetbrains.annotations.Nullable; 7 | 8 | import java.sql.ResultSet; 9 | import java.sql.SQLException; 10 | import java.text.ParseException; 11 | import java.util.HashMap; 12 | import java.util.Map; 13 | 14 | public abstract class UserAccount { 15 | public final String username; 16 | public final boolean admin; 17 | public final String email; 18 | public final String avatarUrl; 19 | public final boolean emailVerified; 20 | public final Preferences preferences = new Preferences(); 21 | private final Consts.AuthType auth; 22 | 23 | UserAccount(ResultSet set, boolean emailVerified) throws SQLException, ParseException { 24 | this.username = set.getString("username"); 25 | this.email = set.getString("email"); 26 | this.auth = Consts.AuthType.parse(set.getString("auth")); 27 | this.admin = set.getBoolean("admin"); 28 | this.avatarUrl = set.getString("avatar_url"); 29 | this.emailVerified = emailVerified; 30 | } 31 | 32 | UserAccount(String username, String email, Consts.AuthType auth, boolean emailVerified, @Nullable String avatarUrl) { 33 | this.username = username; 34 | this.email = email; 35 | this.auth = auth; 36 | this.avatarUrl = avatarUrl; 37 | this.admin = false; 38 | this.emailVerified = emailVerified; 39 | } 40 | 41 | public JsonWrapper toJson() { 42 | JsonWrapper obj = new JsonWrapper(); 43 | obj.add(Consts.UserData.EMAIL, email); 44 | obj.add(Consts.GeneralKeys.AUTH_TYPE, auth.toString()); 45 | obj.add(Consts.UserData.PICTURE, avatarUrl); 46 | obj.add(Consts.UserData.NICKNAME, username); 47 | obj.add(Consts.UserData.EMAIL_VERIFIED, emailVerified); 48 | obj.add(Consts.UserData.IS_ADMIN, admin); 49 | return obj; 50 | } 51 | 52 | public void loadPreferences(@NotNull ResultSet prefs) throws SQLException { 53 | preferences.load(prefs); 54 | } 55 | 56 | public void updatePreferences(@NotNull Map map) { 57 | preferences.update(map); 58 | } 59 | 60 | public class Preferences extends HashMap { 61 | 62 | private Preferences() { 63 | } 64 | 65 | private void load(@NotNull ResultSet set) throws SQLException { 66 | while (set.next()) put(set.getString("key"), set.getString("value")); 67 | } 68 | 69 | @NotNull 70 | public JsonWrapper toJson(@Nullable String[] keys) { 71 | return JsonWrapper.from(this, keys); 72 | } 73 | 74 | private void update(@NotNull Map map) { 75 | for (String key : map.keySet()) { 76 | if (Consts.isPreferenceKeyValid(key)) 77 | put(key, map.get(key)); 78 | } 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/game/OutOfCardsException.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.game; 2 | 3 | /** 4 | * An exception to be thrown when a deck is out of cards in the draw stack. 5 | */ 6 | public class OutOfCardsException extends Exception { 7 | private static final long serialVersionUID = -1797946826947407779L; 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/game/Player.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.game; 2 | 3 | import com.gianlu.pyxreloaded.cards.WhiteCard; 4 | import com.gianlu.pyxreloaded.data.User; 5 | 6 | import java.util.LinkedList; 7 | import java.util.List; 8 | 9 | 10 | /** 11 | * Data required for a player in a {@code Game}. 12 | * 13 | * @author Andy Janata (ajanata@socialgamer.net) 14 | */ 15 | public class Player { 16 | public final List hand = new LinkedList<>(); 17 | private final User user; 18 | private int score = 0; 19 | private int skipCount = 0; 20 | 21 | /** 22 | * Create a new player object. 23 | * 24 | * @param user The {@code User} associated with this player. 25 | */ 26 | public Player(User user) { 27 | this.user = user; 28 | } 29 | 30 | /** 31 | * @return The {@code User} associated with this player. 32 | */ 33 | public User getUser() { 34 | return user; 35 | } 36 | 37 | /** 38 | * @return The player's score. 39 | */ 40 | public int getScore() { 41 | return score; 42 | } 43 | 44 | /** 45 | * Increase the player's score by 1 point. 46 | */ 47 | public void increaseScore() { 48 | score++; 49 | } 50 | 51 | /** 52 | * Reset the player's score to 0. 53 | */ 54 | public void resetScore() { 55 | score = 0; 56 | } 57 | 58 | /** 59 | * Increases this player's skipped round count. 60 | */ 61 | public void skipped() { 62 | skipCount++; 63 | } 64 | 65 | /** 66 | * Reset this player's skipped round count to 0, because they have been back for a round. 67 | */ 68 | public void resetSkipCount() { 69 | skipCount = 0; 70 | } 71 | 72 | /** 73 | * @return This player's skipped round count. 74 | */ 75 | public int getSkipCount() { 76 | return skipCount; 77 | } 78 | 79 | @Override 80 | public String toString() { 81 | return String.format("%s (%dp, %ds)", user.toString(), score, skipCount); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/game/SuggestedGameOptions.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.game; 2 | 3 | import com.gianlu.pyxreloaded.Consts; 4 | import com.gianlu.pyxreloaded.data.JsonWrapper; 5 | import com.gianlu.pyxreloaded.data.User; 6 | import com.gianlu.pyxreloaded.singletons.Preferences; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | public class SuggestedGameOptions extends GameOptions { 10 | private final User suggester; 11 | 12 | public SuggestedGameOptions(Preferences preferences, @NotNull User user, String text) { 13 | super(preferences, text); 14 | suggester = user; 15 | } 16 | 17 | public User getSuggester() { 18 | return suggester; 19 | } 20 | 21 | public JsonWrapper toJson(String id, boolean includePassword) { 22 | JsonWrapper wrapper = new JsonWrapper(); 23 | wrapper.add(Consts.GameOptionsData.OPTIONS, super.toJson(includePassword)); 24 | wrapper.add(Consts.GameSuggestedOptionsData.ID, id); 25 | wrapper.add(Consts.GameSuggestedOptionsData.SUGGESTER, suggester.getNickname()); 26 | return wrapper; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/handlers/BanHandler.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.handlers; 2 | 3 | import com.gianlu.pyxreloaded.Consts; 4 | import com.gianlu.pyxreloaded.data.JsonWrapper; 5 | import com.gianlu.pyxreloaded.data.User; 6 | import com.gianlu.pyxreloaded.server.Annotations; 7 | import com.gianlu.pyxreloaded.server.BaseCahHandler; 8 | import com.gianlu.pyxreloaded.server.Parameters; 9 | import com.gianlu.pyxreloaded.singletons.ConnectedUsers; 10 | import io.undertow.server.HttpServerExchange; 11 | import org.jetbrains.annotations.NotNull; 12 | 13 | public class BanHandler extends BaseHandler { 14 | public static final String OP = Consts.Operation.BAN.toString(); 15 | private final ConnectedUsers connectedUsers; 16 | 17 | public BanHandler(@Annotations.ConnectedUsers ConnectedUsers connectedUsers) { 18 | this.connectedUsers = connectedUsers; 19 | } 20 | 21 | @NotNull 22 | @Override 23 | public JsonWrapper handle(User user, Parameters params, HttpServerExchange exchange) throws BaseCahHandler.CahException { 24 | if (!user.isAdmin()) throw new BaseCahHandler.CahException(Consts.ErrorCode.NOT_ADMIN); 25 | 26 | String nickname = params.getStringNotNull(Consts.UserData.NICKNAME); 27 | if (nickname.isEmpty()) throw new BaseCahHandler.CahException(Consts.ErrorCode.BAD_REQUEST); 28 | 29 | User target = connectedUsers.getUser(nickname); 30 | if (target == null) throw new BaseCahHandler.CahException(Consts.ErrorCode.NO_SUCH_USER); 31 | connectedUsers.banUser(target); 32 | 33 | return JsonWrapper.EMPTY; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/handlers/BaseHandler.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.handlers; 2 | 3 | import com.gianlu.pyxreloaded.data.JsonWrapper; 4 | import com.gianlu.pyxreloaded.data.User; 5 | import com.gianlu.pyxreloaded.server.BaseJsonHandler; 6 | import com.gianlu.pyxreloaded.server.Parameters; 7 | import io.undertow.server.HttpServerExchange; 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | public abstract class BaseHandler { 11 | @NotNull 12 | public abstract JsonWrapper handle(User user, Parameters params, HttpServerExchange exchange) throws BaseJsonHandler.StatusException; 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/handlers/CardcastAddCardsetHandler.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.handlers; 2 | 3 | import com.gianlu.pyxreloaded.Consts; 4 | import com.gianlu.pyxreloaded.cardcast.CardcastDeck; 5 | import com.gianlu.pyxreloaded.cardcast.CardcastService; 6 | import com.gianlu.pyxreloaded.data.EventWrapper; 7 | import com.gianlu.pyxreloaded.data.JsonWrapper; 8 | import com.gianlu.pyxreloaded.data.QueuedMessage; 9 | import com.gianlu.pyxreloaded.data.User; 10 | import com.gianlu.pyxreloaded.game.Game; 11 | import com.gianlu.pyxreloaded.server.Annotations; 12 | import com.gianlu.pyxreloaded.server.BaseCahHandler; 13 | import com.gianlu.pyxreloaded.server.Parameters; 14 | import com.gianlu.pyxreloaded.singletons.GamesManager; 15 | import io.undertow.server.HttpServerExchange; 16 | import org.jetbrains.annotations.NotNull; 17 | 18 | public class CardcastAddCardsetHandler extends GameWithPlayerHandler { 19 | public static final String OP = Consts.Operation.CARDCAST_ADD_CARDSET.toString(); 20 | private final CardcastService cardcastService; 21 | 22 | public CardcastAddCardsetHandler(@Annotations.GameManager GamesManager gamesManager, @Annotations.CardcastService CardcastService cardcastService) { 23 | super(gamesManager); 24 | this.cardcastService = cardcastService; 25 | } 26 | 27 | @NotNull 28 | @Override 29 | public JsonWrapper handleWithUserInGame(User user, Game game, Parameters params, HttpServerExchange exchange) throws BaseCahHandler.CahException { 30 | if (game.getHost() != user) throw new BaseCahHandler.CahException(Consts.ErrorCode.NOT_GAME_HOST); 31 | if (game.getState() != Consts.GameState.LOBBY) 32 | throw new BaseCahHandler.CahException(Consts.ErrorCode.ALREADY_STARTED); 33 | 34 | String deckId = params.getStringNotNull(Consts.GeneralKeys.CARDCAST_ID); 35 | if (deckId.isEmpty()) throw new BaseCahHandler.CahException(Consts.ErrorCode.BAD_REQUEST); 36 | if (deckId.length() != 5) throw new BaseCahHandler.CahException(Consts.ErrorCode.CARDCAST_INVALID_ID); 37 | deckId = deckId.toUpperCase(); 38 | 39 | CardcastDeck deck = cardcastService.loadSet(deckId); 40 | if (deck == null) throw new BaseCahHandler.CahException(Consts.ErrorCode.CARDCAST_CANNOT_FIND); 41 | 42 | if (game.getCardcastDeckCodes().add(deckId)) { 43 | EventWrapper ev = new EventWrapper(game, Consts.Event.CARDCAST_ADD_CARDSET); 44 | ev.add(Consts.GeneralKeys.CARDCAST_DECK_INFO, deck.getClientMetadataJson()); 45 | game.broadcastToPlayers(QueuedMessage.MessageType.GAME_EVENT, ev); 46 | } 47 | 48 | return JsonWrapper.EMPTY; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/handlers/CardcastListCardsetsHandler.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.handlers; 2 | 3 | import com.gianlu.pyxreloaded.Consts; 4 | import com.gianlu.pyxreloaded.cardcast.CardcastDeck; 5 | import com.gianlu.pyxreloaded.cardcast.CardcastService; 6 | import com.gianlu.pyxreloaded.cardcast.FailedLoadingSomeCardcastDecks; 7 | import com.gianlu.pyxreloaded.data.JsonWrapper; 8 | import com.gianlu.pyxreloaded.data.User; 9 | import com.gianlu.pyxreloaded.game.Game; 10 | import com.gianlu.pyxreloaded.server.Annotations; 11 | import com.gianlu.pyxreloaded.server.BaseCahHandler; 12 | import com.gianlu.pyxreloaded.server.Parameters; 13 | import com.gianlu.pyxreloaded.singletons.GamesManager; 14 | import com.google.gson.JsonArray; 15 | import io.undertow.server.HttpServerExchange; 16 | import org.jetbrains.annotations.NotNull; 17 | 18 | public class CardcastListCardsetsHandler extends GameWithPlayerHandler { 19 | public static final String OP = Consts.Operation.CARDCAST_LIST_CARDSETS.toString(); 20 | private final CardcastService cardcastService; 21 | 22 | public CardcastListCardsetsHandler(@Annotations.GameManager GamesManager gamesManager, @Annotations.CardcastService CardcastService cardcastService) { 23 | super(gamesManager); 24 | this.cardcastService = cardcastService; 25 | } 26 | 27 | @NotNull 28 | @Override 29 | public JsonWrapper handleWithUserInGame(User user, Game game, Parameters params, HttpServerExchange exchange) throws BaseCahHandler.CahException { 30 | JsonArray array = new JsonArray(); 31 | 32 | FailedLoadingSomeCardcastDecks cardcastException = null; 33 | for (String deckId : game.getCardcastDeckCodes().toArray(new String[0])) { 34 | CardcastDeck deck = cardcastService.loadSet(deckId); 35 | if (deck == null) { 36 | if (cardcastException == null) cardcastException = new FailedLoadingSomeCardcastDecks(); 37 | cardcastException.failedDecks.add(deckId); 38 | } 39 | 40 | if (deck != null) array.add(deck.getClientMetadataJson().obj()); 41 | } 42 | 43 | if (cardcastException != null) { 44 | throw new BaseCahHandler.CahException(Consts.ErrorCode.CARDCAST_CANNOT_FIND, 45 | new JsonWrapper(Consts.GeneralKeys.CARDCAST_ID, cardcastException.getFailedJson())); 46 | } else { 47 | return new JsonWrapper(Consts.GameOptionsData.CARD_SETS, array); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/handlers/CardcastRemoveCardsetHandler.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.handlers; 2 | 3 | import com.gianlu.pyxreloaded.Consts; 4 | import com.gianlu.pyxreloaded.cardcast.CardcastDeck; 5 | import com.gianlu.pyxreloaded.cardcast.CardcastService; 6 | import com.gianlu.pyxreloaded.data.EventWrapper; 7 | import com.gianlu.pyxreloaded.data.JsonWrapper; 8 | import com.gianlu.pyxreloaded.data.QueuedMessage; 9 | import com.gianlu.pyxreloaded.data.User; 10 | import com.gianlu.pyxreloaded.game.Game; 11 | import com.gianlu.pyxreloaded.server.Annotations; 12 | import com.gianlu.pyxreloaded.server.BaseCahHandler; 13 | import com.gianlu.pyxreloaded.server.Parameters; 14 | import com.gianlu.pyxreloaded.singletons.GamesManager; 15 | import io.undertow.server.HttpServerExchange; 16 | import org.jetbrains.annotations.NotNull; 17 | 18 | public class CardcastRemoveCardsetHandler extends GameWithPlayerHandler { 19 | public static final String OP = Consts.Operation.CARDCAST_REMOVE_CARDSET.toString(); 20 | private final CardcastService cardcastService; 21 | 22 | public CardcastRemoveCardsetHandler(@Annotations.GameManager GamesManager gamesManager, @Annotations.CardcastService CardcastService cardcastService) { 23 | super(gamesManager); 24 | this.cardcastService = cardcastService; 25 | } 26 | 27 | @NotNull 28 | @Override 29 | public JsonWrapper handleWithUserInGame(User user, Game game, Parameters params, HttpServerExchange exchange) throws BaseCahHandler.CahException { 30 | if (game.getHost() != user) throw new BaseCahHandler.CahException(Consts.ErrorCode.NOT_GAME_HOST); 31 | if (game.getState() != Consts.GameState.LOBBY) 32 | throw new BaseCahHandler.CahException(Consts.ErrorCode.ALREADY_STARTED); 33 | 34 | String deckId = params.getStringNotNull(Consts.GeneralKeys.CARDCAST_ID); 35 | if (deckId.isEmpty()) throw new BaseCahHandler.CahException(Consts.ErrorCode.BAD_REQUEST); 36 | if (deckId.length() != 5) throw new BaseCahHandler.CahException(Consts.ErrorCode.CARDCAST_INVALID_ID); 37 | deckId = deckId.toUpperCase(); 38 | 39 | // Remove it from the set regardless if it loads or not. 40 | if (game.getCardcastDeckCodes().remove(deckId)) { 41 | CardcastDeck deck = cardcastService.loadSet(deckId); 42 | if (deck == null) throw new BaseCahHandler.CahException(Consts.ErrorCode.CARDCAST_CANNOT_FIND); 43 | 44 | EventWrapper ev = new EventWrapper(game, Consts.Event.CARDCAST_REMOVE_CARDSET); 45 | ev.add(Consts.GeneralKeys.CARDCAST_DECK_INFO, deck.getClientMetadataJson()); 46 | game.broadcastToPlayers(QueuedMessage.MessageType.GAME_EVENT, ev); 47 | } 48 | 49 | return JsonWrapper.EMPTY; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/handlers/ChangeGameOptionsHandler.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.handlers; 2 | 3 | import com.gianlu.pyxreloaded.Consts; 4 | import com.gianlu.pyxreloaded.data.JsonWrapper; 5 | import com.gianlu.pyxreloaded.data.User; 6 | import com.gianlu.pyxreloaded.game.Game; 7 | import com.gianlu.pyxreloaded.game.GameOptions; 8 | import com.gianlu.pyxreloaded.game.SuggestedGameOptions; 9 | import com.gianlu.pyxreloaded.server.Annotations; 10 | import com.gianlu.pyxreloaded.server.BaseCahHandler; 11 | import com.gianlu.pyxreloaded.server.Parameters; 12 | import com.gianlu.pyxreloaded.singletons.GamesManager; 13 | import com.gianlu.pyxreloaded.singletons.Preferences; 14 | import io.undertow.server.HttpServerExchange; 15 | import org.jetbrains.annotations.NotNull; 16 | 17 | public class ChangeGameOptionsHandler extends GameWithPlayerHandler { 18 | public static final String OP = Consts.Operation.CHANGE_GAME_OPTIONS.toString(); 19 | private final Preferences preferences; 20 | 21 | public ChangeGameOptionsHandler(@Annotations.GameManager GamesManager gamesManager, @Annotations.Preferences Preferences preferences) { 22 | super(gamesManager); 23 | this.preferences = preferences; 24 | } 25 | 26 | @NotNull 27 | @Override 28 | public JsonWrapper handleWithUserInGame(User user, Game game, Parameters params, HttpServerExchange exchange) throws BaseCahHandler.CahException { 29 | if (game.getState() != Consts.GameState.LOBBY) 30 | throw new BaseCahHandler.CahException(Consts.ErrorCode.ALREADY_STARTED); 31 | 32 | User host = game.getHost(); 33 | if (host == null) return JsonWrapper.EMPTY; 34 | 35 | String value = params.getStringNotNull(Consts.GameOptionsData.OPTIONS); 36 | if (value.isEmpty()) throw new BaseCahHandler.CahException(Consts.ErrorCode.BAD_REQUEST); 37 | 38 | if (host == user) { 39 | game.updateGameSettings(new GameOptions(preferences, value)); 40 | return JsonWrapper.EMPTY; 41 | } else { 42 | game.suggestGameOptionsModification(new SuggestedGameOptions(preferences, user, value)); 43 | return new JsonWrapper(Consts.GameInfoData.HOST, host.getNickname()); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/handlers/ChatHandler.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.handlers; 2 | 3 | import com.gianlu.pyxreloaded.Consts; 4 | import com.gianlu.pyxreloaded.data.EventWrapper; 5 | import com.gianlu.pyxreloaded.data.JsonWrapper; 6 | import com.gianlu.pyxreloaded.data.QueuedMessage.MessageType; 7 | import com.gianlu.pyxreloaded.data.User; 8 | import com.gianlu.pyxreloaded.server.Annotations; 9 | import com.gianlu.pyxreloaded.server.BaseCahHandler; 10 | import com.gianlu.pyxreloaded.server.BaseJsonHandler; 11 | import com.gianlu.pyxreloaded.server.Parameters; 12 | import com.gianlu.pyxreloaded.singletons.ConnectedUsers; 13 | import com.gianlu.pyxreloaded.singletons.Preferences; 14 | import io.undertow.server.HttpServerExchange; 15 | import org.jetbrains.annotations.NotNull; 16 | 17 | public class ChatHandler extends BaseHandler { 18 | public static final String OP = Consts.Operation.CHAT.toString(); 19 | private final ConnectedUsers users; 20 | private final boolean registeredOnly; 21 | 22 | public ChatHandler(@Annotations.Preferences Preferences preferences, 23 | @Annotations.ConnectedUsers ConnectedUsers users) { 24 | this.users = users; 25 | this.registeredOnly = preferences.getBoolean("chat/registeredOnly", false); 26 | } 27 | 28 | @NotNull 29 | @Override 30 | public JsonWrapper handle(User user, Parameters params, HttpServerExchange exchange) throws BaseJsonHandler.StatusException { 31 | users.checkChatFlood(user); 32 | 33 | if (registeredOnly && !user.isEmailVerified()) 34 | throw new BaseCahHandler.CahException(Consts.ErrorCode.ACCOUNT_NOT_VERIFIED); 35 | 36 | String msg = params.getStringNotNull(Consts.ChatData.MESSAGE); 37 | if (msg.isEmpty()) throw new BaseCahHandler.CahException(Consts.ErrorCode.BAD_REQUEST); 38 | 39 | if (msg.length() > Consts.CHAT_MAX_LENGTH) { 40 | throw new BaseCahHandler.CahException(Consts.ErrorCode.MESSAGE_TOO_LONG); 41 | } else if (!users.runChatCommand(user, msg)) { 42 | user.getLastMessageTimes().add(System.currentTimeMillis()); 43 | EventWrapper ev = new EventWrapper(Consts.Event.CHAT); 44 | ev.add(Consts.ChatData.FROM, user.getNickname()); 45 | ev.add(Consts.ChatData.MESSAGE, msg); 46 | ev.add(Consts.ChatData.FROM_ADMIN, user.isAdmin()); 47 | if (user.getAccount() != null) ev.add(Consts.UserData.PICTURE, user.getAccount().avatarUrl); 48 | 49 | users.broadcastToAll(MessageType.CHAT, ev); 50 | } 51 | 52 | return JsonWrapper.EMPTY; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/handlers/CreateGameHandler.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.handlers; 2 | 3 | import com.gianlu.pyxreloaded.Consts; 4 | import com.gianlu.pyxreloaded.data.JsonWrapper; 5 | import com.gianlu.pyxreloaded.data.User; 6 | import com.gianlu.pyxreloaded.game.GameOptions; 7 | import com.gianlu.pyxreloaded.server.Annotations; 8 | import com.gianlu.pyxreloaded.server.BaseJsonHandler; 9 | import com.gianlu.pyxreloaded.server.Parameters; 10 | import com.gianlu.pyxreloaded.singletons.GamesManager; 11 | import com.gianlu.pyxreloaded.singletons.Preferences; 12 | import io.undertow.server.HttpServerExchange; 13 | import org.jetbrains.annotations.NotNull; 14 | 15 | public class CreateGameHandler extends BaseHandler { 16 | public static final String OP = Consts.Operation.CREATE_GAME.toString(); 17 | private final Preferences preferences; 18 | private final GamesManager gamesManager; 19 | 20 | public CreateGameHandler(@Annotations.Preferences Preferences preferences, @Annotations.GameManager GamesManager gamesManager) { 21 | this.preferences = preferences; 22 | this.gamesManager = gamesManager; 23 | } 24 | 25 | @NotNull 26 | @Override 27 | public JsonWrapper handle(User user, Parameters params, HttpServerExchange exchange) throws BaseJsonHandler.StatusException { 28 | String value = params.getStringNotNull(Consts.GameOptionsData.OPTIONS); 29 | GameOptions options = new GameOptions(preferences, value); 30 | return new JsonWrapper(Consts.GeneralKeys.GAME_ID, gamesManager.createGameWithPlayer(user, options).getId()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/handlers/DislikeGameHandler.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.handlers; 2 | 3 | import com.gianlu.pyxreloaded.Consts; 4 | import com.gianlu.pyxreloaded.data.JsonWrapper; 5 | import com.gianlu.pyxreloaded.data.User; 6 | import com.gianlu.pyxreloaded.game.Game; 7 | import com.gianlu.pyxreloaded.server.Annotations; 8 | import com.gianlu.pyxreloaded.server.Parameters; 9 | import com.gianlu.pyxreloaded.singletons.GamesManager; 10 | import io.undertow.server.HttpServerExchange; 11 | import org.jetbrains.annotations.NotNull; 12 | 13 | public class DislikeGameHandler extends GameHandler { 14 | public static final String OP = Consts.Operation.DISLIKE.toString(); 15 | 16 | public DislikeGameHandler(@Annotations.GameManager GamesManager gamesManager) { 17 | super(gamesManager); 18 | } 19 | 20 | @NotNull 21 | @Override 22 | public JsonWrapper handle(User user, Game game, Parameters params, HttpServerExchange exchange) { 23 | game.toggleDislikeGame(user); 24 | return game.getLikesInfoJson(user); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/handlers/FirstLoadHandler.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.handlers; 2 | 3 | import com.gianlu.pyxreloaded.Consts; 4 | import com.gianlu.pyxreloaded.cards.PyxCardSet; 5 | import com.gianlu.pyxreloaded.data.JsonWrapper; 6 | import com.gianlu.pyxreloaded.data.User; 7 | import com.gianlu.pyxreloaded.game.GameOptions; 8 | import com.gianlu.pyxreloaded.server.Annotations; 9 | import com.gianlu.pyxreloaded.server.Parameters; 10 | import com.gianlu.pyxreloaded.singletons.Emails; 11 | import com.gianlu.pyxreloaded.singletons.LoadedCards; 12 | import com.gianlu.pyxreloaded.singletons.Preferences; 13 | import com.gianlu.pyxreloaded.singletons.SocialLogin; 14 | import com.google.gson.JsonArray; 15 | import io.undertow.server.HttpServerExchange; 16 | import org.jetbrains.annotations.NotNull; 17 | 18 | import java.util.Set; 19 | 20 | public class FirstLoadHandler extends BaseHandler { 21 | public static final String OP = Consts.Operation.FIRST_LOAD.toString(); 22 | private final JsonWrapper defaultGameOptions; 23 | private final JsonArray cards; 24 | private final JsonWrapper authConfig; 25 | private final String serverStatusPage; 26 | 27 | public FirstLoadHandler(@Annotations.LoadedCards LoadedCards loadedCards, 28 | @Annotations.Emails Emails emails, 29 | @Annotations.SocialLogin SocialLogin socials, 30 | @Annotations.Preferences Preferences preferences) { 31 | serverStatusPage = preferences.getStringNotEmpty("serverStatusPage", null); 32 | 33 | Set cardSets = loadedCards.getLoadedSets(); 34 | cards = new JsonArray(cardSets.size()); 35 | for (PyxCardSet cardSet : cardSets) cards.add(cardSet.getClientMetadataJson().obj()); 36 | 37 | defaultGameOptions = GameOptions.getOptionsDefaultsJson(preferences); 38 | 39 | authConfig = new JsonWrapper(); 40 | if (emails.enabled()) authConfig.add(Consts.AuthType.PASSWORD, emails.senderEmail()); 41 | if (socials.googleEnabled()) authConfig.add(Consts.AuthType.GOOGLE, socials.googleAppId()); 42 | if (socials.facebookEnabled()) authConfig.add(Consts.AuthType.FACEBOOK, socials.facebookAppId()); 43 | if (socials.githubEnabled()) authConfig.add(Consts.AuthType.GITHUB, socials.githubAppId()); 44 | if (socials.twitterEnabled()) authConfig.add(Consts.AuthType.TWITTER, socials.twitterAppId()); 45 | } 46 | 47 | @NotNull 48 | @Override 49 | public JsonWrapper handle(User user, Parameters params, HttpServerExchange exchange) { 50 | JsonWrapper obj = new JsonWrapper(); 51 | 52 | if (user == null) { 53 | obj.add(Consts.GeneralKeys.IN_PROGRESS, Boolean.FALSE) 54 | .add(Consts.GeneralKeys.NEXT, Consts.Operation.REGISTER.toString()); 55 | } else { 56 | // They already have a session in progress, we need to figure out what they were doing 57 | // and tell the client where to continue from. 58 | obj.add(Consts.GeneralKeys.IN_PROGRESS, Boolean.TRUE) 59 | .add(Consts.UserData.NICKNAME, user.getNickname()); 60 | 61 | if (user.getGame() != null) { 62 | obj.add(Consts.GeneralKeys.NEXT, Consts.ReconnectNextAction.GAME.toString()) 63 | .add(Consts.GeneralKeys.GAME_ID, user.getGame().getId()); 64 | } else { 65 | obj.add(Consts.GeneralKeys.NEXT, Consts.ReconnectNextAction.NONE.toString()); 66 | } 67 | } 68 | 69 | obj.add(Consts.GeneralKeys.AUTH_CONFIG, authConfig); 70 | obj.add(Consts.GameOptionsData.CARD_SETS, cards); 71 | obj.add(Consts.GameOptionsData.DEFAULT_OPTIONS, defaultGameOptions); 72 | obj.add(Consts.GeneralKeys.SERVER_STATUS_PAGE, serverStatusPage); 73 | 74 | return obj; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/handlers/GameChatHandler.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.handlers; 2 | 3 | import com.gianlu.pyxreloaded.Consts; 4 | import com.gianlu.pyxreloaded.data.EventWrapper; 5 | import com.gianlu.pyxreloaded.data.JsonWrapper; 6 | import com.gianlu.pyxreloaded.data.QueuedMessage.MessageType; 7 | import com.gianlu.pyxreloaded.data.User; 8 | import com.gianlu.pyxreloaded.game.Game; 9 | import com.gianlu.pyxreloaded.server.Annotations; 10 | import com.gianlu.pyxreloaded.server.BaseCahHandler; 11 | import com.gianlu.pyxreloaded.server.Parameters; 12 | import com.gianlu.pyxreloaded.singletons.ConnectedUsers; 13 | import com.gianlu.pyxreloaded.singletons.GamesManager; 14 | import io.undertow.server.HttpServerExchange; 15 | import org.jetbrains.annotations.NotNull; 16 | 17 | public class GameChatHandler extends GameWithPlayerHandler { 18 | public static final String OP = Consts.Operation.GAME_CHAT.toString(); 19 | private final ConnectedUsers users; 20 | 21 | public GameChatHandler( 22 | @Annotations.ConnectedUsers ConnectedUsers users, 23 | @Annotations.GameManager GamesManager gamesManager) { 24 | super(gamesManager); 25 | this.users = users; 26 | } 27 | 28 | @NotNull 29 | @Override 30 | public JsonWrapper handleWithUserInGame(User user, Game game, Parameters params, HttpServerExchange exchange) throws BaseCahHandler.CahException { 31 | users.checkChatFlood(user); 32 | 33 | String msg = params.getStringNotNull(Consts.ChatData.MESSAGE); 34 | if (msg.isEmpty()) throw new BaseCahHandler.CahException(Consts.ErrorCode.BAD_REQUEST); 35 | 36 | if (msg.length() > Consts.CHAT_MAX_LENGTH) { 37 | throw new BaseCahHandler.CahException(Consts.ErrorCode.MESSAGE_TOO_LONG); 38 | } else if (!users.runChatCommand(user, msg)) { 39 | user.getLastMessageTimes().add(System.currentTimeMillis()); 40 | EventWrapper ev = new EventWrapper(game, Consts.Event.CHAT); 41 | ev.add(Consts.ChatData.FROM, user.getNickname()); 42 | ev.add(Consts.ChatData.MESSAGE, msg); 43 | ev.add(Consts.GeneralKeys.GAME_ID, game.getId()); 44 | ev.add(Consts.ChatData.FROM_ADMIN, user.isAdmin()); 45 | game.broadcastToPlayers(MessageType.CHAT, ev); 46 | } 47 | 48 | return JsonWrapper.EMPTY; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/handlers/GameHandler.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.handlers; 2 | 3 | import com.gianlu.pyxreloaded.Consts; 4 | import com.gianlu.pyxreloaded.data.JsonWrapper; 5 | import com.gianlu.pyxreloaded.data.User; 6 | import com.gianlu.pyxreloaded.game.Game; 7 | import com.gianlu.pyxreloaded.server.BaseCahHandler; 8 | import com.gianlu.pyxreloaded.server.Parameters; 9 | import com.gianlu.pyxreloaded.singletons.GamesManager; 10 | import io.undertow.server.HttpServerExchange; 11 | import org.jetbrains.annotations.NotNull; 12 | 13 | public abstract class GameHandler extends BaseHandler { 14 | protected final GamesManager gamesManager; 15 | 16 | GameHandler(GamesManager gamesManager) { 17 | this.gamesManager = gamesManager; 18 | } 19 | 20 | @NotNull 21 | @Override 22 | public JsonWrapper handle(User user, Parameters params, HttpServerExchange exchange) throws BaseCahHandler.CahException { 23 | String gameIdStr = params.getStringNotNull(Consts.GeneralKeys.GAME_ID); 24 | if (gameIdStr.isEmpty()) throw new BaseCahHandler.CahException(Consts.ErrorCode.BAD_REQUEST); 25 | 26 | int gameId; 27 | try { 28 | gameId = Integer.parseInt(gameIdStr); 29 | } catch (NumberFormatException ex) { 30 | throw new BaseCahHandler.CahException(Consts.ErrorCode.INVALID_GAME, ex); 31 | } 32 | 33 | final Game game = gamesManager.getGame(gameId); 34 | if (game == null) throw new BaseCahHandler.CahException(Consts.ErrorCode.INVALID_GAME); 35 | 36 | return handle(user, game, params, exchange); 37 | } 38 | 39 | @NotNull 40 | public abstract JsonWrapper handle(User user, Game game, Parameters params, HttpServerExchange exchange) throws BaseCahHandler.CahException; 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/handlers/GameListHandler.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.handlers; 2 | 3 | import com.gianlu.pyxreloaded.Consts; 4 | import com.gianlu.pyxreloaded.data.JsonWrapper; 5 | import com.gianlu.pyxreloaded.data.User; 6 | import com.gianlu.pyxreloaded.game.Game; 7 | import com.gianlu.pyxreloaded.server.Annotations; 8 | import com.gianlu.pyxreloaded.server.Parameters; 9 | import com.gianlu.pyxreloaded.singletons.GamesManager; 10 | import com.google.gson.JsonArray; 11 | import io.undertow.server.HttpServerExchange; 12 | import org.jetbrains.annotations.NotNull; 13 | 14 | public class GameListHandler extends BaseHandler { 15 | public static final String OP = Consts.Operation.GAME_LIST.toString(); 16 | private final GamesManager gamesManager; 17 | 18 | public GameListHandler(@Annotations.GameManager GamesManager gamesManager) { 19 | this.gamesManager = gamesManager; 20 | } 21 | 22 | @NotNull 23 | @Override 24 | public JsonWrapper handle(User user, Parameters params, HttpServerExchange exchange) { 25 | JsonWrapper json = new JsonWrapper(); 26 | 27 | JsonArray infoArray = new JsonArray(); 28 | for (Game game : gamesManager.getGameList()) { 29 | JsonWrapper info = game.getInfoJson(user, false); 30 | if (info != null) infoArray.add(info.obj()); 31 | } 32 | 33 | json.add(Consts.GeneralKeys.GAMES, infoArray); 34 | json.add(Consts.GeneralKeys.MAX_GAMES, gamesManager.getMaxGames()); 35 | return json; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/handlers/GameOptionsSuggestionDecisionHandler.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.handlers; 2 | 3 | import com.gianlu.pyxreloaded.Consts; 4 | import com.gianlu.pyxreloaded.data.JsonWrapper; 5 | import com.gianlu.pyxreloaded.data.User; 6 | import com.gianlu.pyxreloaded.game.Game; 7 | import com.gianlu.pyxreloaded.server.Annotations; 8 | import com.gianlu.pyxreloaded.server.BaseCahHandler; 9 | import com.gianlu.pyxreloaded.server.Parameters; 10 | import com.gianlu.pyxreloaded.singletons.GamesManager; 11 | import io.undertow.server.HttpServerExchange; 12 | import org.jetbrains.annotations.NotNull; 13 | 14 | public class GameOptionsSuggestionDecisionHandler extends GameWithPlayerHandler { 15 | public final static String OP = Consts.Operation.GAME_OPTIONS_SUGGESTION_DECISION.toString(); 16 | 17 | public GameOptionsSuggestionDecisionHandler(@Annotations.GameManager GamesManager gamesManager) { 18 | super(gamesManager); 19 | } 20 | 21 | @NotNull 22 | @Override 23 | public JsonWrapper handleWithUserInGame(User user, Game game, Parameters params, HttpServerExchange exchange) throws BaseCahHandler.CahException { 24 | if (game.getHost() != user) throw new BaseCahHandler.CahException(Consts.ErrorCode.NOT_GAME_HOST); 25 | 26 | String suggestedId = params.getStringNotNull(Consts.GameSuggestedOptionsData.ID); 27 | if (suggestedId.isEmpty()) throw new BaseCahHandler.CahException(Consts.ErrorCode.BAD_REQUEST); 28 | 29 | if (!params.has(Consts.GameSuggestedOptionsData.DECISION)) 30 | throw new BaseCahHandler.CahException(Consts.ErrorCode.BAD_REQUEST); 31 | 32 | if (params.getBoolean(Consts.GameSuggestedOptionsData.DECISION, false)) 33 | game.applySuggestedOptions(suggestedId); 34 | else 35 | game.declineSuggestedOptions(suggestedId); 36 | 37 | return JsonWrapper.EMPTY; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/handlers/GameWithPlayerHandler.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.handlers; 2 | 3 | import com.gianlu.pyxreloaded.Consts; 4 | import com.gianlu.pyxreloaded.data.JsonWrapper; 5 | import com.gianlu.pyxreloaded.data.User; 6 | import com.gianlu.pyxreloaded.game.Game; 7 | import com.gianlu.pyxreloaded.server.BaseCahHandler; 8 | import com.gianlu.pyxreloaded.server.Parameters; 9 | import com.gianlu.pyxreloaded.singletons.GamesManager; 10 | import io.undertow.server.HttpServerExchange; 11 | import org.jetbrains.annotations.NotNull; 12 | 13 | public abstract class GameWithPlayerHandler extends GameHandler { 14 | 15 | public GameWithPlayerHandler(GamesManager gamesManager) { 16 | super(gamesManager); 17 | } 18 | 19 | @NotNull 20 | @Override 21 | public final JsonWrapper handle(User user, Game game, Parameters params, HttpServerExchange exchange) throws BaseCahHandler.CahException { 22 | if (user.getGame() != game) throw new BaseCahHandler.CahException(Consts.ErrorCode.NOT_IN_THAT_GAME); 23 | else return handleWithUserInGame(user, game, params, exchange); 24 | } 25 | 26 | @NotNull 27 | public abstract JsonWrapper handleWithUserInGame(User user, Game game, Parameters params, HttpServerExchange exchange) throws BaseCahHandler.CahException; 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/handlers/GetCardsHandler.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.handlers; 2 | 3 | import com.gianlu.pyxreloaded.Consts; 4 | import com.gianlu.pyxreloaded.data.JsonWrapper; 5 | import com.gianlu.pyxreloaded.data.User; 6 | import com.gianlu.pyxreloaded.game.Game; 7 | import com.gianlu.pyxreloaded.server.Annotations; 8 | import com.gianlu.pyxreloaded.server.Parameters; 9 | import com.gianlu.pyxreloaded.singletons.GamesManager; 10 | import io.undertow.server.HttpServerExchange; 11 | import org.jetbrains.annotations.NotNull; 12 | 13 | public class GetCardsHandler extends GameWithPlayerHandler { 14 | public static final String OP = Consts.Operation.GET_CARDS.toString(); 15 | 16 | public GetCardsHandler(@Annotations.GameManager GamesManager gamesManager) { 17 | super(gamesManager); 18 | } 19 | 20 | @NotNull 21 | @Override 22 | public JsonWrapper handleWithUserInGame(User user, Game game, Parameters params, HttpServerExchange exchange) { 23 | JsonWrapper obj = new JsonWrapper(); 24 | obj.add(Consts.OngoingGameData.HAND, game.getHandJson(user)); 25 | obj.addAll(game.getPlayerToPlayCards(user)); 26 | obj.add(Consts.OngoingGameData.BLACK_CARD, game.getBlackCardJson()); 27 | obj.add(Consts.OngoingGameData.WHITE_CARDS, game.getWhiteCardsJson(user)); 28 | obj.add(Consts.GeneralKeys.GAME_ID, game.getId()); 29 | 30 | return obj; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/handlers/GetGameInfoHandler.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.handlers; 2 | 3 | import com.gianlu.pyxreloaded.Consts; 4 | import com.gianlu.pyxreloaded.data.JsonWrapper; 5 | import com.gianlu.pyxreloaded.data.User; 6 | import com.gianlu.pyxreloaded.game.Game; 7 | import com.gianlu.pyxreloaded.server.Annotations; 8 | import com.gianlu.pyxreloaded.server.Parameters; 9 | import com.gianlu.pyxreloaded.singletons.GamesManager; 10 | import io.undertow.server.HttpServerExchange; 11 | import org.jetbrains.annotations.NotNull; 12 | 13 | public class GetGameInfoHandler extends GameWithPlayerHandler { 14 | public static final String OP = Consts.Operation.GET_GAME_INFO.toString(); 15 | 16 | public GetGameInfoHandler(@Annotations.GameManager GamesManager gamesManager) { 17 | super(gamesManager); 18 | } 19 | 20 | @NotNull 21 | @Override 22 | public JsonWrapper handleWithUserInGame(User user, Game game, Parameters params, HttpServerExchange exchange) { 23 | JsonWrapper obj = new JsonWrapper(); 24 | obj.add(Consts.GameInfoData.INFO, game.getInfoJson(user, true)); 25 | obj.add(Consts.GamePlayerInfo.INFO, game.getAllPlayersInfoJson()); 26 | return obj; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/handlers/GetMeHandler.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.handlers; 2 | 3 | import com.gianlu.pyxreloaded.Consts; 4 | import com.gianlu.pyxreloaded.data.JsonWrapper; 5 | import com.gianlu.pyxreloaded.data.User; 6 | import com.gianlu.pyxreloaded.server.Parameters; 7 | import io.undertow.server.HttpServerExchange; 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | /** 11 | * Class to get user's nick for the game.html page - not safe to store/retrieve as a cookie. 12 | * This class returns a JSON string containing the user's nick to the client through AJAX. 13 | * More data will be added/used once user accounts are added. 14 | **/ 15 | public class GetMeHandler extends BaseHandler { 16 | public static final String OP = Consts.Operation.ME.toString(); 17 | 18 | public GetMeHandler() { 19 | } 20 | 21 | @NotNull 22 | @Override 23 | public JsonWrapper handle(User user, Parameters params, HttpServerExchange exchange) { 24 | JsonWrapper obj = new JsonWrapper(); 25 | obj.add(Consts.UserData.NICKNAME, user.getNickname()); 26 | if (user.getAccount() != null) obj.add(Consts.GeneralKeys.ACCOUNT, user.getAccount().toJson()); 27 | 28 | if (user.getGame() != null) obj.add(Consts.GeneralKeys.GAME_ID, user.getGame().getId()); 29 | else obj.add(Consts.GeneralKeys.GAME_ID, -1); 30 | 31 | return obj; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/handlers/GetSuggestedGameOptionsHandler.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.handlers; 2 | 3 | import com.gianlu.pyxreloaded.Consts; 4 | import com.gianlu.pyxreloaded.data.JsonWrapper; 5 | import com.gianlu.pyxreloaded.data.User; 6 | import com.gianlu.pyxreloaded.game.Game; 7 | import com.gianlu.pyxreloaded.game.SuggestedGameOptions; 8 | import com.gianlu.pyxreloaded.server.Annotations; 9 | import com.gianlu.pyxreloaded.server.BaseCahHandler; 10 | import com.gianlu.pyxreloaded.server.Parameters; 11 | import com.gianlu.pyxreloaded.singletons.GamesManager; 12 | import com.google.gson.JsonArray; 13 | import io.undertow.server.HttpServerExchange; 14 | import org.jetbrains.annotations.NotNull; 15 | 16 | import java.util.Map; 17 | 18 | public class GetSuggestedGameOptionsHandler extends GameWithPlayerHandler { 19 | public static final String OP = Consts.Operation.GET_SUGGESTED_GAME_OPTIONS.toString(); 20 | 21 | public GetSuggestedGameOptionsHandler(@Annotations.GameManager GamesManager gamesManager) { 22 | super(gamesManager); 23 | } 24 | 25 | @NotNull 26 | @Override 27 | public JsonWrapper handleWithUserInGame(User user, Game game, Parameters params, HttpServerExchange exchange) throws BaseCahHandler.CahException { 28 | if (user != game.getHost()) throw new BaseCahHandler.CahException(Consts.ErrorCode.NOT_GAME_HOST); 29 | 30 | JsonWrapper obj = new JsonWrapper(); 31 | JsonArray array = new JsonArray(); 32 | for (Map.Entry entry : game.getSuggestedGameOptions().entrySet()) 33 | array.add(entry.getValue().toJson(entry.getKey(), true).obj()); 34 | 35 | obj.add(Consts.GameSuggestedOptionsData.OPTIONS, array); 36 | return obj; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/handlers/GetUserPreferencesHandler.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.handlers; 2 | 3 | import com.gianlu.pyxreloaded.Consts; 4 | import com.gianlu.pyxreloaded.data.JsonWrapper; 5 | import com.gianlu.pyxreloaded.data.User; 6 | import com.gianlu.pyxreloaded.data.accounts.UserAccount; 7 | import com.gianlu.pyxreloaded.server.Parameters; 8 | import io.undertow.server.HttpServerExchange; 9 | import org.jetbrains.annotations.NotNull; 10 | 11 | public class GetUserPreferencesHandler extends BaseHandler { 12 | public static final String OP = Consts.Operation.GET_USER_PREFERENCES.toString(); 13 | 14 | @NotNull 15 | @Override 16 | public JsonWrapper handle(User user, Parameters params, HttpServerExchange exchange) { 17 | UserAccount account = user.getAccount(); 18 | if (account == null) return JsonWrapper.EMPTY; 19 | 20 | String[] keys; 21 | String keysStr = params.getString(Consts.GeneralKeys.USER_PREFERENCES); 22 | if (keysStr == null) keys = null; 23 | else keys = keysStr.split(","); 24 | 25 | return account.preferences.toJson(keys); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/handlers/JoinGameHandler.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.handlers; 2 | 3 | import com.gianlu.pyxreloaded.Consts; 4 | import com.gianlu.pyxreloaded.data.JsonWrapper; 5 | import com.gianlu.pyxreloaded.data.User; 6 | import com.gianlu.pyxreloaded.game.Game; 7 | import com.gianlu.pyxreloaded.server.Annotations; 8 | import com.gianlu.pyxreloaded.server.BaseCahHandler; 9 | import com.gianlu.pyxreloaded.server.Parameters; 10 | import com.gianlu.pyxreloaded.singletons.GamesManager; 11 | import com.gianlu.pyxreloaded.singletons.PreparingShutdown; 12 | import io.undertow.server.HttpServerExchange; 13 | import org.jetbrains.annotations.NotNull; 14 | 15 | public class JoinGameHandler extends GameHandler { 16 | public static final String OP = Consts.Operation.JOIN_GAME.toString(); 17 | 18 | public JoinGameHandler(@Annotations.GameManager GamesManager gamesManager) { 19 | super(gamesManager); 20 | } 21 | 22 | @NotNull 23 | @Override 24 | public JsonWrapper handle(User user, Game game, Parameters params, HttpServerExchange exchange) throws BaseCahHandler.CahException { 25 | PreparingShutdown.get().check(); 26 | 27 | if (!game.isPasswordCorrect(params.getString(Consts.GameOptionsData.PASSWORD))) 28 | throw new BaseCahHandler.CahException(Consts.ErrorCode.WRONG_PASSWORD); 29 | 30 | game.addPlayer(user); 31 | return JsonWrapper.EMPTY; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/handlers/JudgeSelectHandler.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.handlers; 2 | 3 | import com.gianlu.pyxreloaded.Consts; 4 | import com.gianlu.pyxreloaded.data.JsonWrapper; 5 | import com.gianlu.pyxreloaded.data.User; 6 | import com.gianlu.pyxreloaded.game.Game; 7 | import com.gianlu.pyxreloaded.server.Annotations; 8 | import com.gianlu.pyxreloaded.server.BaseCahHandler; 9 | import com.gianlu.pyxreloaded.server.Parameters; 10 | import com.gianlu.pyxreloaded.singletons.GamesManager; 11 | import io.undertow.server.HttpServerExchange; 12 | import org.jetbrains.annotations.NotNull; 13 | 14 | public class JudgeSelectHandler extends GameWithPlayerHandler { 15 | public static final String OP = Consts.Operation.JUDGE_SELECT.toString(); 16 | 17 | public JudgeSelectHandler(@Annotations.GameManager GamesManager gamesManager) { 18 | super(gamesManager); 19 | } 20 | 21 | @NotNull 22 | @Override 23 | public JsonWrapper handleWithUserInGame(User user, Game game, Parameters params, HttpServerExchange exchange) throws BaseCahHandler.CahException { 24 | String cardIdStr = params.getStringNotNull(Consts.GeneralKeys.CARD_ID); 25 | if (cardIdStr.isEmpty()) throw new BaseCahHandler.CahException(Consts.ErrorCode.BAD_REQUEST); 26 | 27 | int cardId; 28 | try { 29 | cardId = Integer.parseInt(cardIdStr); 30 | } catch (NumberFormatException ex) { 31 | throw new BaseCahHandler.CahException(Consts.ErrorCode.INVALID_CARD, ex); 32 | } 33 | 34 | game.judgeCard(user, cardId); 35 | return JsonWrapper.EMPTY; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/handlers/KickHandler.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.handlers; 2 | 3 | import com.gianlu.pyxreloaded.Consts; 4 | import com.gianlu.pyxreloaded.data.JsonWrapper; 5 | import com.gianlu.pyxreloaded.data.User; 6 | import com.gianlu.pyxreloaded.server.Annotations; 7 | import com.gianlu.pyxreloaded.server.BaseCahHandler; 8 | import com.gianlu.pyxreloaded.server.Parameters; 9 | import com.gianlu.pyxreloaded.singletons.ConnectedUsers; 10 | import io.undertow.server.HttpServerExchange; 11 | import org.jetbrains.annotations.NotNull; 12 | 13 | public class KickHandler extends BaseHandler { 14 | public static final String OP = Consts.Operation.KICK.toString(); 15 | private final ConnectedUsers connectedUsers; 16 | 17 | public KickHandler(@Annotations.ConnectedUsers ConnectedUsers connectedUsers) { 18 | this.connectedUsers = connectedUsers; 19 | } 20 | 21 | @NotNull 22 | @Override 23 | public JsonWrapper handle(User user, Parameters params, HttpServerExchange exchange) throws BaseCahHandler.CahException { 24 | if (!user.isAdmin()) throw new BaseCahHandler.CahException(Consts.ErrorCode.NOT_ADMIN); 25 | 26 | String nickname = params.getStringNotNull(Consts.UserData.NICKNAME); 27 | if (nickname.isEmpty()) throw new BaseCahHandler.CahException(Consts.ErrorCode.BAD_REQUEST); 28 | 29 | User target = connectedUsers.getUser(nickname); 30 | if (target == null) throw new BaseCahHandler.CahException(Consts.ErrorCode.NO_SUCH_USER); 31 | connectedUsers.kickUser(target); 32 | 33 | return JsonWrapper.EMPTY; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/handlers/LeaveGameHandler.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.handlers; 2 | 3 | import com.gianlu.pyxreloaded.Consts; 4 | import com.gianlu.pyxreloaded.data.JsonWrapper; 5 | import com.gianlu.pyxreloaded.data.User; 6 | import com.gianlu.pyxreloaded.game.Game; 7 | import com.gianlu.pyxreloaded.server.Annotations; 8 | import com.gianlu.pyxreloaded.server.Parameters; 9 | import com.gianlu.pyxreloaded.singletons.GamesManager; 10 | import io.undertow.server.HttpServerExchange; 11 | import org.jetbrains.annotations.NotNull; 12 | 13 | 14 | public class LeaveGameHandler extends GameWithPlayerHandler { 15 | public static final String OP = Consts.Operation.LEAVE_GAME.toString(); 16 | 17 | public LeaveGameHandler(@Annotations.GameManager GamesManager gamesManager) { 18 | super(gamesManager); 19 | } 20 | 21 | @NotNull 22 | @Override 23 | public JsonWrapper handleWithUserInGame(User user, Game game, Parameters params, HttpServerExchange exchange) { 24 | game.removePlayer(user); 25 | game.removeSpectator(user); 26 | return JsonWrapper.EMPTY; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/handlers/LikeGameHandler.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.handlers; 2 | 3 | import com.gianlu.pyxreloaded.Consts; 4 | import com.gianlu.pyxreloaded.data.JsonWrapper; 5 | import com.gianlu.pyxreloaded.data.User; 6 | import com.gianlu.pyxreloaded.game.Game; 7 | import com.gianlu.pyxreloaded.server.Annotations; 8 | import com.gianlu.pyxreloaded.server.Parameters; 9 | import com.gianlu.pyxreloaded.singletons.GamesManager; 10 | import io.undertow.server.HttpServerExchange; 11 | import org.jetbrains.annotations.NotNull; 12 | 13 | public class LikeGameHandler extends GameHandler { 14 | public static final String OP = Consts.Operation.LIKE.toString(); 15 | 16 | public LikeGameHandler(@Annotations.GameManager GamesManager gamesManager) { 17 | super(gamesManager); 18 | } 19 | 20 | @NotNull 21 | @Override 22 | public JsonWrapper handle(User user, Game game, Parameters params, HttpServerExchange exchange) { 23 | game.toggleLikeGame(user); 24 | return game.getLikesInfoJson(user); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/handlers/LogoutHandler.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.handlers; 2 | 3 | import com.gianlu.pyxreloaded.Consts; 4 | import com.gianlu.pyxreloaded.data.JsonWrapper; 5 | import com.gianlu.pyxreloaded.data.User; 6 | import com.gianlu.pyxreloaded.server.Annotations; 7 | import com.gianlu.pyxreloaded.server.Parameters; 8 | import com.gianlu.pyxreloaded.singletons.ConnectedUsers; 9 | import com.gianlu.pyxreloaded.singletons.Sessions; 10 | import io.undertow.server.HttpServerExchange; 11 | import org.jetbrains.annotations.NotNull; 12 | 13 | public class LogoutHandler extends BaseHandler { 14 | public final static String OP = Consts.Operation.LOG_OUT.toString(); 15 | private final ConnectedUsers users; 16 | 17 | public LogoutHandler(@Annotations.ConnectedUsers ConnectedUsers users) { 18 | this.users = users; 19 | } 20 | 21 | @NotNull 22 | @Override 23 | public JsonWrapper handle(User user, Parameters params, HttpServerExchange exchange) { 24 | user.noLongerValid(); 25 | users.removeUser(user, Consts.DisconnectReason.MANUAL); 26 | Sessions.get().invalidate(user.getSessionId()); 27 | return JsonWrapper.EMPTY; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/handlers/NamesHandler.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.handlers; 2 | 3 | import com.gianlu.pyxreloaded.Consts; 4 | import com.gianlu.pyxreloaded.data.JsonWrapper; 5 | import com.gianlu.pyxreloaded.data.User; 6 | import com.gianlu.pyxreloaded.server.Annotations; 7 | import com.gianlu.pyxreloaded.server.Parameters; 8 | import com.gianlu.pyxreloaded.singletons.ConnectedUsers; 9 | import com.google.gson.JsonArray; 10 | import io.undertow.server.HttpServerExchange; 11 | import org.jetbrains.annotations.NotNull; 12 | 13 | public class NamesHandler extends BaseHandler { 14 | public static final String OP = Consts.Operation.NAMES.toString(); 15 | private final ConnectedUsers users; 16 | 17 | public NamesHandler(@Annotations.ConnectedUsers ConnectedUsers users) { 18 | this.users = users; 19 | } 20 | 21 | @NotNull 22 | @Override 23 | public JsonWrapper handle(User user, Parameters params, HttpServerExchange exchange) { 24 | JsonArray array = new JsonArray(); 25 | for (User item : users.getUsers()) array.add(item.toSmallJson().obj()); 26 | return new JsonWrapper(Consts.GeneralKeys.NAMES, array); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/handlers/PlayCardHandler.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.handlers; 2 | 3 | import com.gianlu.pyxreloaded.Consts; 4 | import com.gianlu.pyxreloaded.data.JsonWrapper; 5 | import com.gianlu.pyxreloaded.data.User; 6 | import com.gianlu.pyxreloaded.game.Game; 7 | import com.gianlu.pyxreloaded.server.Annotations; 8 | import com.gianlu.pyxreloaded.server.BaseCahHandler; 9 | import com.gianlu.pyxreloaded.server.Parameters; 10 | import com.gianlu.pyxreloaded.singletons.GamesManager; 11 | import io.undertow.server.HttpServerExchange; 12 | import org.apache.commons.lang3.StringEscapeUtils; 13 | import org.jetbrains.annotations.NotNull; 14 | 15 | public class PlayCardHandler extends GameWithPlayerHandler { 16 | public static final String OP = Consts.Operation.PLAY_CARD.toString(); 17 | 18 | public PlayCardHandler(@Annotations.GameManager GamesManager gamesManager) { 19 | super(gamesManager); 20 | } 21 | 22 | @NotNull 23 | @Override 24 | public JsonWrapper handleWithUserInGame(User user, Game game, Parameters params, HttpServerExchange exchange) throws BaseCahHandler.CahException { 25 | String cardIdStr = params.getStringNotNull(Consts.GeneralKeys.CARD_ID); 26 | if (cardIdStr.isEmpty()) throw new BaseCahHandler.CahException(Consts.ErrorCode.BAD_REQUEST); 27 | 28 | int cardId; 29 | try { 30 | cardId = Integer.parseInt(cardIdStr); 31 | } catch (NumberFormatException ex) { 32 | throw new BaseCahHandler.CahException(Consts.ErrorCode.INVALID_CARD, ex); 33 | } 34 | 35 | String text = params.getString(Consts.GeneralKeys.WRITE_IN_TEXT); 36 | if (text != null && text.contains("<")) text = StringEscapeUtils.escapeXml11(text); 37 | 38 | return game.playCard(user, cardId, text); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/handlers/PongHandler.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.handlers; 2 | 3 | import com.gianlu.pyxreloaded.Consts; 4 | import com.gianlu.pyxreloaded.data.JsonWrapper; 5 | import com.gianlu.pyxreloaded.data.User; 6 | import com.gianlu.pyxreloaded.server.Parameters; 7 | import io.undertow.server.HttpServerExchange; 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | public class PongHandler extends BaseHandler { 11 | public static final String OP = Consts.Operation.PONG.toString(); 12 | 13 | @NotNull 14 | @Override 15 | public JsonWrapper handle(User user, Parameters params, HttpServerExchange exchange) { 16 | user.userReceivedEvents(); 17 | return JsonWrapper.EMPTY; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/handlers/PrepareShutdownHandler.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.handlers; 2 | 3 | import com.gianlu.pyxreloaded.Consts; 4 | import com.gianlu.pyxreloaded.data.JsonWrapper; 5 | import com.gianlu.pyxreloaded.data.User; 6 | import com.gianlu.pyxreloaded.server.BaseCahHandler; 7 | import com.gianlu.pyxreloaded.server.BaseJsonHandler; 8 | import com.gianlu.pyxreloaded.server.Parameters; 9 | import com.gianlu.pyxreloaded.singletons.PreparingShutdown; 10 | import io.undertow.server.HttpServerExchange; 11 | import org.jetbrains.annotations.NotNull; 12 | 13 | public class PrepareShutdownHandler extends BaseHandler { 14 | public static final String OP = Consts.Operation.PREPARE_SHUTDOWN.toString(); 15 | 16 | public PrepareShutdownHandler() { 17 | } 18 | 19 | @NotNull 20 | @Override 21 | public JsonWrapper handle(User user, Parameters params, HttpServerExchange exchange) throws BaseJsonHandler.StatusException { 22 | if (!user.isAdmin()) throw new BaseCahHandler.CahException(Consts.ErrorCode.NOT_ADMIN); 23 | 24 | PreparingShutdown.get().set(true); 25 | 26 | return JsonWrapper.EMPTY; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/handlers/SetUserPreferencesHandler.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.handlers; 2 | 3 | import com.gianlu.pyxreloaded.Consts; 4 | import com.gianlu.pyxreloaded.Utils; 5 | import com.gianlu.pyxreloaded.data.JsonWrapper; 6 | import com.gianlu.pyxreloaded.data.User; 7 | import com.gianlu.pyxreloaded.data.accounts.UserAccount; 8 | import com.gianlu.pyxreloaded.server.Annotations; 9 | import com.gianlu.pyxreloaded.server.BaseCahHandler; 10 | import com.gianlu.pyxreloaded.server.Parameters; 11 | import com.gianlu.pyxreloaded.singletons.UsersWithAccount; 12 | import com.google.gson.JsonObject; 13 | import com.google.gson.JsonParser; 14 | import com.google.gson.JsonSyntaxException; 15 | import io.undertow.server.HttpServerExchange; 16 | import org.jetbrains.annotations.NotNull; 17 | 18 | import java.util.Map; 19 | 20 | public class SetUserPreferencesHandler extends BaseHandler { 21 | public static final String OP = Consts.Operation.SET_USER_PREFERENCES.toString(); 22 | private final JsonParser parser = new JsonParser(); 23 | private final UsersWithAccount accounts; 24 | 25 | public SetUserPreferencesHandler(@Annotations.UsersWithAccount UsersWithAccount accounts) { 26 | this.accounts = accounts; 27 | } 28 | 29 | @NotNull 30 | @Override 31 | public JsonWrapper handle(User user, Parameters params, HttpServerExchange exchange) throws BaseCahHandler.CahException { 32 | UserAccount account = user.getAccount(); 33 | if (account == null) return JsonWrapper.EMPTY; 34 | 35 | try { 36 | JsonObject obj = parser.parse(params.getStringNotNull(Consts.GeneralKeys.USER_PREFERENCES)).getAsJsonObject(); 37 | Map map = Utils.toMap(obj); 38 | account.updatePreferences(map); 39 | accounts.updatePreferences(account, map); 40 | return account.preferences.toJson(map.keySet().toArray(new String[0])); 41 | } catch (IllegalStateException | JsonSyntaxException ex) { 42 | throw new BaseCahHandler.CahException(Consts.ErrorCode.BAD_REQUEST, ex); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/handlers/SpectateGameHandler.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.handlers; 2 | 3 | import com.gianlu.pyxreloaded.Consts; 4 | import com.gianlu.pyxreloaded.data.JsonWrapper; 5 | import com.gianlu.pyxreloaded.data.User; 6 | import com.gianlu.pyxreloaded.game.Game; 7 | import com.gianlu.pyxreloaded.server.Annotations; 8 | import com.gianlu.pyxreloaded.server.BaseCahHandler; 9 | import com.gianlu.pyxreloaded.server.Parameters; 10 | import com.gianlu.pyxreloaded.singletons.GamesManager; 11 | import com.gianlu.pyxreloaded.singletons.PreparingShutdown; 12 | import io.undertow.server.HttpServerExchange; 13 | import org.jetbrains.annotations.NotNull; 14 | 15 | public class SpectateGameHandler extends GameHandler { 16 | public static final String OP = Consts.Operation.SPECTATE_GAME.toString(); 17 | 18 | public SpectateGameHandler(@Annotations.GameManager GamesManager gamesManager) { 19 | super(gamesManager); 20 | } 21 | 22 | @NotNull 23 | @Override 24 | public JsonWrapper handle(User user, Game game, Parameters params, HttpServerExchange exchange) throws BaseCahHandler.CahException { 25 | PreparingShutdown.get().check(); 26 | 27 | if (!game.isPasswordCorrect(params.getString(Consts.GameOptionsData.PASSWORD))) 28 | throw new BaseCahHandler.CahException(Consts.ErrorCode.WRONG_PASSWORD); 29 | 30 | game.addSpectator(user); 31 | return JsonWrapper.EMPTY; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/handlers/StartGameHandler.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.handlers; 2 | 3 | import com.gianlu.pyxreloaded.Consts; 4 | import com.gianlu.pyxreloaded.cardcast.FailedLoadingSomeCardcastDecks; 5 | import com.gianlu.pyxreloaded.cards.CardSet; 6 | import com.gianlu.pyxreloaded.data.JsonWrapper; 7 | import com.gianlu.pyxreloaded.data.User; 8 | import com.gianlu.pyxreloaded.game.Game; 9 | import com.gianlu.pyxreloaded.server.Annotations; 10 | import com.gianlu.pyxreloaded.server.BaseCahHandler; 11 | import com.gianlu.pyxreloaded.server.Parameters; 12 | import com.gianlu.pyxreloaded.singletons.GamesManager; 13 | import com.gianlu.pyxreloaded.singletons.PreparingShutdown; 14 | import io.undertow.server.HttpServerExchange; 15 | import org.jetbrains.annotations.NotNull; 16 | 17 | import java.util.List; 18 | 19 | public class StartGameHandler extends GameWithPlayerHandler { 20 | public static final String OP = Consts.Operation.START_GAME.toString(); 21 | 22 | public StartGameHandler(@Annotations.GameManager GamesManager gamesManager) { 23 | super(gamesManager); 24 | } 25 | 26 | @NotNull 27 | @Override 28 | public JsonWrapper handleWithUserInGame(User user, Game game, Parameters params, HttpServerExchange exchange) throws BaseCahHandler.CahException { 29 | if (game.getHost() != user) throw new BaseCahHandler.CahException(Consts.ErrorCode.NOT_GAME_HOST); 30 | if (game.getState() != Consts.GameState.LOBBY) 31 | throw new BaseCahHandler.CahException(Consts.ErrorCode.ALREADY_STARTED); 32 | 33 | PreparingShutdown.get().check(); 34 | 35 | try { 36 | if (!game.hasEnoughCards()) { 37 | List cardSets = game.loadCardSets(); 38 | JsonWrapper obj = new JsonWrapper(); 39 | obj.add(Consts.GeneralGameData.BLACK_CARDS_PRESENT, game.blackCardsCount(cardSets)); 40 | obj.add(Consts.GeneralGameData.BLACK_CARDS_REQUIRED, game.getRequiredBlackCardCount()); 41 | obj.add(Consts.GeneralGameData.WHITE_CARDS_PRESENT, game.whiteCardsCount(cardSets)); 42 | obj.add(Consts.GeneralGameData.WHITE_CARDS_REQUIRED, game.getRequiredWhiteCardCount()); 43 | throw new BaseCahHandler.CahException(Consts.ErrorCode.NOT_ENOUGH_CARDS, obj); 44 | } else { 45 | game.start(); 46 | return JsonWrapper.EMPTY; 47 | } 48 | } catch (FailedLoadingSomeCardcastDecks ex) { 49 | throw new BaseCahHandler.CahException(Consts.ErrorCode.CARDCAST_CANNOT_FIND, 50 | new JsonWrapper(Consts.GeneralKeys.CARDCAST_ID, ex.getFailedJson())); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/handlers/StopGameHandler.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.handlers; 2 | 3 | import com.gianlu.pyxreloaded.Consts; 4 | import com.gianlu.pyxreloaded.data.JsonWrapper; 5 | import com.gianlu.pyxreloaded.data.User; 6 | import com.gianlu.pyxreloaded.game.Game; 7 | import com.gianlu.pyxreloaded.server.Annotations; 8 | import com.gianlu.pyxreloaded.server.BaseCahHandler; 9 | import com.gianlu.pyxreloaded.server.Parameters; 10 | import com.gianlu.pyxreloaded.singletons.GamesManager; 11 | import io.undertow.server.HttpServerExchange; 12 | import org.apache.log4j.Logger; 13 | import org.jetbrains.annotations.NotNull; 14 | 15 | public class StopGameHandler extends GameWithPlayerHandler { 16 | public static final String OP = Consts.Operation.STOP_GAME.toString(); 17 | protected final Logger logger = Logger.getLogger(GameWithPlayerHandler.class); 18 | 19 | public StopGameHandler(@Annotations.GameManager GamesManager gamesManager) { 20 | super(gamesManager); 21 | } 22 | 23 | @NotNull 24 | @Override 25 | public JsonWrapper handleWithUserInGame(User user, Game game, Parameters params, HttpServerExchange exchange) throws BaseCahHandler.CahException { 26 | if (game.getHost() != user) { 27 | throw new BaseCahHandler.CahException(Consts.ErrorCode.NOT_GAME_HOST); 28 | } else if (game.getState() == Consts.GameState.LOBBY) { 29 | throw new BaseCahHandler.CahException(Consts.ErrorCode.ALREADY_STOPPED); 30 | } else { 31 | logger.info(String.format("Game %d stopped by host %s. Players: %s", game.getId(), user, game.getPlayers())); 32 | game.resetState(false); 33 | return JsonWrapper.EMPTY; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/paths/AjaxPath.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.paths; 2 | 3 | import com.gianlu.pyxreloaded.Consts; 4 | import com.gianlu.pyxreloaded.data.User; 5 | import com.gianlu.pyxreloaded.server.BaseCahHandler; 6 | import com.gianlu.pyxreloaded.server.Parameters; 7 | import com.gianlu.pyxreloaded.singletons.Handlers; 8 | import com.google.gson.JsonElement; 9 | import io.undertow.server.HttpServerExchange; 10 | import org.jetbrains.annotations.Nullable; 11 | 12 | public class AjaxPath extends BaseCahHandler { 13 | 14 | @Override 15 | protected JsonElement handleRequest(@Nullable String op, @Nullable User user, Parameters params, HttpServerExchange exchange) throws StatusException { 16 | if (user != null) user.userDidSomething(); 17 | if (op == null || op.isEmpty()) throw new CahException(Consts.ErrorCode.OP_NOT_SPECIFIED); 18 | return Handlers.obtain(op).handle(user, params, exchange).obj(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/paths/GithubCallbackPath.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.paths; 2 | 3 | import com.gianlu.pyxreloaded.Consts; 4 | import com.gianlu.pyxreloaded.Utils; 5 | import com.gianlu.pyxreloaded.socials.github.GithubAuthHelper; 6 | import io.undertow.server.HttpHandler; 7 | import io.undertow.server.HttpServerExchange; 8 | import io.undertow.server.handlers.CookieImpl; 9 | import io.undertow.util.Headers; 10 | import io.undertow.util.StatusCodes; 11 | import org.apache.log4j.Logger; 12 | import org.jetbrains.annotations.NotNull; 13 | 14 | import java.util.concurrent.TimeUnit; 15 | 16 | public class GithubCallbackPath implements HttpHandler { 17 | private static final Logger logger = Logger.getLogger(GithubCallbackPath.class); 18 | private static final int COOKIE_MAX_AGE = (int) TimeUnit.MINUTES.toSeconds(5); // sec 19 | private static final String REDIRECT_LOCATION = "/?" + Consts.GeneralKeys.AUTH_TYPE + "=" + Consts.AuthType.GITHUB; 20 | private final GithubAuthHelper githubHelper; 21 | 22 | public GithubCallbackPath(@NotNull GithubAuthHelper githubHelper) { 23 | this.githubHelper = githubHelper; 24 | } 25 | 26 | @Override 27 | public void handleRequest(HttpServerExchange exchange) throws Exception { 28 | exchange.startBlocking(); 29 | if (exchange.isInIoThread()) { 30 | exchange.dispatch(this); 31 | return; 32 | } 33 | 34 | String code = Utils.extractParam(exchange, "code"); 35 | if (code == null) { 36 | exchange.setStatusCode(StatusCodes.BAD_REQUEST); 37 | return; 38 | } 39 | 40 | try { 41 | String accessToken = githubHelper.exchangeCode(code); 42 | 43 | CookieImpl cookie = new CookieImpl("PYX-Github-Token", accessToken); 44 | cookie.setMaxAge(COOKIE_MAX_AGE); 45 | exchange.setResponseCookie(cookie); 46 | exchange.getResponseHeaders().add(Headers.LOCATION, REDIRECT_LOCATION); 47 | exchange.setStatusCode(StatusCodes.TEMPORARY_REDIRECT); 48 | } catch (Throwable ex) { 49 | logger.error("Failed processing the request: " + exchange, ex); 50 | throw ex; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/paths/TwitterCallbackPath.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.paths; 2 | 3 | import com.gianlu.pyxreloaded.Consts; 4 | import com.gianlu.pyxreloaded.Utils; 5 | import com.gianlu.pyxreloaded.socials.twitter.TwitterAuthHelper; 6 | import com.github.scribejava.core.model.OAuth1AccessToken; 7 | import com.github.scribejava.core.model.OAuth1RequestToken; 8 | import io.undertow.server.HttpHandler; 9 | import io.undertow.server.HttpServerExchange; 10 | import io.undertow.server.handlers.Cookie; 11 | import io.undertow.server.handlers.CookieImpl; 12 | import io.undertow.util.Headers; 13 | import io.undertow.util.StatusCodes; 14 | import org.apache.http.NameValuePair; 15 | import org.apache.http.client.utils.URLEncodedUtils; 16 | import org.apache.log4j.Logger; 17 | import org.jetbrains.annotations.NotNull; 18 | 19 | import java.nio.charset.Charset; 20 | import java.util.List; 21 | import java.util.Objects; 22 | import java.util.concurrent.TimeUnit; 23 | 24 | public class TwitterCallbackPath implements HttpHandler { 25 | private static final Logger logger = Logger.getLogger(TwitterCallbackPath.class); 26 | private static final int COOKIE_MAX_AGE = (int) TimeUnit.MINUTES.toSeconds(5); // sec 27 | private static final String REDIRECT_LOCATION = "/?" + Consts.GeneralKeys.AUTH_TYPE + "=" + Consts.AuthType.TWITTER; 28 | private final TwitterAuthHelper helper; 29 | 30 | public TwitterCallbackPath(@NotNull TwitterAuthHelper helper) { 31 | this.helper = helper; 32 | } 33 | 34 | @Override 35 | public void handleRequest(HttpServerExchange exchange) throws Exception { 36 | exchange.startBlocking(); 37 | if (exchange.isInIoThread()) { 38 | exchange.dispatch(this); 39 | return; 40 | } 41 | 42 | try { 43 | String token = Utils.extractParam(exchange, "oauth_token"); 44 | String verifier = Utils.extractParam(exchange, "oauth_verifier"); 45 | 46 | if (token == null || verifier == null) 47 | throw new IllegalArgumentException("Missing token or verifier!"); 48 | 49 | Cookie tokensCookie = exchange.getRequestCookies().get("PYX-Twitter-Token"); 50 | if (tokensCookie == null) 51 | throw new IllegalArgumentException("Missing 'PYX-Twitter-Token' cookie!"); 52 | 53 | List tokens = URLEncodedUtils.parse(tokensCookie.getValue(), Charset.forName("UTF-8")); 54 | if (!Objects.equals(token, Utils.get(tokens, "oauth_token"))) 55 | throw new IllegalStateException("Missing token in cookie or tokens don't match!"); 56 | 57 | String secret = Utils.get(tokens, "oauth_token_secret"); 58 | if (secret == null) 59 | throw new IllegalArgumentException("Missing token secret in cookie!"); 60 | 61 | OAuth1AccessToken accessToken = helper.accessToken(new OAuth1RequestToken(token, secret), verifier); 62 | CookieImpl cookie = new CookieImpl("PYX-Twitter-Token", accessToken.getRawResponse()); 63 | cookie.setMaxAge(COOKIE_MAX_AGE); 64 | exchange.setResponseCookie(cookie); 65 | exchange.getResponseHeaders().add(Headers.LOCATION, REDIRECT_LOCATION); 66 | exchange.setStatusCode(StatusCodes.TEMPORARY_REDIRECT); 67 | } catch (Throwable ex) { 68 | logger.error("Failed processing the request: " + exchange, ex); 69 | throw ex; 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/paths/TwitterStartAuthFlowPath.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.paths; 2 | 3 | import com.gianlu.pyxreloaded.socials.twitter.TwitterAuthHelper; 4 | import com.github.scribejava.core.model.OAuth1RequestToken; 5 | import io.undertow.server.HttpHandler; 6 | import io.undertow.server.HttpServerExchange; 7 | import io.undertow.server.handlers.CookieImpl; 8 | import io.undertow.util.Headers; 9 | import io.undertow.util.StatusCodes; 10 | import org.apache.log4j.Logger; 11 | import org.jetbrains.annotations.NotNull; 12 | 13 | import java.util.concurrent.TimeUnit; 14 | 15 | public class TwitterStartAuthFlowPath implements HttpHandler { 16 | private static final Logger logger = Logger.getLogger(TwitterStartAuthFlowPath.class); 17 | private static final int COOKIE_MAX_AGE = (int) TimeUnit.MINUTES.toSeconds(5); // sec 18 | private final TwitterAuthHelper helper; 19 | 20 | public TwitterStartAuthFlowPath(@NotNull TwitterAuthHelper helper) { 21 | this.helper = helper; 22 | } 23 | 24 | @Override 25 | public void handleRequest(HttpServerExchange exchange) throws Exception { 26 | exchange.startBlocking(); 27 | if (exchange.isInIoThread()) { 28 | exchange.dispatch(this); 29 | return; 30 | } 31 | 32 | try { 33 | OAuth1RequestToken token = helper.requestToken(); 34 | CookieImpl cookie = new CookieImpl("PYX-Twitter-Token", token.getRawResponse()); 35 | cookie.setMaxAge(COOKIE_MAX_AGE); 36 | exchange.setResponseCookie(cookie); 37 | exchange.getResponseHeaders().add(Headers.LOCATION, helper.authorizationUrl(token) + "&force_login=false"); 38 | exchange.setStatusCode(StatusCodes.TEMPORARY_REDIRECT); 39 | } catch (Throwable ex) { 40 | logger.error("Failed processing the request." + exchange, ex); 41 | throw ex; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/paths/VerifyEmailPath.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.paths; 2 | 3 | import com.gianlu.pyxreloaded.Utils; 4 | import com.gianlu.pyxreloaded.server.BaseCahHandler; 5 | import com.gianlu.pyxreloaded.singletons.Emails; 6 | import io.undertow.server.HttpHandler; 7 | import io.undertow.server.HttpServerExchange; 8 | import io.undertow.util.Headers; 9 | import io.undertow.util.StatusCodes; 10 | import org.apache.log4j.Logger; 11 | 12 | import java.sql.SQLException; 13 | 14 | public class VerifyEmailPath implements HttpHandler { 15 | private static final Logger logger = Logger.getLogger(VerifyEmailPath.class); 16 | private final Emails emails; 17 | 18 | public VerifyEmailPath(Emails emails) { 19 | this.emails = emails; 20 | } 21 | 22 | @Override 23 | public void handleRequest(HttpServerExchange exchange) { 24 | exchange.startBlocking(); 25 | if (exchange.isInIoThread()) { 26 | exchange.dispatch(this); 27 | return; 28 | } 29 | 30 | try { 31 | String token = Utils.extractParam(exchange, "token"); 32 | if (token == null) { 33 | exchange.setStatusCode(StatusCodes.BAD_REQUEST); 34 | exchange.getResponseSender().send("Missing token!"); 35 | return; 36 | } 37 | 38 | exchange.getResponseHeaders().add(Headers.CONTENT_TYPE, "text/html"); 39 | 40 | try { 41 | emails.tryVerify(token); 42 | exchange.setStatusCode(StatusCodes.OK); 43 | exchange.getResponseSender().send("Your email has been verified. You'll be redirected in a few seconds..."); 44 | } catch (SQLException ex) { 45 | exchange.setStatusCode(StatusCodes.FORBIDDEN); 46 | exchange.getResponseSender().send("Invalid token. Failed to verify your email or already verified."); 47 | } catch (BaseCahHandler.CahException ex) { 48 | exchange.setStatusCode(StatusCodes.BAD_REQUEST); 49 | exchange.getResponseSender().send(ex.getMessage()); 50 | } 51 | } catch (Throwable ex) { 52 | logger.error("Failed verifying email.", ex); 53 | throw ex; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/paths/VersionPath.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.paths; 2 | 3 | import com.gianlu.pyxreloaded.Utils; 4 | import com.google.gson.JsonObject; 5 | import io.undertow.server.HttpHandler; 6 | import io.undertow.server.HttpServerExchange; 7 | import io.undertow.util.Headers; 8 | 9 | public class VersionPath implements HttpHandler { 10 | private final String json; 11 | 12 | public VersionPath() { 13 | JsonObject obj = new JsonObject(); 14 | Package pkg = Package.getPackage("com.gianlu.pyxreloaded"); 15 | obj.addProperty("version", Utils.getServerVersion(pkg)); 16 | json = obj.toString(); 17 | } 18 | 19 | @Override 20 | public void handleRequest(HttpServerExchange exchange) { 21 | exchange.startBlocking(); 22 | if (exchange.isInIoThread()) { 23 | exchange.dispatch(this); 24 | return; 25 | } 26 | 27 | exchange.getResponseHeaders().add(Headers.CONTENT_TYPE, "application/json"); 28 | exchange.getResponseSender().send(json); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/paths/WebManifestPath.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.paths; 2 | 3 | import com.gianlu.pyxreloaded.singletons.Preferences; 4 | import com.google.gson.JsonObject; 5 | import com.google.gson.JsonParser; 6 | import io.undertow.server.HttpHandler; 7 | import io.undertow.server.HttpServerExchange; 8 | import io.undertow.server.handlers.Cookie; 9 | import io.undertow.util.Headers; 10 | import org.apache.log4j.Logger; 11 | 12 | import java.io.File; 13 | import java.io.FileInputStream; 14 | import java.io.FileNotFoundException; 15 | import java.io.InputStreamReader; 16 | import java.net.URLDecoder; 17 | 18 | public class WebManifestPath implements HttpHandler { 19 | private static final Logger logger = Logger.getLogger(WebManifestPath.class); 20 | private final JsonObject baseManifest; 21 | private final String baseManifestString; 22 | 23 | public WebManifestPath(Preferences preferences) throws FileNotFoundException { 24 | File manifest = new File(preferences.getString("webContent", "./WebContent"), "manifest.json"); 25 | if (!manifest.exists() && !manifest.canRead()) { 26 | logger.error("Missing base manifest file. Loading empty."); 27 | baseManifest = new JsonObject(); 28 | } else { 29 | baseManifest = new JsonParser().parse(new InputStreamReader(new FileInputStream(manifest))).getAsJsonObject(); 30 | } 31 | 32 | baseManifestString = baseManifest.toString(); 33 | } 34 | 35 | @Override 36 | public void handleRequest(HttpServerExchange exchange) throws Exception { 37 | exchange.startBlocking(); 38 | if (exchange.isInIoThread()) { 39 | exchange.dispatch(this); 40 | return; 41 | } 42 | 43 | exchange.getResponseHeaders().add(Headers.CONTENT_TYPE, "application/json"); 44 | 45 | Cookie primaryColor = exchange.getRequestCookies().get("PYX-Theme-Primary"); 46 | if (primaryColor == null) { 47 | exchange.getResponseSender().send(baseManifestString); 48 | } else { 49 | JsonObject manifest = baseManifest.deepCopy(); 50 | manifest.addProperty("theme_color", URLDecoder.decode(primaryColor.getValue(), "UTF-8")); 51 | exchange.getResponseSender().send(manifest.toString()); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/server/Annotations.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.server; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | public final class Annotations { 9 | @Target(ElementType.PARAMETER) 10 | @Retention(RetentionPolicy.RUNTIME) 11 | public @interface ConnectedUsers { 12 | } 13 | 14 | @Target(ElementType.PARAMETER) 15 | @Retention(RetentionPolicy.RUNTIME) 16 | public @interface GameManager { 17 | } 18 | 19 | @Target(ElementType.PARAMETER) 20 | @Retention(RetentionPolicy.RUNTIME) 21 | public @interface CardcastService { 22 | } 23 | 24 | @Target(ElementType.PARAMETER) 25 | @Retention(RetentionPolicy.RUNTIME) 26 | public @interface Preferences { 27 | } 28 | 29 | @Target(ElementType.PARAMETER) 30 | @Retention(RetentionPolicy.RUNTIME) 31 | public @interface LoadedCards { 32 | } 33 | 34 | @Target(ElementType.PARAMETER) 35 | @Retention(RetentionPolicy.RUNTIME) 36 | public @interface BanList { 37 | } 38 | 39 | @Target(ElementType.PARAMETER) 40 | @Retention(RetentionPolicy.RUNTIME) 41 | public @interface UsersWithAccount { 42 | } 43 | 44 | @Target(ElementType.PARAMETER) 45 | @Retention(RetentionPolicy.RUNTIME) 46 | public @interface SocialLogin { 47 | } 48 | 49 | @Target(ElementType.PARAMETER) 50 | @Retention(RetentionPolicy.RUNTIME) 51 | public @interface Emails { 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/server/BaseCahHandler.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.server; 2 | 3 | import com.gianlu.pyxreloaded.Consts; 4 | import com.gianlu.pyxreloaded.data.JsonWrapper; 5 | import com.gianlu.pyxreloaded.data.User; 6 | import com.gianlu.pyxreloaded.singletons.Handlers; 7 | import com.gianlu.pyxreloaded.singletons.Sessions; 8 | import com.google.gson.JsonElement; 9 | import io.undertow.server.HttpServerExchange; 10 | import io.undertow.server.handlers.Cookie; 11 | import io.undertow.util.StatusCodes; 12 | import org.jetbrains.annotations.Nullable; 13 | 14 | import java.io.IOException; 15 | 16 | public abstract class BaseCahHandler extends BaseJsonHandler { 17 | 18 | @Override 19 | protected JsonElement handle(HttpServerExchange exchange) throws StatusException { 20 | Cookie sid = exchange.getRequestCookies().get("PYX-Session"); 21 | User user = null; 22 | if (sid != null) user = Sessions.get().getUser(sid.getValue()); 23 | 24 | Parameters params; 25 | try { 26 | params = Parameters.fromExchange(exchange); 27 | } catch (IOException ex) { 28 | ex.printStackTrace(); 29 | throw new StatusException(StatusCodes.INTERNAL_SERVER_ERROR, ex); 30 | } 31 | 32 | String op = params.getStringNotNull(Consts.GeneralKeys.OP); 33 | if (!Handlers.skipUserCheck(op) && user == null) { 34 | throw new CahException(Consts.ErrorCode.NOT_REGISTERED); 35 | } else if (user != null && !user.isValid()) { 36 | Sessions.get().invalidate(sid.getValue()); 37 | throw new CahException(Consts.ErrorCode.SESSION_EXPIRED); 38 | } else { 39 | return handleRequest(op, user, params, exchange); 40 | } 41 | } 42 | 43 | protected abstract JsonElement handleRequest(@Nullable String op, @Nullable User user, Parameters params, HttpServerExchange exchange) throws StatusException; 44 | 45 | public static class CahException extends StatusException { 46 | public final Consts.ErrorCode code; 47 | public final JsonWrapper data; 48 | 49 | public CahException(Consts.ErrorCode code) { 50 | super(StatusCodes.CONFLICT); 51 | this.code = code; 52 | this.data = null; 53 | } 54 | 55 | public CahException(Consts.ErrorCode code, Throwable cause) { 56 | super(StatusCodes.CONFLICT, cause); 57 | this.code = code; 58 | this.data = null; 59 | } 60 | 61 | public CahException(Consts.ErrorCode code, JsonWrapper data) { 62 | super(StatusCodes.CONFLICT); 63 | this.code = code; 64 | this.data = data; 65 | } 66 | 67 | @Override 68 | public String getMessage() { 69 | return code + (data != null ? (": " + data.toString()) : ""); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/server/BaseJsonHandler.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.server; 2 | 3 | import com.gianlu.pyxreloaded.data.JsonWrapper; 4 | import com.google.gson.JsonElement; 5 | import io.undertow.server.HttpHandler; 6 | import io.undertow.server.HttpServerExchange; 7 | import io.undertow.util.Headers; 8 | import io.undertow.util.Methods; 9 | import io.undertow.util.StatusCodes; 10 | import org.apache.log4j.Logger; 11 | 12 | import java.nio.charset.Charset; 13 | 14 | public abstract class BaseJsonHandler implements HttpHandler { 15 | protected final static Logger logger = Logger.getLogger(BaseJsonHandler.class); 16 | 17 | @Override 18 | public void handleRequest(HttpServerExchange exchange) { 19 | if (exchange.getRequestMethod() == Methods.POST) { 20 | exchange.startBlocking(); 21 | if (exchange.isInIoThread()) { 22 | exchange.dispatch(this); 23 | return; 24 | } 25 | 26 | exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "application/json"); 27 | 28 | try { 29 | JsonElement json = handle(exchange); 30 | exchange.setStatusCode(StatusCodes.OK); 31 | exchange.getResponseSender().send(json.toString(), Charset.forName("UTF-8")); 32 | } catch (StatusException ex) { 33 | exchange.setStatusCode(ex.status); 34 | 35 | if (ex instanceof BaseCahHandler.CahException) { 36 | JsonWrapper obj = new JsonWrapper(((BaseCahHandler.CahException) ex).code); 37 | obj.addAll(((BaseCahHandler.CahException) ex).data); 38 | exchange.getResponseSender().send(obj.toString()); 39 | } 40 | } catch (Throwable ex) { 41 | logger.error("Failed processing the request: " + exchange, ex); 42 | } 43 | } else { 44 | exchange.setStatusCode(StatusCodes.METHOD_NOT_ALLOWED); 45 | } 46 | } 47 | 48 | protected abstract JsonElement handle(HttpServerExchange exchange) throws StatusException; 49 | 50 | public static class StatusException extends Exception { 51 | private final int status; 52 | 53 | StatusException(int status) { 54 | super(status + ": " + StatusCodes.getReason(status)); 55 | this.status = status; 56 | } 57 | 58 | StatusException(int status, Throwable cause) { 59 | super(status + ": " + StatusCodes.getReason(status), cause); 60 | cause.printStackTrace(); 61 | this.status = status; 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/server/CustomResourceHandler.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.server; 2 | 3 | import com.gianlu.pyxreloaded.Utils; 4 | import com.gianlu.pyxreloaded.singletons.Preferences; 5 | import io.undertow.server.HttpServerExchange; 6 | import io.undertow.server.handlers.resource.PathResourceManager; 7 | import io.undertow.server.handlers.resource.ResourceHandler; 8 | import io.undertow.server.handlers.resource.ResourceManager; 9 | import io.undertow.util.ETag; 10 | import io.undertow.util.HeaderMap; 11 | import io.undertow.util.Headers; 12 | import org.apache.log4j.Logger; 13 | import org.jetbrains.annotations.Nullable; 14 | 15 | import java.nio.file.Path; 16 | import java.nio.file.Paths; 17 | 18 | public class CustomResourceHandler extends ResourceHandler { 19 | private final boolean cacheEnabled; 20 | 21 | public CustomResourceHandler(Preferences preferences) { 22 | super(buildResourceManager(preferences)); 23 | 24 | cacheEnabled = preferences.getBoolean("cacheEnabled", true); 25 | setCachable(value -> cacheEnabled); 26 | } 27 | 28 | private static ResourceManager buildResourceManager(Preferences preferences) { 29 | Path root = Paths.get(preferences.getString("webContent", "./WebContent")).toAbsolutePath(); 30 | boolean cacheEnabled = preferences.getBoolean("cacheEnabled", true); 31 | 32 | return PathResourceManager.builder() 33 | .setBase(root) 34 | .setAllowResourceChangeListeners(false) 35 | .setETagFunction(new ETagSupplier(cacheEnabled, Utils.generateAlphanumericString(5))) 36 | .build(); 37 | } 38 | 39 | @Override 40 | public void handleRequest(HttpServerExchange exchange) throws Exception { 41 | super.handleRequest(exchange); 42 | 43 | HeaderMap headers = exchange.getResponseHeaders(); 44 | if (cacheEnabled) headers.add(Headers.CACHE_CONTROL, "private, no-cache"); 45 | else headers.add(Headers.CACHE_CONTROL, "private, no-store, no-cache"); 46 | } 47 | 48 | private static class ETagSupplier implements PathResourceManager.ETagFunction { 49 | private static final Logger logger = Logger.getLogger(ETagSupplier.class); 50 | private final boolean cacheEnabled; 51 | private final String serverTag; 52 | 53 | ETagSupplier(boolean cacheEnabled, String serverTag) { 54 | this.cacheEnabled = cacheEnabled; 55 | this.serverTag = serverTag; 56 | 57 | logger.info("ETag supplier: " + serverTag); 58 | } 59 | 60 | @Override 61 | @Nullable 62 | public ETag generate(Path path) { 63 | if (!cacheEnabled) return null; 64 | return new ETag(false, serverTag); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/server/HttpsRedirect.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.server; 2 | 3 | import io.undertow.server.HttpHandler; 4 | import io.undertow.server.HttpServerExchange; 5 | import io.undertow.util.Headers; 6 | import io.undertow.util.StatusCodes; 7 | import org.apache.http.client.utils.URIBuilder; 8 | 9 | public class HttpsRedirect implements HttpHandler { 10 | @Override 11 | public void handleRequest(HttpServerExchange exchange) throws Exception { 12 | URIBuilder builder = new URIBuilder(exchange.getRequestURL()); 13 | builder.setScheme("https"); 14 | 15 | exchange.setStatusCode(StatusCodes.MOVED_PERMANENTLY); 16 | exchange.getResponseHeaders().add(Headers.LOCATION, builder.toString()); 17 | exchange.endExchange(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/server/Parameters.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.server; 2 | 3 | import com.gianlu.pyxreloaded.Consts; 4 | import io.undertow.server.HttpServerExchange; 5 | import io.undertow.util.QueryParameterUtils; 6 | import org.jetbrains.annotations.NotNull; 7 | import org.jetbrains.annotations.Nullable; 8 | 9 | import java.io.ByteArrayOutputStream; 10 | import java.io.IOException; 11 | import java.io.InputStream; 12 | import java.util.Deque; 13 | import java.util.HashMap; 14 | import java.util.Map; 15 | 16 | public class Parameters extends HashMap { 17 | 18 | private Parameters() { 19 | } 20 | 21 | public static Parameters fromExchange(HttpServerExchange exchange) throws IOException { 22 | ByteArrayOutputStream out = new ByteArrayOutputStream(); 23 | 24 | InputStream in = exchange.getInputStream(); 25 | byte[] buffer = new byte[8 * 1024]; 26 | 27 | int read; 28 | while ((read = in.read(buffer)) != -1) 29 | out.write(buffer, 0, read); 30 | 31 | Parameters params = new Parameters(); 32 | Map> rawParams = QueryParameterUtils.parseQueryString(new String(out.toByteArray()), "UTF-8"); 33 | for (Map.Entry> entry : rawParams.entrySet()) 34 | params.put(entry.getKey(), entry.getValue().getFirst()); 35 | 36 | return params; 37 | } 38 | 39 | public boolean has(Consts.ReceivableKey key) { 40 | return get(key.toString()) != null; 41 | } 42 | 43 | @Nullable 44 | public String getString(Consts.ReceivableKey key) { 45 | return get(key.toString()); 46 | } 47 | 48 | @NotNull 49 | public String getStringNotNull(Consts.ReceivableKey key) throws BaseCahHandler.CahException { 50 | String val = getString(key); 51 | if (val == null) throw new BaseCahHandler.CahException(Consts.ErrorCode.BAD_REQUEST); 52 | else return val; 53 | } 54 | 55 | public boolean getBoolean(Consts.ReceivableKey key, boolean fallback) { 56 | return getBoolean(key.toString(), fallback); 57 | } 58 | 59 | private boolean getBoolean(String key, boolean fallback) { 60 | String val = get(key); 61 | if (val == null) return false; 62 | 63 | try { 64 | return Boolean.parseBoolean(val); 65 | } catch (IllegalArgumentException | NullPointerException ex) { 66 | return fallback; 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/server/Provider.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.server; 2 | 3 | public interface Provider { 4 | E get(); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/singletons/BanList.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.singletons; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | import java.sql.ResultSet; 6 | import java.sql.SQLException; 7 | 8 | public final class BanList { 9 | private final ServerDatabase db; 10 | 11 | public BanList(ServerDatabase db) { 12 | this.db = db; 13 | } 14 | 15 | public synchronized void add(@NotNull String ip) { 16 | try { 17 | db.statement().execute("INSERT OR IGNORE INTO ban_list (ip) VALUES ('" + ip + "')"); 18 | } catch (SQLException ex) { 19 | throw new RuntimeException(ex); 20 | } 21 | } 22 | 23 | public synchronized boolean contains(String ip) { 24 | try (ResultSet set = db.statement().executeQuery("SELECT * FROM ban_list WHERE ip='" + ip + "'")) { 25 | return set.next(); 26 | } catch (SQLException ex) { 27 | throw new RuntimeException(ex); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/singletons/Handlers.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.singletons; 2 | 3 | import com.gianlu.pyxreloaded.Consts; 4 | import com.gianlu.pyxreloaded.handlers.*; 5 | import com.gianlu.pyxreloaded.server.BaseCahHandler; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | import java.lang.reflect.Constructor; 9 | import java.lang.reflect.InvocationTargetException; 10 | import java.lang.reflect.Parameter; 11 | import java.util.ArrayList; 12 | import java.util.HashMap; 13 | import java.util.List; 14 | import java.util.Map; 15 | 16 | public final class Handlers { 17 | private final static Map> LIST; 18 | private final static List SKIP_USER_CHECK; 19 | private static final Map, BaseHandler> handlers = new HashMap<>(); 20 | 21 | static { 22 | LIST = new HashMap<>(); 23 | LIST.put(BanHandler.OP, BanHandler.class); 24 | LIST.put(CardcastAddCardsetHandler.OP, CardcastAddCardsetHandler.class); 25 | LIST.put(CardcastListCardsetsHandler.OP, CardcastListCardsetsHandler.class); 26 | LIST.put(CardcastRemoveCardsetHandler.OP, CardcastRemoveCardsetHandler.class); 27 | LIST.put(ChangeGameOptionsHandler.OP, ChangeGameOptionsHandler.class); 28 | LIST.put(ChatHandler.OP, ChatHandler.class); 29 | LIST.put(CreateGameHandler.OP, CreateGameHandler.class); 30 | LIST.put(FirstLoadHandler.OP, FirstLoadHandler.class); 31 | LIST.put(GameChatHandler.OP, GameChatHandler.class); 32 | LIST.put(GameListHandler.OP, GameListHandler.class); 33 | LIST.put(GetCardsHandler.OP, GetCardsHandler.class); 34 | LIST.put(GetGameInfoHandler.OP, GetGameInfoHandler.class); 35 | LIST.put(JoinGameHandler.OP, JoinGameHandler.class); 36 | LIST.put(JudgeSelectHandler.OP, JudgeSelectHandler.class); 37 | LIST.put(KickHandler.OP, KickHandler.class); 38 | LIST.put(LeaveGameHandler.OP, LeaveGameHandler.class); 39 | LIST.put(LogoutHandler.OP, LogoutHandler.class); 40 | LIST.put(NamesHandler.OP, NamesHandler.class); 41 | LIST.put(PlayCardHandler.OP, PlayCardHandler.class); 42 | LIST.put(RegisterHandler.OP, RegisterHandler.class); 43 | LIST.put(SpectateGameHandler.OP, SpectateGameHandler.class); 44 | LIST.put(StartGameHandler.OP, StartGameHandler.class); 45 | LIST.put(StopGameHandler.OP, StopGameHandler.class); 46 | LIST.put(LikeGameHandler.OP, LikeGameHandler.class); 47 | LIST.put(DislikeGameHandler.OP, DislikeGameHandler.class); 48 | LIST.put(GetMeHandler.OP, GetMeHandler.class); 49 | LIST.put(PongHandler.OP, PongHandler.class); 50 | LIST.put(GameOptionsSuggestionDecisionHandler.OP, GameOptionsSuggestionDecisionHandler.class); 51 | LIST.put(GetSuggestedGameOptionsHandler.OP, GetSuggestedGameOptionsHandler.class); 52 | LIST.put(CreateAccountHandler.OP, CreateAccountHandler.class); 53 | LIST.put(PrepareShutdownHandler.OP, PrepareShutdownHandler.class); 54 | LIST.put(GetUserPreferencesHandler.OP, GetUserPreferencesHandler.class); 55 | LIST.put(SetUserPreferencesHandler.OP, SetUserPreferencesHandler.class); 56 | 57 | SKIP_USER_CHECK = new ArrayList<>(); 58 | SKIP_USER_CHECK.add(RegisterHandler.OP); 59 | SKIP_USER_CHECK.add(FirstLoadHandler.OP); 60 | SKIP_USER_CHECK.add(CreateAccountHandler.OP); 61 | } 62 | 63 | public static boolean skipUserCheck(String op) { 64 | return SKIP_USER_CHECK.contains(op); 65 | } 66 | 67 | @NotNull 68 | public static BaseHandler obtain(String op) throws BaseCahHandler.CahException { 69 | BaseHandler handler; 70 | Class cls = Handlers.LIST.get(op); 71 | if (cls != null) { 72 | handler = handlers.get(cls); 73 | if (handler == null) { 74 | try { 75 | Constructor constructor = cls.getConstructors()[0]; 76 | Parameter[] parameters = constructor.getParameters(); 77 | Object[] objects = new Object[parameters.length]; 78 | 79 | for (int i = 0; i < parameters.length; i++) 80 | objects[i] = Providers.get(parameters[i].getAnnotations()[0].annotationType()).get(); 81 | 82 | handler = (BaseHandler) constructor.newInstance(objects); 83 | handlers.put(cls, handler); 84 | } catch (InstantiationException | IllegalAccessException | InvocationTargetException ex) { 85 | throw new BaseCahHandler.CahException(Consts.ErrorCode.BAD_OP, ex); 86 | } 87 | } 88 | } else { 89 | throw new BaseCahHandler.CahException(Consts.ErrorCode.BAD_OP); 90 | } 91 | 92 | return handler; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/singletons/LoadedCards.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.singletons; 2 | 3 | import com.gianlu.pyxreloaded.cards.PyxBlackCard; 4 | import com.gianlu.pyxreloaded.cards.PyxCardSet; 5 | import com.gianlu.pyxreloaded.cards.PyxWhiteCard; 6 | import org.apache.log4j.Logger; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | import java.sql.*; 10 | import java.util.HashSet; 11 | import java.util.Set; 12 | 13 | public final class LoadedCards { 14 | private static final Logger logger = Logger.getLogger(LoadedCards.class); 15 | private final Set sets = new HashSet<>(); 16 | private final Set whiteCards = new HashSet<>(); 17 | private final Set blackCards = new HashSet<>(); 18 | private final Connection conn; 19 | 20 | public LoadedCards(Preferences preferences) throws SQLException { 21 | conn = DriverManager.getConnection(preferences.getString("pyxDbUrl", "jdbc:sqlite:pyx.sqlite")); 22 | loadWhiteCards(); 23 | loadBlackCards(); 24 | loadSets(); 25 | logger.info("Successfully loaded " + sets.size() + " card sets, " + whiteCards.size() + " white cards and " + blackCards.size() + " black cards."); 26 | } 27 | 28 | @Override 29 | protected void finalize() throws Throwable { 30 | conn.close(); 31 | } 32 | 33 | @NotNull 34 | public Set getLoadedSets() { 35 | return sets; 36 | } 37 | 38 | private void loadWhiteCards() throws SQLException { 39 | try (Statement statement = conn.createStatement(); 40 | ResultSet resultSet = statement.executeQuery("SELECT * FROM white_cards")) { 41 | 42 | synchronized (whiteCards) { 43 | whiteCards.clear(); 44 | while (resultSet.next()) { 45 | whiteCards.add(new PyxWhiteCard(resultSet)); 46 | } 47 | } 48 | } 49 | } 50 | 51 | private void loadBlackCards() throws SQLException { 52 | try (Statement statement = conn.createStatement(); 53 | ResultSet resultSet = statement.executeQuery("SELECT * FROM black_cards")) { 54 | 55 | synchronized (blackCards) { 56 | blackCards.clear(); 57 | while (resultSet.next()) { 58 | blackCards.add(new PyxBlackCard(resultSet)); 59 | } 60 | } 61 | } 62 | } 63 | 64 | private Set getBlackCardIdsFor(int setId) throws SQLException { 65 | try (Statement statement = conn.createStatement(); 66 | ResultSet resultSet = statement.executeQuery("SELECT black_card_id FROM card_set_black_card WHERE card_set_id='" + setId + "'")) { 67 | 68 | Set ids = new HashSet<>(); 69 | while (resultSet.next()) ids.add(resultSet.getInt(1)); 70 | return ids; 71 | } 72 | } 73 | 74 | private Set getWhiteCardIdsFor(int setId) throws SQLException { 75 | try (Statement statement = conn.createStatement(); 76 | ResultSet resultSet = statement.executeQuery("SELECT white_card_id FROM card_set_white_card WHERE card_set_id='" + setId + "'")) { 77 | 78 | Set ids = new HashSet<>(); 79 | while (resultSet.next()) ids.add(resultSet.getInt(1)); 80 | return ids; 81 | } 82 | } 83 | 84 | private void loadSets() throws SQLException { 85 | try (Statement statement = conn.createStatement(); 86 | ResultSet resultSet = statement.executeQuery("SELECT * FROM card_set ORDER BY weight, id")) { 87 | 88 | synchronized (sets) { 89 | sets.clear(); 90 | while (resultSet.next()) { 91 | PyxCardSet set = new PyxCardSet(resultSet); 92 | 93 | Set blackCardIds = getBlackCardIdsFor(set.getId()); 94 | for (PyxBlackCard blackCard : blackCards) 95 | if (blackCardIds.contains(blackCard.getId())) 96 | set.getBlackCards().add(blackCard); 97 | 98 | 99 | Set whiteCardIds = getWhiteCardIdsFor(set.getId()); 100 | for (PyxWhiteCard whiteCard : whiteCards) 101 | if (whiteCardIds.contains(whiteCard.getId())) 102 | set.getWhiteCards().add(whiteCard); 103 | 104 | sets.add(set); 105 | } 106 | } 107 | } 108 | } 109 | 110 | public void close() throws SQLException { 111 | conn.close(); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/singletons/PreparingShutdown.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.singletons; 2 | 3 | import com.gianlu.pyxreloaded.Consts; 4 | import com.gianlu.pyxreloaded.data.EventWrapper; 5 | import com.gianlu.pyxreloaded.data.QueuedMessage; 6 | import com.gianlu.pyxreloaded.server.BaseCahHandler; 7 | import io.undertow.Undertow; 8 | import org.apache.log4j.Logger; 9 | import org.jetbrains.annotations.NotNull; 10 | 11 | import java.io.IOException; 12 | import java.sql.SQLException; 13 | import java.util.concurrent.ScheduledThreadPoolExecutor; 14 | import java.util.concurrent.TimeUnit; 15 | 16 | public final class PreparingShutdown { 17 | private static final Logger logger = Logger.getLogger(PreparingShutdown.class); 18 | private static PreparingShutdown instance; 19 | private final Undertow server; 20 | private final ScheduledThreadPoolExecutor globalTimer; 21 | private final ConnectedUsers users; 22 | private final SocialLogin socials; 23 | private final LoadedCards loadedCards; 24 | private final ServerDatabase serverDatabase; 25 | private final Object waitForEventsToBeSent = new Object(); 26 | private boolean value = false; 27 | private long shutdownTime = -1; 28 | 29 | private PreparingShutdown(Undertow server, ScheduledThreadPoolExecutor globalTimer, ConnectedUsers users, SocialLogin socials, LoadedCards loadedCards, ServerDatabase serverDatabase) { 30 | this.server = server; 31 | this.globalTimer = globalTimer; 32 | this.users = users; 33 | this.socials = socials; 34 | this.loadedCards = loadedCards; 35 | this.serverDatabase = serverDatabase; 36 | } 37 | 38 | @NotNull 39 | public static PreparingShutdown get() { 40 | if (instance == null) throw new IllegalStateException(); 41 | return instance; 42 | } 43 | 44 | public static void setup(Undertow server, ScheduledThreadPoolExecutor globalTimer, ConnectedUsers users, SocialLogin socials, LoadedCards loadedCards, ServerDatabase serverDatabase) { 45 | instance = new PreparingShutdown(server, globalTimer, users, socials, loadedCards, serverDatabase); 46 | } 47 | 48 | public synchronized void check() throws BaseCahHandler.CahException { 49 | if (value) throw new BaseCahHandler.CahException(Consts.ErrorCode.PREPARING_SHUTDOWN); 50 | } 51 | 52 | public synchronized boolean is() { 53 | return value; 54 | } 55 | 56 | public synchronized void set(boolean set) { 57 | if (!value) { 58 | value = set; 59 | shutdownTime = System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(10); 60 | 61 | globalTimer.schedule(this::kickAndShutdown, shutdownTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS); 62 | users.broadcastToAll(QueuedMessage.MessageType.SERVER, getEvent()); 63 | 64 | logger.info("Shutdown will happen at " + shutdownTime); 65 | } 66 | } 67 | 68 | public EventWrapper getEvent() { 69 | EventWrapper ev = new EventWrapper(Consts.Event.PREPARING_SHUTDOWN); 70 | ev.add(Consts.GeneralKeys.BEFORE_SHUTDOWN, shutdownTime - System.currentTimeMillis()); 71 | return ev; 72 | } 73 | 74 | public void allEventsSent() { 75 | synchronized (waitForEventsToBeSent) { 76 | waitForEventsToBeSent.notifyAll(); 77 | } 78 | } 79 | 80 | private void kickAndShutdown() { 81 | users.preShutdown(); 82 | shutdownNow(); 83 | } 84 | 85 | /** 86 | * Shutdown the server if it can be done safely 87 | */ 88 | public void tryShutdown() { 89 | if (value && users.canShutdown()) { 90 | shutdownNow(); 91 | } 92 | } 93 | 94 | /** 95 | * Shutdown the server independently of its status 96 | */ 97 | private void shutdownNow() { 98 | logger.info("Waiting for events to be sent..."); 99 | 100 | synchronized (waitForEventsToBeSent) { 101 | try { 102 | waitForEventsToBeSent.wait(); 103 | } catch (InterruptedException ignored) { 104 | } 105 | } 106 | 107 | logger.info("Shutting down the server!"); 108 | 109 | try { 110 | server.stop(); 111 | socials.close(); 112 | loadedCards.close(); 113 | serverDatabase.close(); 114 | System.exit(0); 115 | } catch (SQLException | IOException ex) { 116 | logger.error("Shutdown wasn't clear.", ex); 117 | System.exit(1); 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/singletons/Providers.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.singletons; 2 | 3 | import com.gianlu.pyxreloaded.server.Provider; 4 | import org.apache.log4j.Logger; 5 | 6 | import java.lang.annotation.Annotation; 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | public final class Providers { 11 | private final static Logger logger = Logger.getLogger(Providers.class); 12 | private final static Map, Provider> providers = new HashMap, Provider>() { 13 | @Override 14 | public Provider put(Class key, Provider value) { 15 | logger.trace("Added provider for " + key); 16 | return super.put(key, value); 17 | } 18 | }; 19 | 20 | private Providers() { 21 | throw new UnsupportedOperationException(); 22 | } 23 | 24 | @SuppressWarnings("unchecked") 25 | public static

    Provider

    get(Class cls) { 26 | return (Provider

    ) providers.get(cls); 27 | } 28 | 29 | public static void add(Class cls, Provider provider) { 30 | providers.put(cls, provider); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/singletons/ServerDatabase.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.singletons; 2 | 3 | import java.sql.Connection; 4 | import java.sql.DriverManager; 5 | import java.sql.SQLException; 6 | import java.sql.Statement; 7 | 8 | public final class ServerDatabase { 9 | private final Connection conn; 10 | 11 | public ServerDatabase(Preferences preferences) throws SQLException { 12 | String username = preferences.getString("serverDb/username", null); 13 | String password = preferences.getString("serverDb/password", null); 14 | 15 | if (username != null && !username.isEmpty() && password != null) 16 | conn = DriverManager.getConnection(preferences.getString("serverDb/url", "jdbc:sqlite:server.sqlite"), username, password); 17 | else 18 | conn = DriverManager.getConnection(preferences.getString("serverDb/url", "jdbc:sqlite:server.sqlite")); 19 | } 20 | 21 | public Statement statement() throws SQLException { 22 | return conn.createStatement(); 23 | } 24 | 25 | public void close() throws SQLException { 26 | conn.close(); 27 | } 28 | 29 | @Override 30 | protected void finalize() throws Throwable { 31 | conn.close(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/singletons/Sessions.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.singletons; 2 | 3 | import com.gianlu.pyxreloaded.Utils; 4 | import com.gianlu.pyxreloaded.data.User; 5 | import org.jetbrains.annotations.Contract; 6 | import org.jetbrains.annotations.NotNull; 7 | import org.jetbrains.annotations.Nullable; 8 | 9 | import java.util.HashMap; 10 | import java.util.Map; 11 | 12 | public final class Sessions { 13 | private static final int SID_LENGTH = 24; 14 | private static Sessions instance; 15 | private final Map sessions; 16 | 17 | private Sessions() { 18 | sessions = new HashMap<>(); 19 | } 20 | 21 | @NotNull 22 | public static Sessions get() { 23 | if (instance == null) instance = new Sessions(); 24 | return instance; 25 | } 26 | 27 | @NotNull 28 | public static String generateNewId() { 29 | return Utils.generateAlphanumericString(SID_LENGTH); 30 | } 31 | 32 | @Contract("null -> null") 33 | @Nullable 34 | public synchronized User getUser(String sid) { 35 | if (sid == null || sid.isEmpty() || sid.length() != SID_LENGTH) return null; 36 | return sessions.get(sid); 37 | } 38 | 39 | public synchronized void invalidate(String sid) { 40 | sessions.remove(sid); 41 | } 42 | 43 | @NotNull 44 | public synchronized String add(@NotNull User user) { 45 | sessions.put(user.getSessionId(), user); 46 | return user.getSessionId(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/socials/facebook/FacebookAuthHelper.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.socials.facebook; 2 | 3 | import com.gianlu.pyxreloaded.singletons.Preferences; 4 | import com.google.gson.JsonElement; 5 | import com.google.gson.JsonObject; 6 | import com.google.gson.JsonParseException; 7 | import com.google.gson.JsonParser; 8 | import org.apache.http.HttpEntity; 9 | import org.apache.http.HttpResponse; 10 | import org.apache.http.client.methods.HttpGet; 11 | import org.apache.http.impl.client.CloseableHttpClient; 12 | import org.apache.http.impl.client.HttpClients; 13 | import org.jetbrains.annotations.NotNull; 14 | import org.jetbrains.annotations.Nullable; 15 | 16 | import java.io.IOException; 17 | import java.io.InputStreamReader; 18 | 19 | public class FacebookAuthHelper { 20 | private static final String DEBUG_TOKEN = "https://graph.facebook.com/debug_token"; 21 | private static final String GRAPH = "https://graph.facebook.com/"; 22 | private final CloseableHttpClient client; 23 | private final String appToken; 24 | private final JsonParser parser = new JsonParser(); 25 | private final String appId; 26 | 27 | private FacebookAuthHelper(@NotNull String appId, @NotNull String appSecret) { 28 | this.client = HttpClients.createDefault(); 29 | this.appId = appId; 30 | this.appToken = appId + "%7C" + appSecret; // URL encoded 31 | } 32 | 33 | @Nullable 34 | public static FacebookAuthHelper instantiate(Preferences preferences) { 35 | String appId = preferences.getString("socials/facebookAppId", null); 36 | if (appId == null || appId.isEmpty()) return null; 37 | 38 | String appSecret = preferences.getString("socials/facebookAppSecret", null); 39 | if (appSecret == null || appSecret.isEmpty()) return null; 40 | 41 | return new FacebookAuthHelper(appId, appSecret); 42 | } 43 | 44 | @NotNull 45 | public String appId() { 46 | return appId; 47 | } 48 | 49 | @Nullable 50 | public FacebookToken verify(@NotNull String accessToken) throws IOException, FacebookOAuthException { 51 | HttpGet get = new HttpGet(DEBUG_TOKEN + "?input_token=" + accessToken + "&access_token=" + appToken); 52 | try { 53 | HttpResponse resp = client.execute(get); 54 | 55 | HttpEntity entity = resp.getEntity(); 56 | if (entity == null) throw new IOException(new NullPointerException("HttpEntity is null")); 57 | 58 | JsonElement element = parser.parse(new InputStreamReader(entity.getContent())); 59 | if (!element.isJsonObject()) throw new IOException("Response is not of type JsonObject"); 60 | 61 | JsonObject obj = element.getAsJsonObject(); 62 | if (obj.has("data")) { 63 | FacebookToken token = new FacebookToken(obj.getAsJsonObject("data")); 64 | if (token.valid && appId.equals(token.appId)) return token; 65 | else return null; 66 | } else if (obj.has("error")) { 67 | throw new FacebookOAuthException(obj.getAsJsonObject("error")); 68 | } else { 69 | throw new IOException("What is that? " + obj); 70 | } 71 | } catch (JsonParseException | NullPointerException ex) { 72 | throw new IOException(ex); 73 | } finally { 74 | get.releaseConnection(); 75 | } 76 | } 77 | 78 | @NotNull 79 | public FacebookProfileInfo info(String userId) throws FacebookOAuthException, IOException, FacebookEmailNotVerifiedException { 80 | HttpGet get = new HttpGet(GRAPH + userId + "/?fields=picture.type(large),email&access_token=" + appToken); 81 | try { 82 | HttpResponse resp = client.execute(get); 83 | 84 | HttpEntity entity = resp.getEntity(); 85 | if (entity == null) throw new IOException(new NullPointerException("HttpEntity is null")); 86 | 87 | JsonElement element = parser.parse(new InputStreamReader(entity.getContent())); 88 | if (!element.isJsonObject()) throw new IOException("Response is not of type JsonObject"); 89 | 90 | JsonObject obj = element.getAsJsonObject(); 91 | if (obj.has("error")) throw new FacebookOAuthException(obj.getAsJsonObject("error")); 92 | else return new FacebookProfileInfo(obj); 93 | } catch (JsonParseException | NullPointerException ex) { 94 | throw new IOException(ex); 95 | } finally { 96 | get.releaseConnection(); 97 | } 98 | } 99 | 100 | public void close() throws IOException { 101 | client.close(); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/socials/facebook/FacebookEmailNotVerifiedException.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.socials.facebook; 2 | 3 | public class FacebookEmailNotVerifiedException extends Exception { 4 | } 5 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/socials/facebook/FacebookOAuthException.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.socials.facebook; 2 | 3 | import com.google.gson.JsonObject; 4 | 5 | public class FacebookOAuthException extends Exception { 6 | FacebookOAuthException(JsonObject error) { 7 | super(error.get("message").getAsString()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/socials/facebook/FacebookProfileInfo.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.socials.facebook; 2 | 3 | import com.google.gson.JsonElement; 4 | import com.google.gson.JsonObject; 5 | 6 | public class FacebookProfileInfo { 7 | public final String email; 8 | public final String pictureUrl; 9 | 10 | FacebookProfileInfo(JsonObject obj) throws NullPointerException, FacebookEmailNotVerifiedException { 11 | JsonElement email = obj.get("email"); 12 | if (email == null || email.isJsonNull()) throw new FacebookEmailNotVerifiedException(); 13 | else this.email = email.getAsString(); 14 | 15 | JsonObject picture = obj.getAsJsonObject("picture").getAsJsonObject("data"); 16 | this.pictureUrl = picture.get("url").getAsString(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/socials/facebook/FacebookToken.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.socials.facebook; 2 | 3 | import com.google.gson.JsonObject; 4 | 5 | public class FacebookToken { 6 | public final String appId; 7 | public final String userId; 8 | final boolean valid; 9 | 10 | FacebookToken(JsonObject obj) throws NullPointerException { 11 | appId = obj.get("app_id").getAsString(); 12 | valid = obj.get("is_valid").getAsBoolean(); 13 | userId = obj.get("user_id").getAsString(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/socials/github/GithubEmails.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.socials.github; 2 | 3 | import com.google.gson.JsonArray; 4 | import com.google.gson.JsonElement; 5 | import com.google.gson.JsonObject; 6 | 7 | import java.util.ArrayList; 8 | 9 | public class GithubEmails extends ArrayList { 10 | GithubEmails(JsonArray array) { 11 | for (JsonElement element : array) add(new Email(element.getAsJsonObject())); 12 | } 13 | 14 | public boolean isPrimaryEmailVerified() { 15 | for (Email email : this) { 16 | if (email.primary && email.verified) 17 | return true; 18 | } 19 | 20 | return false; 21 | } 22 | 23 | public static class Email { 24 | public final boolean verified; 25 | public final boolean primary; 26 | public final String email; 27 | 28 | Email(JsonObject obj) { 29 | verified = obj.get("verified").getAsBoolean(); 30 | primary = obj.get("primary").getAsBoolean(); 31 | email = obj.get("email").getAsString(); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/socials/github/GithubException.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.socials.github; 2 | 3 | import com.google.gson.JsonObject; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | public class GithubException extends Exception { 7 | private GithubException(String message) { 8 | super(message); 9 | } 10 | 11 | @NotNull 12 | public static GithubException invalidScopes() { 13 | return new GithubException("Invalid scopes!"); 14 | } 15 | 16 | @NotNull 17 | public static GithubException fromMessage(JsonObject obj) { 18 | return new GithubException(obj.get("message").getAsString()); 19 | } 20 | 21 | @NotNull 22 | public static GithubException fromError(JsonObject obj) { 23 | return new GithubException(obj.get("error").getAsString()); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/socials/github/GithubProfileInfo.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.socials.github; 2 | 3 | import com.google.gson.JsonObject; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | public class GithubProfileInfo { 7 | public final String id; 8 | public final String email; 9 | public final String avatarUrl; 10 | public final GithubEmails emails; 11 | 12 | GithubProfileInfo(JsonObject obj, @NotNull GithubEmails emails) { 13 | this.id = obj.get("id").getAsString(); 14 | this.email = obj.get("email").getAsString(); 15 | this.avatarUrl = obj.get("avatar_url").getAsString(); 16 | this.emails = emails; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/socials/google/GoogleApacheHttpRequest.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.socials.google; 2 | 3 | import com.google.api.client.http.LowLevelHttpRequest; 4 | import com.google.api.client.http.LowLevelHttpResponse; 5 | import com.google.api.client.util.Preconditions; 6 | import com.google.api.client.util.StreamingContent; 7 | import org.apache.http.HttpEntityEnclosingRequest; 8 | import org.apache.http.client.HttpClient; 9 | import org.apache.http.client.config.RequestConfig; 10 | import org.apache.http.client.methods.HttpRequestBase; 11 | import org.apache.http.entity.AbstractHttpEntity; 12 | 13 | import java.io.IOException; 14 | import java.io.InputStream; 15 | import java.io.OutputStream; 16 | 17 | final class GoogleApacheHttpRequest extends LowLevelHttpRequest { 18 | private final HttpClient httpClient; 19 | private final HttpRequestBase request; 20 | 21 | GoogleApacheHttpRequest(HttpClient httpClient, HttpRequestBase request) { 22 | this.httpClient = httpClient; 23 | this.request = request; 24 | } 25 | 26 | @Override 27 | public void addHeader(String name, String value) { 28 | request.addHeader(name, value); 29 | } 30 | 31 | @Override 32 | public void setTimeout(int connectTimeout, int readTimeout) { 33 | request.setConfig(RequestConfig.custom() 34 | .setConnectionRequestTimeout(connectTimeout) 35 | .setConnectTimeout(connectTimeout) 36 | .setSocketTimeout(readTimeout) 37 | .build()); 38 | } 39 | 40 | @Override 41 | public LowLevelHttpResponse execute() throws IOException { 42 | if (getStreamingContent() != null) { 43 | ContentEntity entity = new ContentEntity(getContentLength(), getStreamingContent()); 44 | entity.setContentEncoding(getContentEncoding()); 45 | entity.setContentType(getContentType()); 46 | ((HttpEntityEnclosingRequest) request).setEntity(entity); 47 | } 48 | 49 | return new GoogleApacheHttpResponse(request, httpClient.execute(request)); 50 | } 51 | 52 | private static final class ContentEntity extends AbstractHttpEntity { 53 | private final long contentLength; 54 | private final StreamingContent streamingContent; 55 | 56 | ContentEntity(long contentLength, StreamingContent streamingContent) { 57 | this.contentLength = contentLength; 58 | this.streamingContent = Preconditions.checkNotNull(streamingContent); 59 | } 60 | 61 | @Override 62 | public InputStream getContent() { 63 | throw new UnsupportedOperationException(); 64 | } 65 | 66 | @Override 67 | public long getContentLength() { 68 | return contentLength; 69 | } 70 | 71 | @Override 72 | public boolean isRepeatable() { 73 | return false; 74 | } 75 | 76 | @Override 77 | public boolean isStreaming() { 78 | return true; 79 | } 80 | 81 | @Override 82 | public void writeTo(OutputStream out) throws IOException { 83 | if (contentLength != 0) { 84 | streamingContent.writeTo(out); 85 | } 86 | } 87 | } 88 | } 89 | 90 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/socials/google/GoogleApacheHttpResponse.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.socials.google; 2 | 3 | import com.google.api.client.http.LowLevelHttpResponse; 4 | import org.apache.http.Header; 5 | import org.apache.http.HttpEntity; 6 | import org.apache.http.HttpResponse; 7 | import org.apache.http.StatusLine; 8 | import org.apache.http.client.methods.HttpRequestBase; 9 | 10 | import java.io.IOException; 11 | import java.io.InputStream; 12 | 13 | final class GoogleApacheHttpResponse extends LowLevelHttpResponse { 14 | private final HttpRequestBase request; 15 | private final HttpResponse response; 16 | private final Header[] allHeaders; 17 | 18 | GoogleApacheHttpResponse(HttpRequestBase request, HttpResponse response) { 19 | this.request = request; 20 | this.response = response; 21 | allHeaders = response.getAllHeaders(); 22 | } 23 | 24 | @Override 25 | public int getStatusCode() { 26 | StatusLine statusLine = response.getStatusLine(); 27 | return statusLine == null ? 0 : statusLine.getStatusCode(); 28 | } 29 | 30 | @Override 31 | public InputStream getContent() throws IOException { 32 | HttpEntity entity = response.getEntity(); 33 | return entity == null ? null : entity.getContent(); 34 | } 35 | 36 | @Override 37 | public String getContentEncoding() { 38 | HttpEntity entity = response.getEntity(); 39 | if (entity != null) { 40 | Header contentEncodingHeader = entity.getContentEncoding(); 41 | if (contentEncodingHeader != null) { 42 | return contentEncodingHeader.getValue(); 43 | } 44 | } 45 | 46 | return null; 47 | } 48 | 49 | @Override 50 | public long getContentLength() { 51 | HttpEntity entity = response.getEntity(); 52 | return entity == null ? -1 : entity.getContentLength(); 53 | } 54 | 55 | @Override 56 | public String getContentType() { 57 | HttpEntity entity = response.getEntity(); 58 | if (entity != null) { 59 | Header contentTypeHeader = entity.getContentType(); 60 | if (contentTypeHeader != null) { 61 | return contentTypeHeader.getValue(); 62 | } 63 | } 64 | 65 | return null; 66 | } 67 | 68 | @Override 69 | public String getReasonPhrase() { 70 | StatusLine statusLine = response.getStatusLine(); 71 | return statusLine == null ? null : statusLine.getReasonPhrase(); 72 | } 73 | 74 | @Override 75 | public String getStatusLine() { 76 | StatusLine statusLine = response.getStatusLine(); 77 | return statusLine == null ? null : statusLine.toString(); 78 | } 79 | 80 | @Override 81 | public int getHeaderCount() { 82 | return allHeaders.length; 83 | } 84 | 85 | @Override 86 | public String getHeaderName(int index) { 87 | return allHeaders[index].getName(); 88 | } 89 | 90 | @Override 91 | public String getHeaderValue(int index) { 92 | return allHeaders[index].getValue(); 93 | } 94 | 95 | @Override 96 | public void disconnect() { 97 | request.releaseConnection(); 98 | } 99 | } 100 | 101 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/socials/twitter/TwitterEmailNotVerifiedException.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.socials.twitter; 2 | 3 | public class TwitterEmailNotVerifiedException extends Exception { 4 | } 5 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/socials/twitter/TwitterProfileInfo.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.socials.twitter; 2 | 3 | import com.google.gson.JsonElement; 4 | import com.google.gson.JsonObject; 5 | 6 | public class TwitterProfileInfo { 7 | public final String id; 8 | public final String avatarUrl; 9 | public final String email; 10 | 11 | TwitterProfileInfo(JsonObject obj) throws TwitterEmailNotVerifiedException { 12 | this.id = obj.get("id_str").getAsString(); 13 | this.avatarUrl = obj.get("profile_image_url_https").getAsString() 14 | .replaceAll("(_(?:normal|bigger|mini))", ""); 15 | 16 | JsonElement email = obj.get("email"); 17 | if (email == null || email.isJsonNull()) throw new TwitterEmailNotVerifiedException(); 18 | else this.email = email.getAsString(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/task/BroadcastGameListUpdateTask.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.task; 2 | 3 | import com.gianlu.pyxreloaded.Consts; 4 | import com.gianlu.pyxreloaded.data.EventWrapper; 5 | import com.gianlu.pyxreloaded.data.QueuedMessage.MessageType; 6 | import com.gianlu.pyxreloaded.singletons.ConnectedUsers; 7 | 8 | public class BroadcastGameListUpdateTask extends SafeTimerTask { 9 | private final ConnectedUsers users; 10 | private volatile boolean needsUpdate = false; 11 | 12 | public BroadcastGameListUpdateTask(final ConnectedUsers users) { 13 | this.users = users; 14 | } 15 | 16 | public void needsUpdate() { 17 | needsUpdate = true; 18 | } 19 | 20 | @Override 21 | public void process() { 22 | if (needsUpdate) { 23 | users.broadcastToAll(MessageType.GAME_EVENT, new EventWrapper(Consts.Event.GAME_LIST_REFRESH)); 24 | needsUpdate = false; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/task/SafeTimerTask.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.task; 2 | 3 | import org.apache.log4j.Logger; 4 | 5 | 6 | public abstract class SafeTimerTask implements Runnable { 7 | private static final Logger logger = Logger.getLogger(SafeTimerTask.class); 8 | 9 | @Override 10 | public final void run() { 11 | try { 12 | process(); 13 | } catch (final Exception e) { 14 | logger.error("Exception running SafeTimerTask", e); 15 | } 16 | } 17 | 18 | public abstract void process(); 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/gianlu/pyxreloaded/task/UserPingTask.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded.task; 2 | 3 | import com.gianlu.pyxreloaded.singletons.ConnectedUsers; 4 | 5 | import java.util.concurrent.ScheduledThreadPoolExecutor; 6 | 7 | 8 | public class UserPingTask extends SafeTimerTask { 9 | private final ConnectedUsers users; 10 | private final ScheduledThreadPoolExecutor globalTimer; 11 | 12 | public UserPingTask(ConnectedUsers users, ScheduledThreadPoolExecutor globalTimer) { 13 | this.users = users; 14 | this.globalTimer = globalTimer; 15 | } 16 | 17 | @Override 18 | public void process() { 19 | users.checkForPingAndIdleTimeouts(); 20 | globalTimer.purge(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/test/java/com/gianlu/pyxreloaded/ConstantsTest.java: -------------------------------------------------------------------------------- 1 | package com.gianlu.pyxreloaded; 2 | 3 | import org.jetbrains.annotations.Nullable; 4 | import org.junit.jupiter.api.Assertions; 5 | import org.junit.jupiter.api.Test; 6 | 7 | import java.lang.annotation.Annotation; 8 | 9 | public class ConstantsTest { 10 | 11 | private static void crossCheck(Class firstEnum, Class secondEnum) throws NoSuchFieldException { 12 | for (Object firstEnumConst : firstEnum.getEnumConstants()) { 13 | for (Object secondEnumConst : secondEnum.getEnumConstants()) { 14 | if (firstEnumConst == secondEnumConst || shouldIgnoreDuplicate(firstEnumConst, secondEnumConst)) 15 | continue; 16 | Assertions.assertNotEquals(firstEnumConst.toString(), secondEnumConst.toString(), 17 | "Found equal value '" + firstEnumConst.toString() + "' in " + firstEnum.getSimpleName() + " and " + secondEnum.getSimpleName()); 18 | } 19 | } 20 | } 21 | 22 | public static boolean shouldIgnoreDuplicate(Object firstEnumConst, Object secondEnumConst) throws NoSuchFieldException { 23 | Class firstIgnoreClass = getIgnoreDuplicateClass(firstEnumConst); 24 | Class secondIgnoreClass = getIgnoreDuplicateClass(secondEnumConst); 25 | 26 | if (firstIgnoreClass == null && secondIgnoreClass == null) return false; 27 | 28 | if (firstIgnoreClass != null) { 29 | if (secondEnumConst.getClass() == firstIgnoreClass) 30 | return true; 31 | } 32 | 33 | return secondIgnoreClass != null && firstEnumConst.getClass() == secondIgnoreClass; 34 | 35 | } 36 | 37 | @Nullable 38 | public static Class getIgnoreDuplicateClass(Object enumConst) throws NoSuchFieldException { 39 | for (Annotation annotation : enumConst.getClass().getField(((Enum) enumConst).name()).getAnnotations()) { 40 | if (annotation.annotationType() == Consts.IgnoreDuplicateIn.class) 41 | return ((Consts.IgnoreDuplicateIn) annotation).value(); 42 | } 43 | 44 | return null; 45 | } 46 | 47 | @Test 48 | public void checkConstants() throws NoSuchFieldException { 49 | Class[] classes = Consts.class.getClasses(); 50 | 51 | for (Class klass : classes) { 52 | for (Class kklass : classes) { 53 | if (klass.isEnum() && kklass.isEnum()) crossCheck(klass, kklass); 54 | } 55 | } 56 | } 57 | } 58 | --------------------------------------------------------------------------------