├── robots.txt ├── data.php ├── .gitignore ├── screenshot.png ├── utils ├── logout.php ├── login.php ├── login_check.php ├── data_management.php └── get_game_data.php ├── .htaccess ├── views ├── login_form.php ├── footer.php ├── settings.php ├── add_game.php └── game_list.php ├── LICENSE ├── README.md ├── config.php ├── _config.php ├── notifications_check.php ├── setup.php ├── api.php └── index.php /robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: / -------------------------------------------------------------------------------- /data.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.idea 2 | config.php 3 | data.php 4 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lx4r/chames/HEAD/screenshot.png -------------------------------------------------------------------------------- /utils/logout.php: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |
5 |
6 |
"> 7 | 8 | 9 |
10 | 11 |
12 |
13 | 14 |
15 |
16 | -------------------------------------------------------------------------------- /utils/login_check.php: -------------------------------------------------------------------------------- 1 | 1800)) { 7 | session_unset(); // Unset the session variable 8 | session_destroy(); // Destroy the session data in memory 9 | 10 | /* Session is new enough -> user authorised */ 11 | } else { 12 | $loggedIn = true; 13 | $_SESSION['LAST_ACTIVITY'] = time(); // Update the session's timestamp 14 | } 15 | /* If the right secret can't be found in the session the user stays logged out */ 16 | } 17 | -------------------------------------------------------------------------------- /views/footer.php: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | 6 |
7 |
8 |

9 | This software is not in any way affiliated with g2a.com.
10 | Chames by lx4r and awesome contributors 11 |

12 |
13 | 16 |
17 |
18 | 19 |
20 |
21 | 24 |
25 |
26 | -------------------------------------------------------------------------------- /utils/data_management.php: -------------------------------------------------------------------------------- 1 | ', file_get_contents($file)); 18 | if (isset($data[1])){ 19 | return json_decode($data[1], true, 512, JSON_UNESCAPED_UNICODE); 20 | } else { 21 | return false; 22 | } 23 | } 24 | 25 | /* Save the games' data to the json file */ 26 | function SaveData($file, $newData){ 27 | $data = '' . json_encode($newData, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); 28 | return file_put_contents($file, $data); 29 | } 30 | 31 | 32 | ?> 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 lx4r 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /views/settings.php: -------------------------------------------------------------------------------- 1 |
2 |
3 |
Your settings
4 |
5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 23 | 24 |
Email
Sender email
Email subject
Email text
20 | API secret
21 | for the app 22 |
25 | These settings and your password can be changed by edititing config.php. 26 |
27 |
28 |
29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Chames 2 | as in **ch**eap g**ames**, a small web application to watch the prices of your favourite games on [G2A](https://www.g2a.com) 3 | 4 | ![Screenshot](screenshot.png) 5 | 6 | ## Features 7 | - sends you notification emails when a game is available for a specific price or cheaper with informations about the seller (after sending that email the alert for this game is disabled and can be enabled again from the overview page) 8 | - an overview website for adding and deleting games 9 | 10 | ## Requirements 11 | - PHP >= 5.4.0 12 | - a webserver that supports `.htaccess` files 13 | - a way to execute a script regularly (like cron or runwhen) 14 | 15 | ## Setup 16 | 1. go to `setup.php` with your browser and copy the configuration generated into `config.php` 17 | 2. change the other options in in `config.php` according to your needs 18 | 3. create a cronjob or something similar to execute `notifications_check.php` regularly (e.g. every day) 19 | 3. Enjoy! 20 | 21 | ## Powered by 22 | g2a API, [REST Countries](https://restcountries.eu), [Bootstrap](http://getbootstrap.com) 23 | 24 | ## License 25 | 26 | Chames is licensed under the MIT License 27 | 28 | ---- 29 | This software is not in any way affiliated with g2a.com. 30 | Author: lx4r () 31 | and awesome contributors: [jburg88](https://github.com/jburg88), [solygen](https://github.com/solygen), [m-villalilla](https://github.com/m-villalilla) 32 | 33 | If you have a question regarding Chames or if you want to give me feedback about my code please don't hesitate to contact me [via Twitter](https://twitter.com/lx4r). 34 | 35 | The code of this project is 100% biodegradable and was written on happy keyboards. 36 | -------------------------------------------------------------------------------- /config.php: -------------------------------------------------------------------------------- 1 | 'noreply@example.com', 5 | 6 | /* The email adress the notification emails will come from */ 7 | 'senderEmail' => 'noreply@example.com', 8 | 9 | /* The HTML body of the notification email */ 10 | /* Available placeholders that will be replaced by the game's information: {gameName}, {gamePrice}, {gameURL}, {sellerCountry}, {sellerRating}, {sellerSells} */ 11 | 'emailText' => 12 | 'Hey,
the game {gameName} is now available on g2a.com for {gamePrice} ({lowestPrice}).
13 | The seller is from {sellerCountry} and has a rating of {sellerRating}% (based on {sellerSells} sells).
14 |
15 | The alert for this game is now disabled. Please go to the web interface of this tool to reactivate it.
16 |
17 | Your price watchdog', 18 | 19 | /* The subject of the notification emails */ 20 | /* Available placeholder that will be replaced by the game's name: {gameName} */ 21 | 'emailSubject' => '{gameName} is now available for your desired price', 22 | 23 | /* Location of the data file */ 24 | 'dataFile' => __DIR__ . '/data.php', 25 | 26 | /* ------------- 27 | Replace this section with the values from setup.php */ 28 | 29 | /* Secret saved in the session to make it unique */ 30 | 'sessionSecret' => 'hlrMkPR8wH', 31 | 32 | /* API secret used for authenticating the mobile app */ 33 | 'apiSecret' => 'hlrMkPR8wH', 34 | 35 | /* Default password: "password" */ 36 | 'rightPasswordHash' => '$2y$10$ueBR3zRV3W/H.z2hBDxwh..16NeIwTVVQOCdBHTUVnj9ahVanXCcu', 37 | 38 | // ------------- 39 | ]; -------------------------------------------------------------------------------- /_config.php: -------------------------------------------------------------------------------- 1 | 'noreply@example.com', 5 | 6 | /* The email adress the notification emails will come from */ 7 | 'senderEmail' => 'noreply@example.com', 8 | 9 | /* The HTML body of the notification email */ 10 | /* Available placeholders that will be replaced by the game's information: {gameName}, {gamePrice}, {gameURL}, {sellerCountry}, {sellerRating}, {sellerSells} */ 11 | 'emailText' => 12 | 'Hey,
the game {gameName} is now available on g2a.com for {gamePrice} ({lowestPrice}).
13 | The seller is from {sellerCountry} and has a rating of {sellerRating}% (based on {sellerSells} sells).
14 |
15 | The alert for this game is now disabled. Please go to the web interface of this tool to reactivate it.
16 |
17 | Your price watchdog', 18 | 19 | /* The subject of the notification emails */ 20 | /* Available placeholder that will be replaced by the game's name: {gameName} */ 21 | 'emailSubject' => '{gameName} is now available for your desired price', 22 | 23 | /* Location of the data file */ 24 | 'dataFile' => __DIR__ . '/data.php', 25 | 26 | /* ------------- 27 | Replace this section with the values from setup.php */ 28 | 29 | /* Secret saved in the session to make it unique */ 30 | 'sessionSecret' => 'hlrMkPR8wH', 31 | 32 | /* API secret used for authenticating the mobile app */ 33 | 'apiSecret' => 'hlrMkPR8wH', 34 | 35 | /* Default password: "password" */ 36 | 'rightPasswordHash' => '$2y$10$ueBR3zRV3W/H.z2hBDxwh..16NeIwTVVQOCdBHTUVnj9ahVanXCcu', 37 | 38 | // ------------- 39 | ]; 40 | -------------------------------------------------------------------------------- /utils/get_game_data.php: -------------------------------------------------------------------------------- 1 | 9 | array( 10 | 'method' => 'GET', 11 | 'header' => array( 12 | 'accept-encoding: *', 13 | 'accept-language: *' 14 | ) 15 | ) 16 | )); 17 | 18 | $data = json_decode(file_get_contents('https://www.g2a.com/marketplace/product/auctions/?id=' . $gameID, false, $context), true); 19 | 20 | if (isset($data['a'])) { 21 | $data = array_values(array_values($data)[0])[0]; 22 | $result = array(); 23 | $result['price'] = (float) $data['p']; 24 | $result['rating'] = $data['r']; 25 | $result['sells'] = $data['tr']; 26 | $result['country'] = GetCountry($data['c']); 27 | $result['currency'] = preg_replace("/^[0-9\., ]*/", "", $data['f']); 28 | $AUCTION_DATA[$gameID] = $result; 29 | return $result; 30 | } else { 31 | $AUCTION_DATA[$gameID] = false; 32 | return false; 33 | } 34 | } 35 | 36 | $COUNTRY_CODE_DATA = array(); 37 | function GetCountry($code){ 38 | global $COUNTRY_CODE_DATA; 39 | if (isset($COUNTRY_CODE_DATA[$code])) { 40 | return $COUNTRY_CODE_DATA[$code]; 41 | } 42 | $data = json_decode(file_get_contents('http://restcountries.eu/rest/v1/alpha/' . $code), true); 43 | $COUNTRY_CODE_DATA[$code] = $data['name']; 44 | return $data['name']; 45 | } 46 | function GetLowestPrice($gameID){ 47 | $data = GetAuctionData($gameID); 48 | return ($data !== false ? $data['price'] : false); 49 | } 50 | 51 | /* Finds a game's entity ID (needed for using the G2A API) by extracting it from the game's URL on G2A */ 52 | function GetGameEntityID($gameURL){ 53 | /* Find the game's ID in the page's URL */ 54 | preg_match("/\d{14}/", $gameURL, $entityID); 55 | if (count($entityID) >= 1){ 56 | return $entityID[0]; 57 | } else { 58 | return false; 59 | } 60 | } 61 | function IsPriceLowEnough($price, $limit){ 62 | return $price <= $limit; 63 | } 64 | ?> 65 | -------------------------------------------------------------------------------- /views/add_game.php: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
Add a game
5 |
6 |
7 |
"> 8 | 9 | " 12 | required> 13 |
14 |
"> 15 | 16 | " required> 20 |

URL of the game's page on g2a.com (including 21 | "https://")

22 |
23 |
"> 24 | 25 | " 28 | required> 29 |

When the game is available for this price or less 30 | an email will be sent to you.

31 |
32 | 33 |
34 |
35 |
36 |
37 | -------------------------------------------------------------------------------- /notifications_check.php: -------------------------------------------------------------------------------- 1 | $game){ 12 | if ($game['active']){ 13 | $auctionData = GetAuctionData($game['gameID']); 14 | 15 | if ($auctionData === false) { 16 | continue; 17 | } 18 | 19 | /* If the saved lowest price is higher than the current price replace it with the current price and then save the changes to the JSON file */ 20 | if ($game['lowestPrice'] > $auctionData['price']){ 21 | $data[$key]['lowestPrice'] = $auctionData['price']; 22 | $changed = true; 23 | } 24 | 25 | if ($auctionData['price'] <= $game['notificationLimit']){ 26 | if ($game['lowestPrice'] == $auctionData['price']){ 27 | $lowestPrice = "lowest yet"; 28 | } else { 29 | $lowest = "lowest: " . $game['lowestPrice']; 30 | } 31 | 32 | /* Replace the placeholders in the email strings with the actual values */ 33 | $placeholders = ['{gameName}', '{gamePrice}', '{gameURL}', '{sellerCountry}', '{sellerRating}', '{sellerSells}', '{lowestPrice}']; 34 | $values = [$game['gameName'], $auctionData['price'], $game['gameURL'], $auctionData['country'], $auctionData['rating'], $auctionData['sells'], $lowestPrice]; 35 | $emailText = str_replace($placeholders, $values, $config['emailText']); 36 | $emailSubject = str_replace('{gameName}', $game['gameName'], $config['emailSubject']); 37 | 38 | $emailHeader = 39 | 'From: ' . $config['senderEmail'] . "\r\n" . 40 | 'X-Mailer: PHP/' . phpversion() . "\r\n" . 41 | "Content-type: text/html; charset=UTF-8"; 42 | 43 | /* Send the email and disable the alert for this game*/ 44 | mail($config['userEmail'], $emailSubject, $emailText, $emailHeader); 45 | $data[$key]['active'] = false; 46 | 47 | $changed = true; 48 | } 49 | } 50 | } 51 | /* If an email was sent save the new status of the game's alert (now disbaled) to the data file */ 52 | if ($changed){ 53 | SaveData($config['dataFile'], $data); 54 | } 55 | } 56 | ?> 57 | -------------------------------------------------------------------------------- /setup.php: -------------------------------------------------------------------------------- 1 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | Setup Chames 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | 46 | 47 |
48 | 49 |
50 |
51 |
52 |
53 |
54 | 56 |
57 |
58 |
59 |
60 | Please replace the marked section in config.php with the following code (will set the session secret, the API secret and your password):
61 | 62 |
63 |
64 |
65 |
66 | 67 |
68 | 69 | -------------------------------------------------------------------------------- /api.php: -------------------------------------------------------------------------------- 1 | 0){ 45 | $id = 0; 46 | foreach ($data as $key => $game){ 47 | $auctionData = GetAuctionData($game['gameID']); 48 | $priceLowEnough = IsPriceLowEnough($auctionData['price'], $game['notificationLimit']); 49 | $result[] = [ 50 | 'id' => $id, 51 | 'gameName' => $game['gameName'], 52 | 'isPriceLowEnough' => $priceLowEnough 53 | ]; 54 | $id++; 55 | } 56 | } else { 57 | $result = ['error' => 'noGames']; 58 | } 59 | 60 | /* Request type: all data for one game */ 61 | } elseif ($request['type'] == 'detail' && isset($request['id'])){ 62 | 63 | $game = $data[intval($_GET['id'])]; 64 | $auctionData = GetAuctionData(intval($game['gameID'])); 65 | $priceLowEnough = IsPriceLowEnough($auctionData['price'], $game['notificationLimit']); 66 | 67 | $result = [ 68 | 'gameName' => $game['gameName'], 69 | 'gamePrice' => floatval($auctionData['price']), 70 | 'gameURL' => $game['gameURL'], 71 | 'sellerRating' => $auctionData['rating'], 72 | 'sellerSells' => $auctionData['sells'], 73 | 'sellerCountry' => $auctionData['country'], 74 | 'notificationLimit' => $game['notificationLimit'] 75 | ]; 76 | } 77 | } else { 78 | $result = ['error' => 'request']; 79 | } 80 | } 81 | } else { 82 | $result = ['error' => true]; 83 | } 84 | } 85 | header('Content-Type: application/json'); 86 | header('Cache-Control: no-cache'); 87 | header('Access-Control-Allow-Origin: *'); 88 | echo json_encode($result, JSON_UNESCAPED_UNICODE); -------------------------------------------------------------------------------- /views/game_list.php: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | $game) { 20 | $auctionData = GetAuctionData($game['gameID']); 21 | 22 | if ($auctionData === false) { 23 | ?> 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 37 | 38 | $auctionData['price']){ 44 | $data[$key]['lowestPrice'] = $auctionData['price']; 45 | $changed = true; 46 | } 47 | 48 | if ($game['lowestPrice'] == $auctionData['price']){ 49 | $lowest = "lowest yet"; 50 | } else { 51 | $lowest = "lowest: " . sprintf('%.2f %s', $game['lowestPrice'], $auctionData['currency']); 52 | } 53 | 54 | if ($auctionData['price'] <= $game['notificationLimit']) { 55 | ?> 56 | 57 | 58 | 59 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 74 | 79 | 80 | 81 | 82 |
statusgameyour limitcheapest pricecountry of sellerrating of seller
unavailable---delete 33 | 34 | reactivate 35 | 36 |
activedisabled ()% (based on sells) 72 | delete 73 | 75 | 76 | reactivate 77 | 78 |
83 |
84 |
85 | 86 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | $_POST['gameName'], 47 | 'gameID' => GetGameEntityID($_POST["gameURL"]), 48 | 'gameURL' => $_POST['gameURL'], 49 | 'notificationLimit' => floatval($_POST['notificationLimit']), 50 | 'active' => true, 51 | 'lowestPrice' => $lowestPrice 52 | ); 53 | array_push($data, $newEntry); 54 | /* and save it to the json file again */ 55 | SaveData($config['dataFile'], $data); 56 | /* empty the form fields */ 57 | $_POST['gameName'] = null; 58 | $_POST['gameURL'] = null; 59 | $_POST['notificationLimit'] = null; 60 | } 61 | // Remove a game from the array if a delete link is clicked and save the array to the json file again 62 | } elseif(isset($_GET['delete']) && $_GET['delete'] != '') { 63 | unset($data[intval($_GET['delete'])]); 64 | SaveData($config['dataFile'], $data); 65 | // Reactivate an alert in the array if a reactivate link is clicked and save the array to the json file again 66 | } elseif(isset($_GET['reactivate']) && $_GET['reactivate'] != ''){ 67 | $data[intval($_GET['reactivate'])]['active'] = true; 68 | SaveData($config['dataFile'], $data); 69 | } 70 | } 71 | 72 | ?> 73 | 74 | 75 | 76 | 77 | 78 | Chames 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 |
89 |
90 |
91 |

Chames 92 | Your humble assistant to find cheap games 93 |

94 |
95 |
96 | 112 | 113 | 114 | 115 | --------------------------------------------------------------------------------