├── .github └── dependabot.yml ├── .gitignore ├── .ncurc.js ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── localserver ├── .babelrc ├── .env ├── .gitignore ├── README.md ├── Routes │ ├── cards.js │ ├── home.js │ └── index.js ├── Views │ ├── card_error.pug │ ├── cards.pug │ ├── error.pug │ ├── home.pug │ ├── layout.production.pug │ ├── layout.pug │ └── layout │ │ ├── footer.pug │ │ └── nav.pug ├── app.js ├── favicon.ico ├── libs │ ├── boot.js │ ├── errorCatching.js │ └── middleware.js ├── package-lock.json ├── package.json ├── src │ ├── entry-js.js │ ├── images │ │ ├── patternbg.png │ │ └── wallpaper.jpg │ ├── js │ │ └── scripts │ │ │ └── app.js │ └── sass │ │ ├── includes │ │ ├── components │ │ │ ├── _demo.scss │ │ │ ├── _error.scss │ │ │ ├── _footer.scss │ │ │ └── _navbar.scss │ │ └── variables │ │ │ ├── _colors.scss │ │ │ └── _screens.scss │ │ ├── main.scss │ │ └── mixins │ │ ├── _box-shadow.scss │ │ ├── _gradient.scss │ │ ├── _make-square.scss │ │ ├── _position-absolute-center.scss │ │ ├── _rotate.scss │ │ ├── _themes.scss │ │ ├── _transition.scss │ │ └── _user-select.scss ├── webpack.config.js └── webpack.dev.config.js ├── package-lock.json ├── package.json └── src ├── config.js ├── index.js ├── querybuilder.js ├── test ├── test-card.js ├── test-set.js ├── test-subtype.js ├── test-supertype.js └── test-type.js └── unqueryable.js /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: 'npm' 9 | directory: '/' 10 | schedule: 11 | interval: 'monthly' 12 | open-pull-requests-limit: 0 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.ncurc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | /** ESM only */ 3 | reject: ['chai'] 4 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "10" 4 | - "8" 5 | script: npm run build -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thanks for your interest in contributing to mtg-sdk-javascript! 4 | 5 | Any contributions are greatly appreciated and all skill levels welcome. The project uses ES6 (Javascript with some new syntax for things like functions and assignment, for example) and Promises (a type of object that represents a future value, a helpful abstraction for asynchronous programming). 6 | 7 | An example workflow for contributing looks like the following: 8 | 9 | 1. Fork and clone the repo 10 | 1. Create a branch for the new feature 11 | 1. Run `npm run watch` to automatically build on edit 12 | 1. Write a unit test for the new feature (as seen in [test-card.js](https://github.com/MagicTheGathering/mtg-sdk-javascript/blob/master/test-src/test-card.js)) 13 | 1. Run `npm test` to make sure the test fails (since you haven't implemented the feature yet) 14 | 1. Edit the source code until the new unit test passes 15 | 1. Submit a pull request to have your feature reviewed 16 | 1. Make any any formatting or functional revisions asked for 17 | 1. Once your pull request is accepted, your changes will be merged into the master branch and published to npm! 18 | 19 | # Roadmap 20 | You can jump in on any of the features yet to be implemented (unchecked). Follow the approach Just create an issue to let me know you are working on something, and stay in communication about how it's going. 21 | 22 | - [x] `card.find` - find a card by code 23 | - [x] `card.where` - filter cards 24 | - [x] `card.all` - get all cards 25 | - [ ] `set.find` - find a set by code 26 | - [ ] `set.where` - filter sets 27 | - [ ] `set.all` - get all sets 28 | - [ ] `types`- get all types 29 | - [ ] `subtypes` - get all subtypes 30 | - [ ] `supertypes` - get all supertypes 31 | 32 | # Contact 33 | 34 | You can contact Raine at raineorshine@gmail.com if you have any questions about how to contribute. I'm happy to support you! 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Magic: The Gathering SDK 2 | 3 | [![npm version](https://img.shields.io/npm/v/mtgsdk)](https://www.npmjs.com/package/mtgsdk) 4 | [![discord](https://img.shields.io/badge/discord-mtg%20developers-738bd7.svg)](https://discord.gg/qwGJNnP) 5 | 6 | This is the Magic: The Gathering SDK Javascript implementation. It is a wrapper around the MTG API of [magicthegathering.io](http://magicthegathering.io/). 7 | 8 | ## Installation 9 | 10 | ``` 11 | npm install mtgsdk 12 | ``` 13 | 14 | ## Usage 15 | ```js 16 | const mtg = require('mtgsdk') 17 | 18 | const result = await mtg.card.find(3) 19 | console.log(result.card.name) // "Black Lotus" 20 | 21 | const result = await mtg.set.find('AER') 22 | console.log(result.set.name) // "Aether Revolt" 23 | ``` 24 | 25 | Query cards and sets by any property: 26 | 27 | ```js 28 | const result = await card.where({ supertypes: 'legendary', subtypes: 'goblin' }) 29 | console.log(cards[0].name) // "Squee, Goblin Nabob" 30 | 31 | const result = await set.where({ block: 'Shadows over Innistrad', border: 'black' }) 32 | console.log(sets[0].name) // "Welcome Deck 2016" 33 | console.log(sets[1].name) // "Shadows over Innistrad" 34 | console.log(sets[2].name) // "Eldritch Moon" 35 | ``` 36 | 37 | Retrieve cards across multiple pages of results: 38 | 39 | ```js 40 | card.all({ name: 'Squee', pageSize: 1 }) 41 | .on('data', card => { 42 | console.log(card.name) 43 | }) 44 | 45 | // Squee 46 | // Squee, Goblin Nabob Avatar 47 | // Squee's Toy 48 | // Squeeze 49 | // Squee, Goblin Nabob 50 | // Squee's Embrace 51 | // Squee's Revenge 52 | // Squee, Goblin Nabob 53 | // Squee, Goblin Nabob 54 | // (the duplicates are from different sets) 55 | 56 | set.all({ name: 'limited' }) 57 | .on('data', set => { 58 | console.log(set.name) 59 | }) 60 | 61 | // Limited Edition Alpha 62 | // Limited Edition Beta 63 | // Unlimited Edition 64 | ``` 65 | 66 | ### Properties 67 | 68 | #### Card 69 | 70 | name 71 | multiverseid 72 | layout 73 | names 74 | manaCost 75 | cmc 76 | colors 77 | type 78 | types 79 | subtypes 80 | rarity 81 | text 82 | flavor 83 | artist 84 | number 85 | power 86 | toughness 87 | reserved 88 | rulings 89 | printings 90 | originalText 91 | originalType 92 | legalities 93 | source 94 | imageUrl 95 | set 96 | id 97 | 98 | #### Set 99 | 100 | code 101 | name 102 | gatherer_code 103 | old_code 104 | magic_cards_info_code 105 | release_date 106 | border 107 | type 108 | block 109 | online_only 110 | booster 111 | mkm_id 112 | mkm_name 113 | 114 | **Please note:** Currently, sets are only queryable on their _name_ and _block_ fields. 115 | -------------------------------------------------------------------------------- /localserver/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "env" 4 | ], 5 | "plugins": [ 6 | "react-html-attrs" 7 | ] 8 | } -------------------------------------------------------------------------------- /localserver/.env: -------------------------------------------------------------------------------- 1 | PORT=8080 2 | ENV=development 3 | -------------------------------------------------------------------------------- /localserver/.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.sln.docstates 8 | # Build results 9 | .vscode/ 10 | [Dd]ebug/ 11 | [Dd]ebugPublic/ 12 | [Rr]elease/ 13 | x64/ 14 | build/ 15 | bld/ 16 | [Bb]in/ 17 | [Oo]bj/ 18 | photos/users/ 19 | node_modules/ 20 | public/ 21 | 22 | # Roslyn cache directories 23 | *.ide/ 24 | 25 | # MSTest test Results 26 | [Tt]est[Rr]esult*/ 27 | [Bb]uild[Ll]og.* 28 | 29 | #NUNIT 30 | *.VisualState.xml 31 | TestResult.xml 32 | 33 | # Build Results of an ATL Project 34 | [Dd]ebugPS/ 35 | [Rr]eleasePS/ 36 | dlldata.c 37 | 38 | *_i.c 39 | *_p.c 40 | *_i.h 41 | *.ilk 42 | *.meta 43 | *.obj 44 | *.pch 45 | *.pdb 46 | *.pgc 47 | *.pgd 48 | *.rsp 49 | *.sbr 50 | *.tlb 51 | *.tli 52 | *.tlh 53 | *.tmp 54 | *.tmp_proj 55 | *.log 56 | *.vspscc 57 | *.vssscc 58 | .builds 59 | *.pidb 60 | *.svclog 61 | *.scc 62 | 63 | # Chutzpah Test files 64 | _Chutzpah* 65 | 66 | # Visual C++ cache files 67 | ipch/ 68 | *.aps 69 | *.ncb 70 | *.opensdf 71 | *.sdf 72 | *.cachefile 73 | 74 | # Visual Studio profiler 75 | *.psess 76 | *.vsp 77 | *.vspx 78 | 79 | # TFS 2012 Local Workspace 80 | $tf/ 81 | 82 | # Guidance Automation Toolkit 83 | *.gpState 84 | 85 | # ReSharper is a .NET coding add-in 86 | _ReSharper*/ 87 | *.[Rr]e[Ss]harper 88 | *.DotSettings.user 89 | 90 | # JustCode is a .NET coding addin-in 91 | .JustCode 92 | 93 | # TeamCity is a build add-in 94 | _TeamCity* 95 | 96 | # DotCover is a Code Coverage Tool 97 | *.dotCover 98 | 99 | # NCrunch 100 | _NCrunch_* 101 | .*crunch*.local.xml 102 | 103 | # MightyMoose 104 | *.mm.* 105 | AutoTest.Net/ 106 | 107 | # Web workbench (sass) 108 | .sass-cache/ 109 | 110 | # Installshield output folder 111 | [Ee]xpress/ 112 | 113 | # DocProject is a documentation generator add-in 114 | DocProject/buildhelp/ 115 | DocProject/Help/*.HxT 116 | DocProject/Help/*.HxC 117 | DocProject/Help/*.hhc 118 | DocProject/Help/*.hhk 119 | DocProject/Help/*.hhp 120 | DocProject/Help/Html2 121 | DocProject/Help/html 122 | 123 | # Click-Once directory 124 | publish/ 125 | 126 | # Publish Web Output 127 | *.[Pp]ublish.xml 128 | *.azurePubxml 129 | ## TODO: Comment the next line if you want to checkin your 130 | ## web deploy settings but do note that will include unencrypted 131 | ## passwords 132 | #*.pubxml 133 | 134 | # NuGet Packages Directory 135 | packages/* 136 | ## TODO: If the tool you use requires repositories.config 137 | ## uncomment the next line 138 | #!packages/repositories.config 139 | 140 | # Enable "build/" folder in the NuGet Packages folder since 141 | # NuGet packages use it for MSBuild targets. 142 | # This line needs to be after the ignore of the build folder 143 | # (and the packages folder if the line above has been uncommented) 144 | !packages/build/ 145 | 146 | # Windows Azure Build Output 147 | csx/ 148 | *.build.csdef 149 | 150 | # Windows Store app package directory 151 | AppPackages/ 152 | 153 | # Others 154 | sql/ 155 | *.Cache 156 | ClientBin/ 157 | [Ss]tyle[Cc]op.* 158 | ~$* 159 | *~ 160 | *.dbmdl 161 | *.dbproj.schemaview 162 | *.pfx 163 | *.publishsettings 164 | node_modules/ 165 | 166 | # RIA/Silverlight projects 167 | Generated_Code/ 168 | 169 | # Backup & report files from converting an old project file 170 | # to a newer Visual Studio version. Backup files are not needed, 171 | # because we have git ;-) 172 | _UpgradeReport_Files/ 173 | Backup*/ 174 | UpgradeLog*.XML 175 | UpgradeLog*.htm 176 | 177 | # SQL Server files 178 | *.mdf 179 | *.ldf 180 | 181 | # Business Intelligence projects 182 | *.rdl.data 183 | *.bim.layout 184 | *.bim_*.settings 185 | 186 | # Microsoft Fakes 187 | FakesAssemblies/ 188 | 189 | # LightSwitch generated files 190 | GeneratedArtifacts/ 191 | _Pvt_Extensions/ 192 | ModelManifest.xml -------------------------------------------------------------------------------- /localserver/README.md: -------------------------------------------------------------------------------- 1 | # MTG Local Server 2 | 3 | ## Stack Server-Side 4 | 5 | ### Server 6 | * This application will host an API built in NodeJS using ExpressJS. 7 | 8 | 9 | ### View Engine 10 | * The server uses a view engine known as PUG. 11 | 12 | ## Stack Client-side 13 | The Front end is built using React JS. 14 | 15 | All API calls will transfer JSON for communication. 16 | * All React code is bundled with Webpack 17 | * All Styles are bundled with Webpack 18 | * All client-side code is located in ./src 19 | 20 | ## ENV file 21 | * __ENV__=**development** 22 | * Webpack dev server is enabled 23 | * Webpack hot reload is enabled 24 | * Webpack is compiled automatically 25 | * CORS is disabled. 26 | * __/Views/home.pug__ needs to point to the __layout.pug__ file. 27 | * __ENV__=**production** 28 | * Webpack dev server is disabled 29 | * Webpack hot reload is disabled 30 | * Expects build to be ran 31 | * CORS is enabled. 32 | * All bundled files get rendered to the /public directory. 33 | * __/Views/home.pug__ needs to point to the __layout.production.pug__ file. 34 | 35 | 36 | # ENV 37 | 38 | You will need to create a .env file in the root. 39 | In the file, you will need to add two variables. 40 | 41 | PORT=8080 42 | 43 | ENV=development 44 | 45 | 46 | # Requirements 47 | * NodeJS > v6 48 | * expressjs v4.x 49 | * webpack v3.10.0 50 | -------------------------------------------------------------------------------- /localserver/Routes/cards.js: -------------------------------------------------------------------------------- 1 | import mtg from 'mtgsdk' 2 | module.exports = app => { 3 | app.route('/card') 4 | .get((req, res) => { 5 | const id = req.query.id || 3 6 | mtg.card.find(id) 7 | .then(data => { 8 | let card = data.card 9 | const keys = Object.keys(card) 10 | for (let key of keys) { 11 | if (card[key] === undefined) { 12 | delete card[key] 13 | } 14 | } 15 | 16 | res.render('cards', { card }) 17 | }) 18 | .catch(() => { 19 | res.render('card_error') 20 | }) 21 | }) 22 | app.route('/card/:id') 23 | .get((req, res) => { 24 | const id = req.params.id 25 | mtg.card.find(id) 26 | .then(data => { 27 | let card = data.card 28 | const keys = Object.keys(card) 29 | for (let key of keys) { 30 | if (card[key] === undefined) { 31 | delete card[key] 32 | } 33 | } 34 | res.render('cards', { card }) 35 | }) 36 | .catch(() => { 37 | res.render('card_error') 38 | }) 39 | }) 40 | } 41 | -------------------------------------------------------------------------------- /localserver/Routes/home.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = app => { 3 | app.route('/home') 4 | .get((req, res) => { 5 | const data = { 6 | ViewBag: { 7 | title: 'Home' 8 | } 9 | } 10 | res.render('home', data, (err, html) => { 11 | if (err) { 12 | console.log('ERROR') 13 | console.log(err) 14 | res.status(500).json(err) 15 | } else { 16 | res.status(200).send(html) 17 | } 18 | }) 19 | }) 20 | } 21 | -------------------------------------------------------------------------------- /localserver/Routes/index.js: -------------------------------------------------------------------------------- 1 | module.exports = app => { 2 | app.route('/') 3 | .get((req, res) => { 4 | const data = { 5 | ViewBag: { 6 | title: 'Home' 7 | } 8 | } 9 | res.render('home', data, (err, html) => { 10 | if (err) { 11 | console.log('ERROR') 12 | console.log(err) 13 | res.status(500).json(err) 14 | } else { 15 | res.status(200).send(html) 16 | } 17 | }) 18 | }) 19 | } 20 | -------------------------------------------------------------------------------- /localserver/Views/card_error.pug: -------------------------------------------------------------------------------- 1 | extends layout.pug 2 | block body_content 3 | div.ErrorContainer 4 | div.__message 5 | h1 Error: Card id does not exist 6 | form(action="/card" method="get") 7 | .form-group 8 | label(for='id' style="color:white;") Enter Multiverse Id 9 | input.form-control(type='number', name='id', value='2') 10 | button.btn.btn-primary(type='submit') Search 11 | 12 | -------------------------------------------------------------------------------- /localserver/Views/cards.pug: -------------------------------------------------------------------------------- 1 | extends layout.pug 2 | 3 | block body_content 4 | .container.cards_container 5 | - let keys = Object.keys(card) 6 | .col-xs-12 7 | h1=`${card['name']}` 8 | .row 9 | .col-xs-12.col-md-3 10 | img(src=`${card['imageUrl']}`) 11 | .col-xs-12.col-md-8 12 | each val in keys 13 | if val !== "imageUrl" && val !== "name" && val !== "rulings" 14 | if Array.isArray(card[val]) 15 | if val === "legalities" 16 | .row 17 | .col-xs-12.col-sm-2 18 | p 19 | strong= val 20 | .col-xs-12.col-sm-10 21 | table.table 22 | thead 23 | tr 24 | th= "Format" 25 | th= "Legality" 26 | tbody 27 | each v in card[val] 28 | tr 29 | td= v.format 30 | td= v.legality 31 | else 32 | .row 33 | .col-xs-12.col-sm-2 34 | p 35 | strong= val 36 | .col-xs-12.col-sm-10 37 | ul 38 | each v in card[val] 39 | li=v 40 | else 41 | .row 42 | .col-xs-12.col-sm-2 43 | p 44 | strong= val 45 | .col-xs-12.col-sm-10 46 | p= card[val] 47 | if card["rulings"] !== undefined 48 | .col-12 49 | h1= "Rulings" 50 | .col-12 51 | table.table 52 | thead 53 | tr 54 | th(style="width: 150px;")= "Date" 55 | th= "Rule" 56 | tbody 57 | each v in card["rulings"] 58 | tr 59 | td= v.date 60 | td= v.text -------------------------------------------------------------------------------- /localserver/Views/error.pug: -------------------------------------------------------------------------------- 1 | extends layout.pug 2 | block body_content 3 | div.ErrorContainer 4 | div.__message 5 | h1 404 - NOT FOUND 6 | div.__btn 7 | a.btn.btn-info(href='/') Home 8 | 9 | -------------------------------------------------------------------------------- /localserver/Views/home.pug: -------------------------------------------------------------------------------- 1 | extends layout.pug 2 | 3 | block body_content 4 | .DemoComponent 5 | form(action="/card" method="get") 6 | .form-group 7 | label(for='id' style="color:white;") Enter Multiverse Id 8 | input.form-control(type='number', name='id', value='2') 9 | button.btn.btn-primary(type='submit') Search 10 | 11 | -------------------------------------------------------------------------------- /localserver/Views/layout.production.pug: -------------------------------------------------------------------------------- 1 | doctype html 2 | html(lang="en") 3 | head 4 | meta(charset="utf-8") 5 | meta(name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no") 6 | title= "MTG Local Server" 7 | link(href='https://fonts.googleapis.com/css?family=Arimo' rel='stylesheet') 8 | link(rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css" integrity="sha384-WskhaSGFgHYWDcbwN70/dfYBj47jz9qbsMId/iRN3ewGhXQFZCSftd1LZCfmhktB" crossorigin="anonymous") 9 | link(rel="stylesheet" href="https://use.fontawesome.com/releases/v5.0.13/css/all.css" integrity="sha384-DNOHZ68U8hZfKXOrtjWvjxusGo9WQnrNx2sqG0tfsghAvtVlRW3tvkXWZh58N9jp" crossorigin="anonymous") 10 | link(rel="stylesheet" href="/public/css/bundle.css") 11 | meta(name="viewport" content="width=device-width, initial-scale=1") 12 | body 13 | include layout/nav.pug 14 | block body_content 15 | include layout/footer.pug 16 | script(src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous") 17 | script(src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous") 18 | script(src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/js/bootstrap.min.js" integrity="sha384-smHYKdLADwkXOn1EmN1qk/HfnUcbVRZyYmZ4qpPea6sjB/pTJ0euyQp0Mk8ck+5T" crossorigin="anonymous") 19 | script(type="text/javascript" src='./public/bundle.js') -------------------------------------------------------------------------------- /localserver/Views/layout.pug: -------------------------------------------------------------------------------- 1 | doctype html 2 | html(lang="en") 3 | head 4 | meta(charset="utf-8") 5 | meta(name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no") 6 | title= "MTG Local Server" 7 | link(href='https://fonts.googleapis.com/css?family=Arimo' rel='stylesheet') 8 | link(rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous") 9 | link(rel="stylesheet" href="https://use.fontawesome.com/releases/v5.0.13/css/all.css" integrity="sha384-DNOHZ68U8hZfKXOrtjWvjxusGo9WQnrNx2sqG0tfsghAvtVlRW3tvkXWZh58N9jp" crossorigin="anonymous") 10 | meta(name="viewport" content="width=device-width, initial-scale=1") 11 | body 12 | include layout/nav.pug 13 | block body_content 14 | include layout/footer.pug 15 | script(src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous") 16 | script(src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous") 17 | script(src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous") 18 | script(type="text/javascript" src='/bundle.js') -------------------------------------------------------------------------------- /localserver/Views/layout/footer.pug: -------------------------------------------------------------------------------- 1 | .FooterContainer 2 | .row 3 | .col-6.__color.__color--teal 4 | .col-6.__color.__color--orange 5 | 6 | -------------------------------------------------------------------------------- /localserver/Views/layout/nav.pug: -------------------------------------------------------------------------------- 1 | .NavContainer 2 | nav.navbar.navbar-expand-lg.navbar-dark.bg-dark 3 | .container 4 | button.navbar-toggler(type='button', data-toggle='collapse', data-target='#navbarMain', aria-controls='navbarMain', aria-expanded='false', aria-label='Toggle navigation') 5 | span.navbar-toggler-icon 6 | #navbarMain.collapse.navbar-collapse 7 | ul.navbar-nav.mr-auto 8 | li.nav-item.active 9 | a.nav-link(href='/') 10 | span Home 11 | span.sr-only 12 | form.form-inline(action="/card" method="get") 13 | input.form-control.mr-sm-2(name="id" type='text', placeholder='Enter Multiverse Id') 14 | button.btn.btn-outline-success.my-2.my-sm-0(type='submit') View -------------------------------------------------------------------------------- /localserver/app.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import consign from 'consign' 3 | 'use strict' 4 | require('./node_modules/dotenv/lib/main').config() 5 | 6 | const app = express() 7 | var port = process.env.PORT || '8081' 8 | var environment = process.env.ENV || 'development' 9 | app.set('port', port) 10 | app.set('environment', environment) 11 | 12 | consign() 13 | .then('libs/middleware.js') 14 | .then('Routes') 15 | .then('libs/errorCatching.js') 16 | .then('libs/boot.js') 17 | .into(app) 18 | 19 | module.exports = app 20 | -------------------------------------------------------------------------------- /localserver/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MagicTheGathering/mtg-sdk-javascript/db6c4e1d0338d4ca59b215aebbd4a7637d00da51/localserver/favicon.ico -------------------------------------------------------------------------------- /localserver/libs/boot.js: -------------------------------------------------------------------------------- 1 | import opn from 'opn' 2 | module.exports = app => { 3 | const port = app.get('port') 4 | app.listen(port, () => { 5 | console.log(`Welcome to the app - Port ${port}`) 6 | opn(`http://localhost:${port}/`) 7 | }) 8 | } 9 | -------------------------------------------------------------------------------- /localserver/libs/errorCatching.js: -------------------------------------------------------------------------------- 1 | module.exports = app => { 2 | // error report view 3 | app.use(function (req, res) { 4 | res.status(404) 5 | // respond with html page 6 | if (req.accepts('html')) { 7 | res.render('error') 8 | return 9 | } 10 | // respond with json 11 | if (req.accepts('json')) { 12 | res.send({ error: 'Not found' }) 13 | return 14 | } 15 | // default to plain-text. send() 16 | res.type('txt').send('Not found') 17 | }) 18 | } 19 | -------------------------------------------------------------------------------- /localserver/libs/middleware.js: -------------------------------------------------------------------------------- 1 | import serveIndex from 'serve-index' 2 | import express from 'express' 3 | import logger from 'morgan' 4 | import path from 'path' 5 | import favicon from 'serve-favicon' 6 | import bodyParser from 'body-parser' 7 | import webpack from 'webpack' 8 | import webpackDevMiddleware from 'webpack-dev-middleware' 9 | import webpackHotMiddleware from 'webpack-hot-middleware' 10 | import helmet from 'helmet' 11 | 12 | module.exports = app => { 13 | const _env = app.get('environment') 14 | 15 | // view engine setup 16 | app.set('views', path.join(__dirname, '../Views')) 17 | app.set('view engine', 'pug') 18 | app.use(favicon(path.join(__dirname, '../favicon.ico'))) 19 | app.set('json spaces', 4) 20 | app.use(logger('dev')) 21 | app.use(bodyParser.json()) 22 | app.use(bodyParser.urlencoded({ extended: false })) 23 | if (_env === 'production') { 24 | // Access-Control 25 | app.use(helmet()) 26 | // Static Files 27 | app.use('/public', express.static(path.join(__dirname, '../public'))) 28 | } else { 29 | // Hot Reload 30 | const config = require('../webpack.dev.config') 31 | const compiler = webpack(config) 32 | const dev_middleware = webpackDevMiddleware(compiler, { 33 | hot: true, 34 | filename: config.output.filename, 35 | publicPath: config.output.publicPath, 36 | stats: { 37 | colors: true 38 | }, 39 | noInfo: true 40 | }) 41 | const hot_middleware = webpackHotMiddleware(compiler, { 42 | log: console.log, 43 | heartbeat: 10 * 1000 44 | }) 45 | app.use(dev_middleware) 46 | app.use(hot_middleware) 47 | app.use(function (req, res, next) { 48 | // Website you wish to allow to connect 49 | res.setHeader('Access-Control-Allow-Origin', __dirname) 50 | // Request methods you wish to allow 51 | res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE') 52 | // Request headers you wish to allow 53 | res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type') 54 | // Set to true if you need the website to include cookies in the requests sent 55 | // to the API (e.g. in case you use sessions) 56 | res.setHeader('Access-Control-Allow-Credentials', true) 57 | next() 58 | }) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /localserver/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mtg_local_derver", 3 | "version": "0.1.0", 4 | "private": false, 5 | "scripts": { 6 | "start": "./node_modules/.bin/babel-node ./app.js", 7 | "build": "./node_modules/.bin/webpack -p", 8 | "start_d": "./node_modules/.bin/babel-node --inspect ./app.js" 9 | }, 10 | "babel": { 11 | "presets": [ 12 | "env", 13 | "react" 14 | ], 15 | "plugins": [] 16 | }, 17 | "devDependencies": { 18 | "autoprefixer": "^10.4.12", 19 | "babel-cli": "^6.24.1", 20 | "babel-core": "^6.26.3", 21 | "babel-loader": "^7.1.2", 22 | "babel-plugin-add-module-exports": "^0.2.1", 23 | "babel-plugin-react-html-attrs": "^2.1.0", 24 | "babel-plugin-transform-class-properties": "^6.24.1", 25 | "babel-plugin-transform-decorators-legacy": "^1.3.4", 26 | "babel-preset-env": "^1.7.0", 27 | "babel-preset-es2015": "^6.24.1", 28 | "babel-preset-react": "^6.24.1", 29 | "babel-preset-stage-0": "^6.24.1", 30 | "body-parser": "^1.18.3", 31 | "bootstrap-loader": "^2.2.0", 32 | "bootstrap-sass": "^3.3.7", 33 | "clean-webpack-plugin": "^0.1.17", 34 | "consign": "^0.1.6", 35 | "cookie-parser": "~1.3.5", 36 | "copy-webpack-plugin": "^4.5.2", 37 | "css-loader": ">=0.28.7", 38 | "debug": "~2.2.0", 39 | "delete": "^0.3.0", 40 | "dotenv": "^4.0.0", 41 | "exports-loader": ">=0.6.4", 42 | "express": "^4.16.3", 43 | "extract-text-webpack-plugin": "^3.0.2", 44 | "file-loader": "^1.1.5", 45 | "helmet": "^3.4.0", 46 | "html-webpack-plugin": "^2.30.1", 47 | "imports-loader": "^0.7.1", 48 | "jest": "^22.4.3", 49 | "jsdom": "^16.5.0", 50 | "json-loader": "^0.5.7", 51 | "jsonwebtoken": "^7.4.0", 52 | "jspdf": "^1.3.5", 53 | "jsts": "~1.4.0", 54 | "less": "^2.6.1", 55 | "less-loader": "^2.2.3", 56 | "morgan": "^1.9.0", 57 | "multer": "^1.3.0", 58 | "node-sass": "^4.9.2", 59 | "node-sass-middleware": "0.8.0", 60 | "opn": "^5.1.0", 61 | "password-hash-and-salt": "^0.1.4", 62 | "pg": "^6.4.2", 63 | "pg-hstore": "^2.3.2", 64 | "postcss-loader": "^7.0.1", 65 | "pug": "2.0.0-beta6", 66 | "react-hot-loader": "^3.1.3", 67 | "request": "^2.87.0", 68 | "request-promise": "^4.2.1", 69 | "request-promise-native": "^1.0.4", 70 | "resolve-url-loader": "^2.2.1", 71 | "sass-loader": "^6.0.6", 72 | "serve-favicon": "^2.5.0", 73 | "serve-index": "^1.8.0", 74 | "stats-webpack-plugin": "^0.6.1", 75 | "style-loader": "^0.19.1", 76 | "url-loader": "^0.6.2", 77 | "uuid": "^3.0.1", 78 | "webpack": "^3.10.0", 79 | "webpack-dev-middleware": "^2.0.1", 80 | "webpack-dev-server": "^4.11.1", 81 | "webpack-hot-middleware": "^2.25.2" 82 | }, 83 | "dependencies": { 84 | "font-awesome": "^4.7.0", 85 | "mtgsdk": "^0.3.0" 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /localserver/src/entry-js.js: -------------------------------------------------------------------------------- 1 | /******************** 2 | * CSS Libraries * 3 | ********************/ 4 | /****************** 5 | * CSS Custom * 6 | ******************/ 7 | require('./sass/main.scss') 8 | require('./images/patternbg.png') 9 | require('./images/wallpaper.jpg') 10 | 11 | /******************** 12 | * JS Libraries * 13 | ********************/ 14 | 15 | require('./js/scripts/app.js') 16 | 17 | if (module.hot) { 18 | module.hot.accept() 19 | } 20 | -------------------------------------------------------------------------------- /localserver/src/images/patternbg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MagicTheGathering/mtg-sdk-javascript/db6c4e1d0338d4ca59b215aebbd4a7637d00da51/localserver/src/images/patternbg.png -------------------------------------------------------------------------------- /localserver/src/images/wallpaper.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MagicTheGathering/mtg-sdk-javascript/db6c4e1d0338d4ca59b215aebbd4a7637d00da51/localserver/src/images/wallpaper.jpg -------------------------------------------------------------------------------- /localserver/src/js/scripts/app.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MagicTheGathering/mtg-sdk-javascript/db6c4e1d0338d4ca59b215aebbd4a7637d00da51/localserver/src/js/scripts/app.js -------------------------------------------------------------------------------- /localserver/src/sass/includes/components/_demo.scss: -------------------------------------------------------------------------------- 1 | .DemoComponent{ 2 | height: 88.9vh; 3 | display: flex; 4 | align-items: center; 5 | justify-content: center; 6 | flex-direction: column; 7 | 8 | #map{ 9 | width: 100%; 10 | height:100%; 11 | } 12 | } 13 | 14 | -------------------------------------------------------------------------------- /localserver/src/sass/includes/components/_error.scss: -------------------------------------------------------------------------------- 1 | .ErrorContainer { 2 | height: 88.9vh; 3 | display: flex; 4 | align-items: center; 5 | justify-content: center; 6 | flex-direction: column; 7 | .__message { 8 | h1 { 9 | text-transform: uppercase; 10 | font-size: 6rem; 11 | color: $red; 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /localserver/src/sass/includes/components/_footer.scss: -------------------------------------------------------------------------------- 1 | 2 | .FooterContainer { 3 | .__color{ 4 | min-height:44px; 5 | &--orange{ 6 | background-color: $orange; 7 | } 8 | &--teal{ 9 | background-color: $teal; 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /localserver/src/sass/includes/components/_navbar.scss: -------------------------------------------------------------------------------- 1 | $sm: 576px; 2 | $md: 768px; 3 | $lg: 992px; 4 | $el: 1200px; 5 | $black: #222; 6 | $orange:#E28C36; 7 | .NavContainer { 8 | border-bottom: 5px solid #005858; 9 | .bg-dark { 10 | background-image: url('http://www.ingham.org/Portals/_default/Skins/ingham/images/patternbg.png') 11 | } 12 | .nav-link { 13 | color: white !important; 14 | text-transform: uppercase; 15 | font-weight: bolder; 16 | &:hover { 17 | color: $orange !important; 18 | } 19 | } 20 | .navbar-brand{ 21 | width: 70%; 22 | img{ 23 | width: 100%; 24 | height:auto; 25 | max-width: 410px; 26 | } 27 | } 28 | .dropdown-toggle::before { 29 | display: inline-block; 30 | width: 0; 31 | height: 0; 32 | margin-right: 0.355em; 33 | vertical-align: 0.255em; 34 | content: ""; 35 | border-top: .3em solid; 36 | border-right: .3em solid transparent; 37 | border-bottom: 0; 38 | border-left: .3em solid transparent; 39 | color:$orange; 40 | } 41 | .dropdown-toggle::after { 42 | content: ""; 43 | border-top: none; 44 | border-right: none; 45 | border-bottom: none; 46 | border-left: none; 47 | } 48 | .dropdown-menu{ 49 | background-color:$black; 50 | background-image: url('http://www.ingham.org/Portals/_default/Skins/ingham/images/patternbg.png'); 51 | .dropdown-item{ 52 | color:white; 53 | padding-top:10px; 54 | padding-bottom:10px; 55 | &:hover{ 56 | color:$orange; 57 | background-color:$black; 58 | } 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /localserver/src/sass/includes/variables/_colors.scss: -------------------------------------------------------------------------------- 1 | $yellow: #ffe053; 2 | $red: #d43839; 3 | $dark: #2c2d31; 4 | $dark-lite: #424242; 5 | $off-white: #f5f5f2; 6 | $light-blue: #009cda; 7 | $gray-blue: #515959; 8 | $light-gray: #d5d6d7; 9 | $black: #222; 10 | $orange:#E28C36; 11 | $teal: #267b7e; -------------------------------------------------------------------------------- /localserver/src/sass/includes/variables/_screens.scss: -------------------------------------------------------------------------------- 1 | $screen-lg-min: 1200px; 2 | $screen-md-max: 1150px; 3 | $screen-md-min: 992px; 4 | $screen-sm-max: 991px; 5 | $screen-sm-min: 768px; 6 | $screen-xs-max: 767px; 7 | $sm: 576px; 8 | $md: 768px; 9 | $lg: 992px; 10 | $el: 1200px; -------------------------------------------------------------------------------- /localserver/src/sass/main.scss: -------------------------------------------------------------------------------- 1 | @import 'includes/variables/_screens.scss'; 2 | @import 'includes/variables/_colors.scss'; 3 | html { 4 | font-size: 16px; 5 | font-size: 100%; 6 | height: 100%; 7 | body { 8 | overflow-x: hidden; 9 | overflow-y: visible; 10 | height: 100%; 11 | background-repeat: no-repeat; 12 | background-attachment: fixed !important; 13 | background-position: center center; 14 | background-size: cover; 15 | background-image: url("https://wallpaper.wiki/wp-content/uploads/2017/05/wallpaper.wiki-Free-HD-Desktop-Mtg-Pictures-Download-PIC-WPE008081.jpg"); 16 | @import 'includes/components/_navbar.scss'; 17 | @import 'includes/components/_demo.scss'; 18 | @import 'includes/components/_footer.scss'; 19 | @import 'includes/components/_error.scss'; 20 | 21 | 22 | .cards_container{ 23 | background-color: rgba(255,255,255,0.5); 24 | padding:10px; 25 | color:white; 26 | text-shadow: 0 0 5px #333; 27 | table{ 28 | background-color: white; 29 | color: black; 30 | text-shadow: 0 0 0 #333; 31 | } 32 | h1{ 33 | color: black; 34 | text-shadow: 0 0 0 #333; 35 | border-bottom: 1px solid white; 36 | margin-bottom: 2rem; 37 | padding-bottom: 1rem; 38 | } 39 | strong{ 40 | color: black; 41 | text-shadow: 0 0 0 #333; 42 | text-transform: capitalize; 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /localserver/src/sass/mixins/_box-shadow.scss: -------------------------------------------------------------------------------- 1 | @mixin box-shadow($type, $distance, $color) { 2 | -moz-box-shadow: $type 0 0 $distance $color; 3 | -webkit-box-shadow: $type 0 0 $distance $color; 4 | box-shadow: $type 0 0 $distance $color; 5 | } -------------------------------------------------------------------------------- /localserver/src/sass/mixins/_gradient.scss: -------------------------------------------------------------------------------- 1 | @mixin blueGradient() { 2 | /* Permalink - use to edit and share this gradient: http://colorzilla.com/gradient-editor/#e1ffff+0,c8eefb+34,bee4f8+75,b1d8f5+100&1+0,0.55+100 */ 3 | background: -moz-radial-gradient(center, ellipse cover, rgba(225,255,255,1) 0%, rgba(200,238,251,0.85) 34%, rgba(190,228,248,0.66) 75%, rgba(177,216,245,0.55) 100%); /* FF3.6-15 */ 4 | background: -webkit-radial-gradient(center, ellipse cover, rgba(225,255,255,1) 0%,rgba(200,238,251,0.85) 34%,rgba(190,228,248,0.66) 75%,rgba(177,216,245,0.55) 100%); /* Chrome10-25,Safari5.1-6 */ 5 | background: radial-gradient(ellipse at center, rgba(225,255,255,1) 0%,rgba(200,238,251,0.85) 34%,rgba(190,228,248,0.66) 75%,rgba(177,216,245,0.55) 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */ 6 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#e1ffff', endColorstr='#8cb1d8f5',GradientType=1 ); /* IE6-9 fallback on horizontal gradient */ 7 | } -------------------------------------------------------------------------------- /localserver/src/sass/mixins/_make-square.scss: -------------------------------------------------------------------------------- 1 | @mixin make-square($widthHeight) { 2 | width: $widthHeight; 3 | height: $widthHeight; 4 | } 5 | 6 | @mixin width-height($w, $h) { 7 | width: $w; 8 | height: $h; 9 | } -------------------------------------------------------------------------------- /localserver/src/sass/mixins/_position-absolute-center.scss: -------------------------------------------------------------------------------- 1 | @mixin pos-ab-cntr() { 2 | position: absolute; 3 | top: 0; 4 | bottom: 0; 5 | left: 0; 6 | right: 0; 7 | margin: auto; 8 | } -------------------------------------------------------------------------------- /localserver/src/sass/mixins/_rotate.scss: -------------------------------------------------------------------------------- 1 | @mixin rotate($deg) { 2 | -ms-transform: rotate($deg); /* IE 9 */ 3 | -webkit-transform: rotate($deg); /* Chrome, Safari, Opera */ 4 | transform: rotate($deg); 5 | } -------------------------------------------------------------------------------- /localserver/src/sass/mixins/_themes.scss: -------------------------------------------------------------------------------- 1 | @mixin background_theme($gender) { 2 | @if($gender=='boy') { 3 | /* Permalink - use to edit and share this gradient: http://colorzilla.com/gradient-editor/#ffffff+0,96d4ea+32,86ccb3+75,6dafff+100 */ 4 | /* Old browsers */ 5 | background: -moz-linear-gradient(45deg, #ffffff 0%, #96d4ea 32%, #86ccb3 75%, #6dafff 100%); 6 | /* FF3.6-15 */ 7 | background: -webkit-linear-gradient(45deg, #ffffff 0%, #96d4ea 32%, #86ccb3 75%, #6dafff 100%); 8 | /* Chrome10-25,Safari5.1-6 */ 9 | background: linear-gradient(45deg, #ffffff 0%, #96d4ea 32%, #86ccb3 75%, #6dafff 100%); 10 | /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */ 11 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#6dafff', GradientType=1); 12 | /* IE6-9 fallback on horizontal gradient */ 13 | } 14 | @else if($gender=='girl') { 15 | /* Permalink - use to edit and share this gradient: http://colorzilla.com/gradient-editor/#ffffff+0,e5b3b5+33,db90d8+76,ff6d70+100 */ 16 | /* Old browsers */ 17 | background: -moz-linear-gradient(45deg, #ffffff 0%, #e5b3b5 33%, #db90d8 76%, #ff6d70 100%); 18 | /* FF3.6-15 */ 19 | background: -webkit-linear-gradient(45deg, #ffffff 0%, #e5b3b5 33%, #db90d8 76%, #ff6d70 100%); 20 | /* Chrome10-25,Safari5.1-6 */ 21 | background: linear-gradient(45deg, #ffffff 0%, #e5b3b5 33%, #db90d8 76%, #ff6d70 100%); 22 | /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */ 23 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#ff6d70', GradientType=1); 24 | /* IE6-9 fallback on horizontal gradient */ 25 | } 26 | @else { 27 | /* Permalink - use to edit and share this gradient: http://colorzilla.com/gradient-editor/#ffffff+0,e5b3b5+33,8188c4+77,6d81ff+100 */ 28 | background: -moz-linear-gradient(45deg, #ffffff 0%, #e5b3b5 33%, #8188c4 77%, #6d81ff 100%); 29 | /* FF3.6-15 */ 30 | background: -webkit-linear-gradient(45deg, #ffffff 0%, #e5b3b5 33%, #8188c4 77%, #6d81ff 100%); 31 | /* Chrome10-25,Safari5.1-6 */ 32 | background: linear-gradient(45deg, #ffffff 0%, #e5b3b5 33%, #8188c4 77%, #6d81ff 100%); 33 | /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */ 34 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#6d81ff', GradientType=1); 35 | /* IE6-9 fallback on horizontal gradient */ 36 | } 37 | } -------------------------------------------------------------------------------- /localserver/src/sass/mixins/_transition.scss: -------------------------------------------------------------------------------- 1 | @mixin transition($ele, $len, $ease) { 2 | -moz-transition: $ele $len $ease; 3 | -o-transition: $ele $len $ease; 4 | -webkit-transition: $ele $len $ease; 5 | transition: $ele $len $ease; 6 | } -------------------------------------------------------------------------------- /localserver/src/sass/mixins/_user-select.scss: -------------------------------------------------------------------------------- 1 | @mixin user-select($val) { 2 | -moz-user-select: $val; 3 | -ms-user-select: $val; 4 | -webkit-user-select: $val; 5 | user-select: $val; 6 | } -------------------------------------------------------------------------------- /localserver/webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack') 2 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 3 | const path = require('path') 4 | module.exports = { 5 | entry: ['babel-polyfill', './src/entry-js.js'], 6 | devtool: 'source-map', 7 | output: { 8 | path: path.join(__dirname, '/public'), 9 | publicPath: '/public', 10 | filename: 'bundle.js' 11 | }, 12 | module: { 13 | rules: [ 14 | { 15 | test: /\.(jsx|js)$/, 16 | include: [ 17 | path.join(__dirname, 'src'), 18 | path.join(__dirname, 'node_modules/') 19 | ], 20 | loader: 'babel-loader', 21 | options: { 22 | presets: ['react', 'env', 'stage-0'], 23 | plugins: ['react-html-attrs', 'transform-class-properties', 'transform-decorators-legacy'] 24 | } 25 | }, 26 | { 27 | test: /\.json$/, 28 | loader: 'json-loader', 29 | exclude: [ 30 | path.join(__dirname, 'node_modules/') 31 | ] 32 | }, 33 | { 34 | test: /\.css$/, 35 | use: ExtractTextPlugin.extract({ 36 | fallback: 'style-loader', 37 | use: 'css-loader' 38 | }) 39 | }, 40 | { 41 | test: /\.(scss|sass)$/, 42 | use: ExtractTextPlugin.extract({ 43 | fallback: 'style-loader', 44 | use: [ 45 | 'css-loader', 46 | 'sass-loader' 47 | ] 48 | }) 49 | }, 50 | { 51 | test: /\.less$/, 52 | use: ExtractTextPlugin.extract({ 53 | fallback: 'style-loader', 54 | use: [ 55 | 'css-loader', 56 | 'less-loader' 57 | ] 58 | }) 59 | }, 60 | { 61 | test: /\.(png|svg|jpg|gif)$/, 62 | use: [{ 63 | loader: 'file-loader', 64 | options: { 65 | limit: 500, 66 | name: './img/[name].[ext]' 67 | } 68 | }] 69 | }, 70 | { 71 | test: /\.(woff|woff2|eot|ttf|otf)$/, 72 | use: [{ 73 | loader: 'file-loader', 74 | options: { 75 | limit: 500, 76 | name: './fonts/[name].[ext]' 77 | } 78 | }] 79 | } 80 | ] 81 | }, 82 | resolve: { 83 | modules: [ 84 | path.join(__dirname, 'src'), 85 | 'bower_components', 86 | 'node_modules' 87 | ], 88 | extensions: [ 89 | '.js', 90 | '.jsx', 91 | '.style' 92 | ] 93 | }, 94 | plugins: [ 95 | new ExtractTextPlugin({ 96 | filename: './css/bundle.css', 97 | disable: false, 98 | allChunks: true 99 | }), 100 | new webpack.ProvidePlugin({ 101 | $: 'jquery', 102 | jQuery: 'jquery', 103 | 'window.jQuery': 'jquery' 104 | }) 105 | ] 106 | } 107 | -------------------------------------------------------------------------------- /localserver/webpack.dev.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const CleanWebpackPlugin = require('clean-webpack-plugin') 3 | const webpack = require('webpack') 4 | module.exports = { 5 | entry: [ 6 | 'webpack-hot-middleware/client?path=/__webpack_hmr&timeout=20000', 7 | 'babel-polyfill', 8 | './src/entry-js.js' 9 | ], 10 | output: { 11 | publicPath: '/', 12 | filename: 'bundle.js', 13 | path: __dirname 14 | }, 15 | devtool: 'eval-source-map', 16 | devServer: { 17 | contentBase: './', 18 | hot: true, 19 | historyApiFallBack: true 20 | }, 21 | module: { 22 | rules: [ 23 | { 24 | test: /\.(jsx|js)$/, 25 | include: [ 26 | path.join(__dirname, 'src'), 27 | path.join(__dirname, 'node_modules/') 28 | ], 29 | loader: 'babel-loader', 30 | options: { 31 | presets: ['react', 'env', 'stage-0'], 32 | plugins: ['react-html-attrs', 'transform-class-properties', 'transform-decorators-legacy'] 33 | } 34 | }, 35 | { 36 | test: /\.css$/, 37 | use: [{ loader: 'style-loader' }, 38 | { 39 | loader: 'css-loader', 40 | options: { 41 | modules: true, 42 | sourceMap: true 43 | } 44 | } 45 | ] 46 | }, 47 | { 48 | test: /\.json$/, 49 | loader: 'json-loader', 50 | exclude: [ 51 | path.join(__dirname, 'node_modules/') 52 | ] 53 | }, 54 | { 55 | test: /\.(scss|sass)$/, 56 | use: [ 57 | { loader: 'style-loader' }, 58 | { 59 | loader: 'css-loader', 60 | options: { 61 | sourceMap: true 62 | } 63 | }, 64 | { 65 | loader: 'sass-loader', 66 | options: { 67 | sourceMap: true 68 | } 69 | } 70 | ] 71 | }, 72 | { 73 | test: /\.less$/, 74 | use: [{ 75 | loader: 'style-loader' // creates style nodes from JS strings 76 | }, { 77 | loader: 'css-loader' // translates CSS into CommonJS 78 | }, { 79 | loader: 'less-loader' // compiles Less to CSS 80 | }] 81 | }, 82 | { 83 | test: /\.(png|svg|jpg|gif)$/, 84 | use: [{ 85 | loader: 'file-loader', 86 | options: { 87 | limit: 10000 88 | } 89 | }] 90 | }, 91 | { 92 | test: /\.(woff|woff2|eot|ttf|otf)$/, 93 | use: [{ 94 | loader: 'file-loader', 95 | options: { 96 | limit: 500 97 | } 98 | }] 99 | } 100 | ] 101 | }, 102 | resolve: { 103 | modules: [ 104 | 'bower_components', 105 | 'node_modules' 106 | ], 107 | extensions: [ 108 | '*', 109 | '.js', 110 | '.jsx', 111 | '.style' 112 | ] 113 | }, 114 | plugins: [ 115 | new CleanWebpackPlugin(['public']), 116 | new webpack.NamedModulesPlugin(), 117 | new webpack.HotModuleReplacementPlugin() 118 | ] 119 | } 120 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mtgsdk", 3 | "version": "1.0.1", 4 | "description": "Magic: The Gathering SDK is a javascript wrapper around the MTG API located at magicthegathering.io", 5 | "author": { 6 | "name": "Raine Revere", 7 | "email": "raineorshine@gmail.com", 8 | "url": "https://github.com/metaraine" 9 | }, 10 | "main": "src/index.js", 11 | "engines": { 12 | "node": ">=12" 13 | }, 14 | "license": "MIT", 15 | "scripts": { 16 | "lint": "standard \"*src/**/*.js\"", 17 | "test": "mocha -t 20000 src/test", 18 | "test:watch": "nodemon -w src -x npm run test" 19 | }, 20 | "devDependencies": { 21 | "chai": "^4.3.4", 22 | "chai-as-promised": "^7.1.1", 23 | "mocha": "^10.2.0", 24 | "nodemon": "^3.0.2", 25 | "standard": "^17.1.0" 26 | }, 27 | "dependencies": { 28 | "emitter20": "^2.0.0", 29 | "ramda": "^0.29.1", 30 | "request": "^2.81.0", 31 | "request-promise": "^4.2.1" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | endpoint: 'https://api.magicthegathering.io/v1' 3 | } 4 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const qb = require('./querybuilder.js') 2 | const unq = require('./unqueryable') 3 | module.exports = { 4 | card: qb('cards'), 5 | set: qb('sets'), 6 | type: unq('types'), 7 | supertype: unq('supertypes'), 8 | subtype: unq('subtypes') 9 | } 10 | -------------------------------------------------------------------------------- /src/querybuilder.js: -------------------------------------------------------------------------------- 1 | const request = require('request-promise') 2 | const config = require('./config.js') 3 | const { curryN, mergeLeft, prop } = require('ramda') 4 | const Emitter = require('emitter20') 5 | 6 | const get = curryN(3, (type, page, args = {}) => { 7 | return request({ 8 | uri: `${config.endpoint}/${type}`, 9 | qs: mergeLeft(args, { 10 | page: args.page || page 11 | }), 12 | json: true 13 | }).then(prop(type)) 14 | }) 15 | 16 | module.exports = type => ({ 17 | 18 | /** Gets a resource by its id. */ 19 | find: id => request({ 20 | uri: `${config.endpoint}/${type}/${id}`, 21 | json: true 22 | }), 23 | 24 | /** Gets a resource with a given query. */ 25 | where: get(type, 0), 26 | 27 | /** Gets a resource with a given query (like where), but 28 | returns an emitter that emits 3 events: 29 | - data(card): emits a card when it is retrieved from the API 30 | - error(err): emits an error if the request fails 31 | - end(): called when all results have been retrieved 32 | */ 33 | all: args => { 34 | const emitter = new Emitter() 35 | const getEmit = (type, page, args) => { 36 | return get(type, page, args) 37 | .then(items => { 38 | if (items.length > 0) { 39 | items.forEach(c => emitter.trigger('data', c)) 40 | return getEmit(type, page + 1, args) // RECURSION 41 | } else { 42 | emitter.trigger('end') 43 | return null 44 | } 45 | }) 46 | .catch(err => emitter.trigger('error', err)) 47 | } 48 | getEmit(type, 1, args) 49 | 50 | return emitter 51 | } 52 | 53 | }) 54 | -------------------------------------------------------------------------------- /src/test/test-card.js: -------------------------------------------------------------------------------- 1 | /* global describe, it */ 2 | const chai = require('chai') 3 | chai.should() 4 | 5 | const { card } = require('../index.js') 6 | 7 | describe('card', () => { 8 | describe('find', () => { 9 | it('returns card', async () => { 10 | const result = await card.find(88803) 11 | result.card.should.have.property('id') 12 | result.card.should.have.property('name', 'Choice of Damnations') 13 | result.card.should.have.property('manaCost', '{5}{B}') 14 | result.card.should.have.property('cmc', 6) 15 | result.card.should.have.property('type', 'Sorcery — Arcane') 16 | result.card.should.have.nested.property('colors[0]', 'B') 17 | result.card.should.have.nested.property('types[0]', 'Sorcery') 18 | result.card.should.have.nested.property('subtypes[0]', 'Arcane') 19 | result.card.should.have.property('rarity', 'Rare') 20 | result.card.should.have.property('set', 'SOK') 21 | result.card.should.have.property('text', 'Target opponent chooses a number. You may have that player lose that much life. If you don\'t, that player sacrifices all but that many permanents.') 22 | result.card.should.have.property('flavor', '"Life is a series of choices between bad and worse."\n—Toshiro Umezawa') 23 | result.card.should.have.property('artist', 'Tim Hildebrandt') 24 | result.card.should.have.property('number', '62') 25 | result.card.should.have.property('multiverseid', '88803') 26 | result.card.should.have.property('imageUrl', 'http://gatherer.wizards.com/Handlers/Image.ashx?multiverseid=88803&type=card') 27 | result.card.should.have.property('originalText', 'Target opponent chooses a number. You may have that player lose that much life. If you don\'t, that player sacrifices all but that many permanents.') 28 | result.card.should.have.property('originalType', 'Sorcery - Arcane') 29 | }) 30 | }) 31 | 32 | describe('where', () => { 33 | it('should filter', async () => { 34 | const result = await card.where({ supertypes: 'legendary', subtypes: 'goblin' }) 35 | result 36 | .should.be.an('array') 37 | .with.nested.property('[0]') 38 | .that.has.nested.property('supertypes[0]', 'Legendary') 39 | }) 40 | 41 | it('should return 1 page of results', async () => { 42 | const result1 = await card.where({}) 43 | result1 44 | .should.be.an('array') 45 | .that.has.length(100) 46 | 47 | const result2 = await card.where({ page: 1 }) 48 | result2 49 | .should.be.an('array') 50 | .that.has.length(100) 51 | }) 52 | 53 | it('should be able to control the page size', async () => { 54 | const result = await card.where({ page: 1, pageSize: 1 }) 55 | result 56 | .should.be.an('array') 57 | .that.has.length(1) 58 | }) 59 | }) 60 | 61 | describe('all', () => { 62 | it('should emit results from multiple pages', (cb) => { 63 | const results = [] 64 | const cardEmitter = card.all({ name: 'Squee', pageSize: 5 }) 65 | cardEmitter.on('data', card => results.push(card)) 66 | cardEmitter.on('error', cb) 67 | cardEmitter.on('end', () => { 68 | results.should.have.length.of.at.least(10) // length at time of test 69 | cb() 70 | }) 71 | }) 72 | }) 73 | }) 74 | -------------------------------------------------------------------------------- /src/test/test-set.js: -------------------------------------------------------------------------------- 1 | /* global describe, it */ 2 | const chai = require('chai') 3 | const assert = chai.assert 4 | 5 | const { set } = require('../index.js') 6 | const { pipe, prop } = require('ramda') 7 | 8 | describe('set', () => { 9 | describe('find', () => { 10 | it('returns set', () => { 11 | return set.find('SOI').then(pipe(prop('set'), set => { 12 | assert.equal(set.code, 'SOI') 13 | assert.equal(set.name, 'Shadows over Innistrad') 14 | assert.equal(set.type, 'expansion') 15 | assert.equal(set.releaseDate, '2016-04-08') 16 | assert.equal(set.block, 'Shadows over Innistrad') 17 | })) 18 | }) 19 | }) 20 | 21 | describe('where', () => { 22 | it('should filter', () => { 23 | return set.where({ name: 'Shadows over Innistrad' }).then(result => { 24 | assert.deepEqual(result[0].name, 'Shadows over Innistrad Promos') 25 | }) 26 | }) 27 | }) 28 | 29 | describe('all', () => { 30 | it('should emit all results', (cb) => { 31 | const results = [] 32 | const setEmitter = set.all({ name: 'urza' }) 33 | setEmitter.on('data', set => results.push(set)) 34 | setEmitter.on('error', cb) 35 | setEmitter.on('end', () => { 36 | assert.equal(results.length, 6) 37 | cb() 38 | }) 39 | }) 40 | }) 41 | }) 42 | -------------------------------------------------------------------------------- /src/test/test-subtype.js: -------------------------------------------------------------------------------- 1 | /* global describe, it */ 2 | 3 | const chai = require('chai') 4 | const chaiAsPromised = require('chai-as-promised') 5 | chai.should() 6 | chai.use(chaiAsPromised) 7 | 8 | const { subtype } = require('../index.js') 9 | 10 | describe('subtype', () => { 11 | describe('all', () => { 12 | it('should emit all the subtypes', (cb) => { 13 | const results = [] 14 | 15 | const subtypeEmitter = subtype.all() 16 | subtypeEmitter.on('data', subtype => results.push(subtype)) 17 | subtypeEmitter.on('error', cb) 18 | subtypeEmitter.on('end', () => { 19 | results[0].should.equal('Abian') 20 | results.should.have.length.of.at.least(377) // length at time of test 21 | cb() 22 | }) 23 | }) 24 | }) 25 | }) 26 | -------------------------------------------------------------------------------- /src/test/test-supertype.js: -------------------------------------------------------------------------------- 1 | /* global describe, it */ 2 | 3 | const chai = require('chai') 4 | const chaiAsPromised = require('chai-as-promised') 5 | chai.should() 6 | chai.use(chaiAsPromised) 7 | 8 | const { supertype } = require('../index.js') 9 | 10 | describe('supertype', () => { 11 | describe('all', () => { 12 | it('should emit all the supertypes', (cb) => { 13 | const results = [] 14 | 15 | const supertypeEmitter = supertype.all() 16 | supertypeEmitter.on('data', supertype => results.push(supertype)) 17 | supertypeEmitter.on('error', cb) 18 | supertypeEmitter.on('end', () => { 19 | results.should.have.length.of.at.least(5) // length at time of test 20 | cb() 21 | }) 22 | }) 23 | }) 24 | }) 25 | -------------------------------------------------------------------------------- /src/test/test-type.js: -------------------------------------------------------------------------------- 1 | /* global describe, it */ 2 | 3 | const chai = require('chai') 4 | const chaiAsPromised = require('chai-as-promised') 5 | chai.should() 6 | chai.use(chaiAsPromised) 7 | 8 | const { type } = require('../index.js') 9 | 10 | describe('type', () => { 11 | describe('all', () => { 12 | it('should emit all the types', (cb) => { 13 | const results = [] 14 | 15 | const typeEmitter = type.all() 16 | typeEmitter.on('data', type => results.push(type)) 17 | typeEmitter.on('error', cb) 18 | typeEmitter.on('end', () => { 19 | results.should.have.length.of.at.least(14) // length at time of test 20 | cb() 21 | }) 22 | }) 23 | }) 24 | }) 25 | -------------------------------------------------------------------------------- /src/unqueryable.js: -------------------------------------------------------------------------------- 1 | 2 | const qb = require('./querybuilder.js') 3 | 4 | module.exports = type => ({ 5 | 6 | /** Gets a set of resources of the given type, but 7 | returns an emitter that emits 3 events: 8 | - data(resource): emits a resource when it is retrieved from the API 9 | - error(err): emits an error if the request fails 10 | - end(): called when all results have been retrieved 11 | */ 12 | all: () => { 13 | return qb(type).all() 14 | } 15 | 16 | }) 17 | --------------------------------------------------------------------------------