File structure
This commit is contained in:
3
gather/.eslintrc
Normal file
3
gather/.eslintrc
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "standard"
|
||||
}
|
||||
9
gather/ecosystem.config.js
Normal file
9
gather/ecosystem.config.js
Normal file
@@ -0,0 +1,9 @@
|
||||
module.exports = {
|
||||
apps: [{
|
||||
name: 'warframe-market-bot',
|
||||
script: './src/index.js',
|
||||
watch: true,
|
||||
ignore_watch: ['node_modules', 'public'],
|
||||
restart_delay: 1 * 60 * 1000
|
||||
}]
|
||||
}
|
||||
6863
gather/package-lock.json
generated
Normal file
6863
gather/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
21
gather/package.json
Normal file
21
gather/package.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "warframe-market-bot",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "Anatoly Kopyl",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"axios": "^0.26.0",
|
||||
"dotenv": "^16.0.0",
|
||||
"handlebars": "^4.7.7",
|
||||
"limiter": "^2.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint-config-standard": "^16.0.3",
|
||||
"pm2": "^5.2.0"
|
||||
}
|
||||
}
|
||||
31
gather/src/api.js
Normal file
31
gather/src/api.js
Normal file
@@ -0,0 +1,31 @@
|
||||
const axios = require('axios')
|
||||
const { RateLimiter } = require('limiter')
|
||||
|
||||
class Api {
|
||||
constructor () {
|
||||
this.baseUrl = 'https://api.warframe.market/v1/'
|
||||
this.delay = process.env.API_DELAY ?? 3000
|
||||
this.limiter = new RateLimiter({ tokensPerInterval: 1, interval: Number(this.delay) })
|
||||
}
|
||||
|
||||
async _get (url) {
|
||||
await this.limiter.removeTokens(1)
|
||||
return axios.get(url)
|
||||
}
|
||||
|
||||
async getOrders (url) {
|
||||
const response = await this._get(this.baseUrl + 'items/' + url + '/orders')
|
||||
return response.data.payload.orders.filter(function (order) {
|
||||
return order.order_type === 'sell' && order.user.status !== 'offline'
|
||||
})
|
||||
}
|
||||
|
||||
async getSortedOrders (url) {
|
||||
const orders = await this.getOrders(url)
|
||||
return orders.sort(function (a, b) {
|
||||
return a.platinum - b.platinum
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new Api()
|
||||
21
gather/src/index.js
Normal file
21
gather/src/index.js
Normal file
@@ -0,0 +1,21 @@
|
||||
require('dotenv').config()
|
||||
const items = require('./items')
|
||||
const output = require('./output');
|
||||
|
||||
(async () => {
|
||||
for (const item of items) {
|
||||
console.log(`Looking at ${item.name}`)
|
||||
|
||||
let partsPrice = 0
|
||||
for (const part of item.parts) {
|
||||
partsPrice += await part.getPrice()
|
||||
}
|
||||
|
||||
const setPrice = await item.set.getPrice()
|
||||
if (partsPrice < setPrice) {
|
||||
output.addItem(item.name, partsPrice, setPrice)
|
||||
}
|
||||
}
|
||||
|
||||
output.submit()
|
||||
})()
|
||||
11
gather/src/items/Archwings.js
Normal file
11
gather/src/items/Archwings.js
Normal file
@@ -0,0 +1,11 @@
|
||||
const Item = require('./Item')
|
||||
|
||||
module.exports = class Archwings extends Item {
|
||||
constructor (name) {
|
||||
super(name)
|
||||
|
||||
this.addPart('harness')
|
||||
this.addPart('wings')
|
||||
this.addPart('systems')
|
||||
}
|
||||
}
|
||||
11
gather/src/items/Companion.js
Normal file
11
gather/src/items/Companion.js
Normal file
@@ -0,0 +1,11 @@
|
||||
const Item = require('./Item')
|
||||
|
||||
module.exports = class Companion extends Item {
|
||||
constructor (name) {
|
||||
super(name)
|
||||
|
||||
this.addPart('carapace')
|
||||
this.addPart('cerebrum')
|
||||
this.addPart('systems')
|
||||
}
|
||||
}
|
||||
15
gather/src/items/Item.js
Normal file
15
gather/src/items/Item.js
Normal file
@@ -0,0 +1,15 @@
|
||||
const Part = require('./Part')
|
||||
|
||||
module.exports = class Item {
|
||||
constructor (name) {
|
||||
this.name = name
|
||||
this.set = new Part(name, 'set')
|
||||
|
||||
this.parts = []
|
||||
this.addPart('blueprint')
|
||||
}
|
||||
|
||||
addPart (part, amount) {
|
||||
this.parts.push(new Part(this.name, part, amount))
|
||||
}
|
||||
}
|
||||
14
gather/src/items/Part.js
Normal file
14
gather/src/items/Part.js
Normal file
@@ -0,0 +1,14 @@
|
||||
const Api = require('../api')
|
||||
|
||||
module.exports = class Part {
|
||||
constructor (set, part, amount) {
|
||||
this.part = part
|
||||
this.url = `${set}_prime_${part}`
|
||||
this.amount = amount ?? 1
|
||||
}
|
||||
|
||||
async getPrice () {
|
||||
const orders = await Api.getSortedOrders(this.url)
|
||||
return Number(orders[0].platinum) * this.amount
|
||||
}
|
||||
}
|
||||
11
gather/src/items/Primary.js
Normal file
11
gather/src/items/Primary.js
Normal file
@@ -0,0 +1,11 @@
|
||||
const Item = require('./Item')
|
||||
|
||||
module.exports = class Primary extends Item {
|
||||
constructor (name) {
|
||||
super(name)
|
||||
|
||||
this.addPart('barrel')
|
||||
this.addPart('receiver')
|
||||
this.addPart('stock')
|
||||
}
|
||||
}
|
||||
11
gather/src/items/Secondary.js
Normal file
11
gather/src/items/Secondary.js
Normal file
@@ -0,0 +1,11 @@
|
||||
const Item = require('./Item')
|
||||
|
||||
module.exports = class Secondary extends Item {
|
||||
constructor (name) {
|
||||
super(name)
|
||||
|
||||
this.addPart('barrel', 2)
|
||||
this.addPart('receiver', 2)
|
||||
this.addPart('link')
|
||||
}
|
||||
}
|
||||
11
gather/src/items/Warframe.js
Normal file
11
gather/src/items/Warframe.js
Normal file
@@ -0,0 +1,11 @@
|
||||
const Item = require('./Item')
|
||||
|
||||
module.exports = class Warframe extends Item {
|
||||
constructor (name) {
|
||||
super(name)
|
||||
|
||||
this.addPart('systems')
|
||||
this.addPart('neuroptics')
|
||||
this.addPart('chassis')
|
||||
}
|
||||
}
|
||||
26
gather/src/items/index.js
Normal file
26
gather/src/items/index.js
Normal file
@@ -0,0 +1,26 @@
|
||||
const items = require('./items.json')
|
||||
const Warframe = require('./Warframe')
|
||||
// const Primary = require('./Primary')
|
||||
// const Secondary = require('./Secondary')
|
||||
const Companion = require('./Companion')
|
||||
const Archwings = require('./Archwings')
|
||||
|
||||
const result = []
|
||||
|
||||
items.warframes.forEach((name) => {
|
||||
result.push(new Warframe(name))
|
||||
})
|
||||
|
||||
// items.primaries.forEach((name) => {
|
||||
// result.push(new Primary(name))
|
||||
// })
|
||||
|
||||
items.companions.forEach((name) => {
|
||||
result.push(new Companion(name))
|
||||
})
|
||||
|
||||
items.archwings.forEach((name) => {
|
||||
result.push(new Archwings(name))
|
||||
})
|
||||
|
||||
module.exports = result
|
||||
22
gather/src/items/items.json
Normal file
22
gather/src/items/items.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"warframes": [
|
||||
"ash", "atlas", "banshee", "chroma", "ember",
|
||||
"equinox", "frost", "gara", "harrow", "hydroid",
|
||||
"inaros", "ivara", "limbo", "loki", "mag",
|
||||
"mesa", "mirage", "nekros", "nezha", "nidus",
|
||||
"nova", "nyx", "oberon", "octavia", "rhino",
|
||||
"saryn", "titania", "trinity", "valkyr", "vauban",
|
||||
"volt", "wukong", "zephyr"
|
||||
],
|
||||
"primaries": [
|
||||
"astilla", "baza", "boar", "boltor", "braton",
|
||||
"burston", "corinth", "larton", "panthera",
|
||||
"rubico", "scourge", "soma", "stradavar",
|
||||
"strun", "sybaris", "tenora", "tiberon", "tigris",
|
||||
"vectis", "zhuge"
|
||||
],
|
||||
"companions": [
|
||||
"carrier", "dethcube", "helios", "wyrm"
|
||||
],
|
||||
"archwings": ["odonata"]
|
||||
}
|
||||
10
gather/src/output/ListingSet.js
Normal file
10
gather/src/output/ListingSet.js
Normal file
@@ -0,0 +1,10 @@
|
||||
module.exports = class ListingSet {
|
||||
constructor (name, parts, set) {
|
||||
this.name = name + ' prime'
|
||||
this.parts = parts
|
||||
this.set = set
|
||||
this.link = `https://warframe.market/items/${name}_prime_set`
|
||||
|
||||
this.difference = set - parts
|
||||
}
|
||||
}
|
||||
9
gather/src/output/helpers.js
Normal file
9
gather/src/output/helpers.js
Normal file
@@ -0,0 +1,9 @@
|
||||
function ifEq (a, b, opts) {
|
||||
if (a === b) {
|
||||
return opts.fn(this)
|
||||
} else {
|
||||
return opts.inverse(this)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { ifEq }
|
||||
35
gather/src/output/i18n.json
Normal file
35
gather/src/output/i18n.json
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"en": {
|
||||
"link": "",
|
||||
"title": "Market Gaps",
|
||||
"description": "Find a profitable difference between the price of the set and the price of the sum of it's parts.",
|
||||
"filter_by_difference": "Filter by difference:",
|
||||
"name": "Name",
|
||||
"parts_price": "Parts price",
|
||||
"set_price": "Set price",
|
||||
"difference": "Difference",
|
||||
"generated_at": "Generated at"
|
||||
},
|
||||
"ru": {
|
||||
"link": "ru",
|
||||
"title": "Market Gaps",
|
||||
"description": "Находите выгоду между покупкой сета по отдельности и продажей его в собранном виде.",
|
||||
"filter_by_difference": "Фильтровать по разнице:",
|
||||
"name": "Название",
|
||||
"parts_price": "Стоимость частей",
|
||||
"set_price": "Стоимость сета",
|
||||
"difference": "Разница",
|
||||
"generated_at": "Сгенерировано в"
|
||||
},
|
||||
"ua": {
|
||||
"link": "ua",
|
||||
"title": "Market Gaps",
|
||||
"description": "Знаходьте вигоду між покупкою сета окремо та продажем його в зібраному вигляді.",
|
||||
"filter_by_difference": "Фільтрувати за різницею:",
|
||||
"name": "Назва",
|
||||
"parts_price": "Вартість частин",
|
||||
"set_price": "Вартість сета",
|
||||
"difference": "Різниця",
|
||||
"generated_at": "Згенеровано в"
|
||||
}
|
||||
}
|
||||
48
gather/src/output/index.js
Normal file
48
gather/src/output/index.js
Normal file
@@ -0,0 +1,48 @@
|
||||
const fs = require('fs')
|
||||
const Handlebars = require('handlebars')
|
||||
const { ifEq } = require('./helpers')
|
||||
const ListingSet = require('./ListingSet')
|
||||
const i18n = require('./i18n.json')
|
||||
|
||||
Handlebars.registerHelper('if_eq', ifEq)
|
||||
|
||||
class Output {
|
||||
constructor () {
|
||||
this.items = []
|
||||
this.timestamp = null
|
||||
}
|
||||
|
||||
addItem (...p) {
|
||||
this.items.push(new ListingSet(...p))
|
||||
}
|
||||
|
||||
async _compileTemplate () {
|
||||
const templateFile = await fs.readFileSync('./src/output/template.hbs', 'utf8')
|
||||
return Handlebars.compile(templateFile)
|
||||
}
|
||||
|
||||
async _writeToFile (filename, content) {
|
||||
try {
|
||||
await fs.unlinkSync(filename)
|
||||
} catch {
|
||||
console.log('File probably doesnt exist')
|
||||
}
|
||||
fs.writeFileSync(filename, content, 'utf8')
|
||||
}
|
||||
|
||||
async submit () {
|
||||
this.timestamp = new Date()
|
||||
const template = await this._compileTemplate()
|
||||
|
||||
Object.keys(i18n).forEach(locale => {
|
||||
const filename = i18n[locale].link || 'index'
|
||||
this._writeToFile(`./public/${filename}.html`, template({
|
||||
...this,
|
||||
t: i18n[locale],
|
||||
languages: i18n
|
||||
}))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new Output()
|
||||
102
gather/src/output/template.hbs
Normal file
102
gather/src/output/template.hbs
Normal file
@@ -0,0 +1,102 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="Find profits in the Warframe market">
|
||||
<meta name="keywords" content="warframe,market,buy,sell,trade,mod,blueprint,syndicate,relic,order,auction,riven,wts,wtb,sales,worth,platinum,price,checking">
|
||||
<link rel="canonical" href="https://warframe.center/">
|
||||
<link rel="alternate" hreflang="en" href="https://warframe.center/">
|
||||
<link rel="alternate" hreflang="ru" href="https://warframe.center/ru/">
|
||||
<link rel="alternate" hreflang="uk" href="https://warframe.center/ua/">
|
||||
<link rel="alternate" hreflang="x-default" href="https://warframe.center/">
|
||||
<meta property="og:title" content="Warframe Center">
|
||||
<meta property="og:description" content="Find profits in the Warframe market">
|
||||
<meta property="og:url" content="https://warframe.center">
|
||||
<meta property="og:image" content="https://warframe.center/assets/warframe_logo.png">
|
||||
<meta property="og:site_name" content="Warframe Center">
|
||||
<meta property="og:locale" content="en">
|
||||
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon">
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<title>Warframe Center</title>
|
||||
|
||||
<!-- Yandex.Metrika counter -->
|
||||
<script type="text/javascript" >
|
||||
(function(m,e,t,r,i,k,a){m[i]=m[i]||function(){(m[i].a=m[i].a||[]).push(arguments)};
|
||||
m[i].l=1*new Date();k=e.createElement(t),a=e.getElementsByTagName(t)[0],k.async=1,k.src=r,a.parentNode.insertBefore(k,a)})
|
||||
(window, document, "script", "https://mc.yandex.ru/metrika/tag.js", "ym");
|
||||
|
||||
ym(87671663, "init", {
|
||||
clickmap:true,
|
||||
trackLinks:true,
|
||||
accurateTrackBounce:true
|
||||
});
|
||||
</script>
|
||||
<noscript><div><img src="https://mc.yandex.ru/watch/87671663" style="position:absolute; left:-9999px;" alt="" /></div></noscript>
|
||||
<!-- /Yandex.Metrika counter -->
|
||||
</head>
|
||||
<body>
|
||||
<div class="hero">
|
||||
<img id="gunner1" src="./assets/gunner1.png">
|
||||
<div class="main">
|
||||
<img class="logo" src="./assets/warframe_logo.png">
|
||||
<div class="text">
|
||||
<h1>{{t.title}}</h1>
|
||||
<p>{{t.description}}</p>
|
||||
</div>
|
||||
</div>
|
||||
<img id="gunner2" src="./assets/gunner2.png">
|
||||
|
||||
<div class="languages">
|
||||
{{#each languages}}
|
||||
<a
|
||||
href="/{{this.link}}"
|
||||
class="{{#if_eq ../t.link this.link}}selected{{/if_eq}}"
|
||||
>
|
||||
<img src="./assets/languages/{{@key}}.png">
|
||||
</a>
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="main-column">
|
||||
<div class="controls">
|
||||
<label for="min-difference">{{t.filter_by_difference}}</label>
|
||||
<input type="range" min="1" max="60" value="1" id="min-difference">
|
||||
<span id="filter-value">1</span>
|
||||
</div>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{t.name}}</th>
|
||||
<th>{{t.parts_price}}</th>
|
||||
<th>{{t.set_price}}</th>
|
||||
<th>{{t.difference}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="items">
|
||||
{{#each items}}
|
||||
<tr data-difference="{{this.difference}}">
|
||||
<td class="name">
|
||||
<a href="{{this.link}}" target="_blank">
|
||||
{{this.name}}
|
||||
</a>
|
||||
</td>
|
||||
<td>{{this.parts}}</td>
|
||||
<td>{{this.set}}</td>
|
||||
<td>{{this.difference}}</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="timestamp">
|
||||
{{t.generated_at}} {{timestamp}}
|
||||
</div>
|
||||
|
||||
<script src="index.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user