71 Commits

Author SHA1 Message Date
9a50b2c6cf Dockerize gather 2024-02-04 19:09:33 +03:00
db9180721a Updated drone.yml
All checks were successful
continuous-integration/drone/push Build is passing
2022-07-07 02:36:58 +03:00
1af1048670 Fix crash
All checks were successful
continuous-integration/drone/push Build is passing
2022-07-06 19:43:44 +03:00
148a3693b9 Merge branch 'monorepo' of ssh://git.radner.ru:3036/anatolykopyl/warframe-center into monorepo
All checks were successful
continuous-integration/drone/push Build is passing
2022-07-05 14:50:16 +03:00
efb1cba216 Remove metrika id from env 2022-07-05 14:50:12 +03:00
755cb06276 Remove metrika id from env
Some checks failed
continuous-integration/drone/push Build was killed
2022-07-05 14:49:12 +03:00
a40390a30e Add ym id to drone secrets
All checks were successful
continuous-integration/drone/push Build is passing
2022-07-05 14:10:50 +03:00
9d5d49c9f2 Fix undefined scan results
All checks were successful
continuous-integration/drone/push Build is passing
2022-07-05 13:31:08 +03:00
fe1cf2f171 Merge branch 'monorepo' of ssh://git.radner.ru:3036/anatolykopyl/warframe-center into monorepo
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone Build was killed
2022-07-05 12:03:29 +03:00
d3cb737972 Fix ya metrika hopefully 2022-07-05 12:03:08 +03:00
7a0480fa69 Fix ya metrika hopefully
Some checks failed
continuous-integration/drone/push Build is failing
2022-07-05 11:58:01 +03:00
a77fefc251 Switch ya metrika packages
Some checks failed
continuous-integration/drone/push Build is failing
2022-07-05 09:50:16 +03:00
ad10f35e19 Disable ya metrika
All checks were successful
continuous-integration/drone/push Build is passing
2022-07-05 02:34:43 +03:00
0df5022493 Yandex metrica work
All checks were successful
continuous-integration/drone/push Build is passing
2022-07-05 02:19:27 +03:00
ccf1aaba9b Fix empty orders in getSortedOrders
All checks were successful
continuous-integration/drone/push Build is passing
2022-07-04 12:38:56 +03:00
5433c7a1d2 Fixed linting error
All checks were successful
continuous-integration/drone/push Build is passing
2022-07-04 02:40:48 +03:00
0863a73625 Added a help modal
Some checks failed
continuous-integration/drone/push Build is failing
2022-07-04 02:23:09 +03:00
ba28925d3c Modified drone.yml
All checks were successful
continuous-integration/drone/push Build is passing
2022-07-03 17:41:56 +03:00
b96feef044 Modified drone.yml
All checks were successful
continuous-integration/drone/push Build is passing
2022-07-03 17:34:52 +03:00
9387994d6d Fixed no db on build error
Some checks failed
continuous-integration/drone/push Build is failing
2022-07-03 17:27:35 +03:00
e13d6a7f4a Translate table headers
Some checks failed
continuous-integration/drone/push Build is failing
2022-07-03 17:14:56 +03:00
b956bb525d Drone build update
Some checks failed
continuous-integration/drone/push Build is failing
2022-07-03 16:59:19 +03:00
3c888dc7f1 Added i18n
Some checks failed
continuous-integration/drone/push Build is failing
2022-07-03 16:31:31 +03:00
f7b78f6d0f Return empty orders on error
Some checks failed
continuous-integration/drone/push Build is failing
2022-07-02 18:58:00 +03:00
04a3ea5a23 Handle api errors
Some checks failed
continuous-integration/drone/push Build is failing
2022-07-02 17:50:25 +03:00
0b808328f4 Path to pm2
Some checks failed
continuous-integration/drone/push Build is failing
2022-03-30 03:30:38 +03:00
55ecb3b12b Edited pipeline
Some checks failed
continuous-integration/drone/push Build is failing
2022-03-30 03:22:45 +03:00
d366262636 Edited pipeline
Some checks failed
continuous-integration/drone/push Build is failing
2022-03-30 03:21:54 +03:00
714ef9c87f Edited pipeline
Some checks failed
continuous-integration/drone/push Build is failing
2022-03-30 03:20:43 +03:00
41116e6ef1 POSIX exec
Some checks failed
continuous-integration/drone/push Build is failing
2022-03-30 03:11:14 +03:00
b56f1b0b62 Full path
Some checks failed
continuous-integration/drone/push Build is failing
2022-03-30 03:06:34 +03:00
5aab4a955b Source interactive shell files
Some checks failed
continuous-integration/drone/push Build is failing
2022-03-30 03:05:20 +03:00
3f60cba322 Edited pipeline
Some checks failed
continuous-integration/drone/push Build is failing
2022-03-30 02:58:00 +03:00
f8e36fc519 Join lines 2022-03-30 02:54:40 +03:00
9937a407de Edited pipeline 2022-03-30 02:49:14 +03:00
92395f9db1 Replace ssh key with pass 2022-03-30 02:47:37 +03:00
f649cc2c14 Edited pipeline 2022-03-30 02:40:26 +03:00
3f3e851590 Source profioe
Some checks failed
continuous-integration/drone/push Build is failing
2022-03-22 17:05:05 +03:00
dc54effc83 ssh client
Some checks failed
continuous-integration/drone/push Build is failing
2022-03-22 17:02:23 +03:00
6951ce5db7 Remove dependency
Some checks failed
continuous-integration/drone/push Build is failing
2022-03-22 17:00:45 +03:00
10516d84e6 Big step 2022-03-22 16:59:42 +03:00
307dfc2433 alpine-ssh 2022-03-22 16:53:53 +03:00
42c66ac8da All docker 2022-03-22 16:53:08 +03:00
7d70bec13b Костыль путь до npm
Some checks failed
continuous-integration/drone/push Build is failing
2022-03-22 16:49:37 +03:00
fcff4f50c1 Twst pwd
Some checks failed
continuous-integration/drone/push Build is failing
2022-03-22 16:42:29 +03:00
72a395e813 Specified port
Some checks failed
continuous-integration/drone/push Build encountered an error
continuous-integration/drone Build is failing
2022-03-22 16:21:58 +03:00
3877a37e7e Removed useless scp
Some checks failed
continuous-integration/drone/push Build encountered an error
2022-03-22 16:14:57 +03:00
cbffc25a78 Updated drone config
Some checks failed
continuous-integration/drone/push Build encountered an error
2022-03-22 16:12:58 +03:00
131a0b23f5 Disclamer
All checks were successful
continuous-integration/drone/push Build is passing
2022-03-19 20:01:58 +03:00
4f54d08666 Got rid of scss files, fixet time format
All checks were successful
continuous-integration/drone/push Build is passing
2022-03-19 15:41:32 +03:00
76bf04f8de Fix redirect and restart frontend on push
All checks were successful
continuous-integration/drone/push Build is passing
2022-03-19 03:38:25 +03:00
12a482324c pm2 file and redirect fix
All checks were successful
continuous-integration/drone/push Build is passing
2022-03-19 03:11:52 +03:00
e733ca9cc3 Renamed pm2 process
Some checks failed
continuous-integration/drone/push Build is failing
2022-03-19 02:59:51 +03:00
66212c9a7d Sequential pipeline
All checks were successful
continuous-integration/drone/push Build is passing
2022-03-19 01:49:17 +03:00
34c1e577f4 Absolute path
Some checks failed
continuous-integration/drone/push Build is failing
2022-03-19 01:45:44 +03:00
3d3d145072 Install deps in pipeline
Some checks failed
continuous-integration/drone/push Build encountered an error
continuous-integration/drone Build is failing
2022-03-19 01:41:44 +03:00
7e31449bd2 added mongo url
All checks were successful
continuous-integration/drone/push Build is passing
2022-03-18 23:23:33 +03:00
9d663510bc Install in workspace
Some checks failed
continuous-integration/drone/push Build is failing
2022-03-18 22:46:35 +03:00
2c4cd8ca5d Docker deploy yml
Some checks failed
continuous-integration/drone/push Build is failing
2022-03-18 22:24:25 +03:00
2198e134d6 Explicit logo size
Some checks failed
continuous-integration/drone/push Build is failing
2022-03-14 23:57:27 +03:00
2df13c117a ci preparations
Some checks failed
continuous-integration/drone Build is failing
2022-03-14 22:46:08 +03:00
789fb6f38d Fixed mongo hanging script 2022-03-14 03:38:29 +03:00
946a9afeac Expanded mongo model 2022-03-14 01:41:52 +03:00
beb5ff052f Material UI 2022-03-14 00:29:29 +03:00
6ce512cac4 scss variables 2022-03-13 22:49:28 +03:00
773797757c Fixed critical mongoose hang error 2022-03-13 20:29:37 +03:00
9d6735bd40 eslint 2022-03-13 18:20:10 +03:00
648af4f060 Split table into component 2022-03-13 16:19:52 +03:00
9e216f821f Prefer update documents instead of inserting 2022-03-13 14:34:57 +03:00
ff2cc92b5f npm workspace integration 2022-03-13 14:06:16 +03:00
1e397af280 File structure 2022-03-12 03:35:35 +03:00
67 changed files with 17375 additions and 4096 deletions

View File

@@ -1,20 +0,0 @@
kind: pipeline
type: ssh
name: default
server:
host: 192.168.1.54
user: pi
password:
from_secret: password
steps:
- name: pull
commands:
- cd /home/pi/warframe-market-bot
- git fetch --all
- git reset --hard origin/master
- name: install
commands:
- cd /home/pi/warframe-market-bot
- npm install

3
.gitignore vendored
View File

@@ -2,4 +2,5 @@ node_modules
.DS_Store .DS_Store
.vscode .vscode
.env .env
public/*.html .idea
.yarn

1
app/.env.local.example Normal file
View File

@@ -0,0 +1 @@
MONGODB_URI=

15
app/.eslintrc.json Normal file
View File

@@ -0,0 +1,15 @@
{
"extends": ["standard", "standard-jsx", "standard-react", "next/core-web-vitals"],
"rules": {
"import/order": [
"error",
{
"newlines-between": "always",
"alphabetize": {
"order": "asc",
"caseInsensitive": true
}
}
]
}
}

30
app/.gitignore vendored Normal file
View File

@@ -0,0 +1,30 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local

76
app/README.md Normal file
View File

@@ -0,0 +1,76 @@
## Example app using MongoDB
[MongoDB](https://www.mongodb.com/) is a general purpose, document-based, distributed database built for modern application developers and for the cloud era. This example will show you how to connect to and use MongoDB as your backend for your Next.js app.
If you want to learn more about MongoDB, visit the following pages:
- [MongoDB Atlas](https://mongodb.com/atlas)
- [MongoDB Documentation](https://docs.mongodb.com/)
## Deploy your own
Once you have access to the environment variables you'll need, deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example):
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https://github.com/vercel/next.js/tree/canary/examples/with-mongodb&project-name=with-mongodb&repository-name=with-mongodb&env=MONGODB_URI&envDescription=Required%20to%20connect%20the%20app%20with%20MongoDB)
## How to use
Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example:
```bash
npx create-next-app --example with-mongodb with-mongodb-app
# or
yarn create next-app --example with-mongodb with-mongodb-app
```
## Configuration
### Set up a MongoDB database
Set up a MongoDB database either locally or with [MongoDB Atlas for free](https://mongodb.com/atlas).
### Set up environment variables
Copy the `env.local.example` file in this directory to `.env.local` (which will be ignored by Git):
```bash
cp .env.local.example .env.local
```
Set each variable on `.env.local`:
- `MONGODB_URI` - Your MongoDB connection string. If you are using [MongoDB Atlas](https://mongodb.com/atlas) you can find this by clicking the "Connect" button for your cluster.
### Run Next.js in development mode
```bash
npm install
npm run dev
# or
yarn install
yarn dev
```
Your app should be up and running on [http://localhost:3000](http://localhost:3000)! If it doesn't work, post on [GitHub discussions](https://github.com/vercel/next.js/discussions).
You will either see a message stating "You are connected to MongoDB" or "You are NOT connected to MongoDB". Ensure that you have provided the correct `MONGODB_URI` environment variable.
When you are successfully connected, you can refer to the [MongoDB Node.js Driver docs](https://mongodb.github.io/node-mongodb-native/3.4/tutorials/collections/) for further instructions on how to query your database.
## Deploy on Vercel
You can deploy this app to the cloud with [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)).
#### Deploy Your Local Project
To deploy your local project to Vercel, push it to GitHub/GitLab/Bitbucket and [import to Vercel](https://vercel.com/new?utm_source=github&utm_medium=readme&utm_campaign=next-example).
**Important**: When you import your project on Vercel, make sure to click on **Environment Variables** and set them to match your `.env.local` file.
#### Deploy from Our Template
Alternatively, you can deploy using our template by clicking on the Deploy button below.
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https://github.com/vercel/next.js/tree/canary/examples/with-mongodb&project-name=with-mongodb&repository-name=with-mongodb&env=MONGODB_URI,MONGODB_DB&envDescription=Required%20to%20connect%20the%20app%20with%20MongoDB)

33
app/components/Footer.js Normal file
View File

@@ -0,0 +1,33 @@
import { Box, Typography, Link } from '@mui/material'
import { Component } from 'react'
export default class Footer extends Component {
render () {
return (
<Box
sx={{
p: 12
}}
>
All data is collected from the&nbsp;
<Link
href='https://warframe.market/api_docs'
target='_blank'
rel='noreferrer'
>
Warframe Market API
</Link>
<Typography variant='h6'>DISCLAIMER</Typography>
<Box
sx={{
fontSize: 12,
lineHeight: 'normal'
}}
>
Digital Extremes Ltd, Warframe and the logo Warframe are registered trademarks. All rights are reserved worldwide. This site has no official link with Digital Extremes Ltd or Warframe. All artwork, screenshots, characters or other recognizable features of the intellectual property relating to these trademarks are likewise the intellectual property of Digital Extremes Ltd.
</Box>
</Box>
)
}
}

View File

@@ -0,0 +1,37 @@
import Router from 'next/router'
import React, { useCallback, useEffect } from 'react'
import ym, { YMInitializer } from 'react-yandex-metrika'
const WithYandexMetrika = (props) => {
const { children } = props
const enabled = process.env.NODE_ENV !== 'development'
const hit = useCallback((url) => {
if (enabled) {
ym('hit', url)
} else {
console.log('%c[YandexMetrika](HIT)', 'color: orange', url)
}
}, [enabled])
useEffect(() => {
hit(window.location.pathname + window.location.search)
Router.events.on('routeChangeComplete', hit)
}, [hit])
return (
<>
{enabled && (
<YMInitializer
accounts={[87671663]}
options={{ webvisor: true, defer: true }}
version='2'
/>
)}
{children}
</>
)
}
export default WithYandexMetrika

16
app/components/layout.js Normal file
View File

@@ -0,0 +1,16 @@
import Head from 'next/head'
import Footer from './Footer'
export default function Layout ({ children }) {
return (
<>
<Head>
<title>Warframe Center</title>
<link rel='icon' href='/favicon.ico' />
</Head>
<main>{children}</main>
<Footer />
</>
)
}

43
app/lib/dbConnect.js Normal file
View File

@@ -0,0 +1,43 @@
import mongoose from 'mongoose'
const MONGODB_URI = process.env.MONGODB_URI
if (!MONGODB_URI) {
throw new Error(
'Please define the MONGODB_URI environment variable inside .env.local'
)
}
/**
* Global is used here to maintain a cached connection across hot reloads
* in development. This prevents connections growing exponentially
* during API Route usage.
*/
let cached = global.mongoose
if (!cached) {
cached = global.mongoose = { conn: null, promise: null }
}
async function dbConnect () {
if (cached.conn) {
return cached.conn
}
if (!cached.promise) {
const opts = {
useNewUrlParser: true,
useUnifiedTopology: true,
bufferCommands: false
}
cached.promise = mongoose.connect(MONGODB_URI, opts)
.then(mongoose => {
return mongoose
})
}
cached.conn = await cached.promise
return cached.conn
}
export default dbConnect

View File

@@ -0,0 +1,6 @@
module.exports = {
i18n: {
defaultLocale: 'en',
locales: ['en', 'ru']
}
}

31
app/next.config.js Normal file
View File

@@ -0,0 +1,31 @@
const withPlugins = require('next-compose-plugins')
const { i18n } = require('./next-i18next.config')
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
i18n
// webpack: (config, { webpack }) => {
// return config
// },
}
const redirects = {
async redirects () {
return [
{
source: '/',
destination: '/home',
permanent: true
}
]
}
}
module.exports = withPlugins(
[
[redirects]
],
nextConfig
)

38
app/package.json Normal file
View File

@@ -0,0 +1,38 @@
{
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
},
"dependencies": {
"@emotion/react": "^11.8.2",
"@emotion/styled": "^11.8.1",
"@mui/icons-material": "^5.5.0",
"@mui/material": "^5.5.0",
"@mui/x-data-grid": "^5.6.1",
"next": "latest",
"next-compose-plugins": "^2.2.1",
"next-i18next": "^11.0.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-moment": "^1.1.1",
"react-yandex-metrika": "^2.6.0",
"reset-css": "^5.0.1",
"sass": "^1.49.9",
"shared-stuff": "file:../shared-stuff",
"sharp": "^0.30.3"
},
"devDependencies": {
"babel-eslint": "^10.1.0",
"eslint": "^8.11.0",
"eslint-config-next": "^12.1.0",
"eslint-config-standard": "^16.0.3",
"eslint-config-standard-jsx": "^10.0.0",
"eslint-config-standard-react": "^11.0.1",
"eslint-plugin-import": "^2.25.4",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^6.0.0",
"eslint-plugin-react": "^7.29.3"
}
}

29
app/pages/_app.js Normal file
View File

@@ -0,0 +1,29 @@
import { CssBaseline } from '@mui/material'
import { createTheme, ThemeProvider } from '@mui/material/styles'
import { appWithTranslation } from 'next-i18next'
import 'reset-css'
import '../styles/global.scss'
import Layout from '../components/layout'
import WithYandexMetrika from '../components/WithYandexMetrika'
function App ({ Component, pageProps }) {
const theme = createTheme({
palette: {
mode: 'light'
}
})
return (
<WithYandexMetrika>
<ThemeProvider theme={theme}>
<CssBaseline />
<Layout>
<Component {...pageProps} />
</Layout>
</ThemeProvider>
</WithYandexMetrika>
)
}
export default appWithTranslation(App)

View File

@@ -0,0 +1,32 @@
import { Dialog, DialogTitle, Box, Typography } from '@mui/material'
import { useTranslation } from 'next-i18next'
export default function HelpModal ({ open, onClose }) {
const { t } = useTranslation('home')
return (
<Dialog
open={open}
onClose={onClose}
>
<DialogTitle>
{t('help_header')}
</DialogTitle>
<Box
sx={{
p: 3
}}
>
<Typography gutterBottom>
{t('help_body_1')}
</Typography>
<Typography gutterBottom>
{t('help_body_2')}
</Typography>
<Typography gutterBottom>
{t('help_body_3')}
</Typography>
</Box>
</Dialog>
)
}

73
app/pages/home/Hero.js Normal file
View File

@@ -0,0 +1,73 @@
import { Box, Chip } from '@mui/material'
import { useTranslation } from 'next-i18next'
import Image from 'next/image'
import { useState } from 'react'
import logo from './assets/warframe_logo.png'
import HelpModal from './HelpModal'
const Hero = () => {
const { t } = useTranslation('home')
const [modalOpen, setModalOpen] = useState(false)
const openModal = () => {
setModalOpen(true)
}
const closeModal = () => {
setModalOpen(false)
}
return (
<>
<Box
sx={{
py: 12,
textAlign: 'center'
}}
>
<Chip
label={t('help_header')}
variant='outlined'
onClick={openModal}
sx={{
position: 'absolute',
top: '10px',
right: '10px'
}}
/>
<Box
sx={{
width: 400,
m: 'auto'
}}
>
<Image
src={logo}
alt='logo'
layout='responsive'
width={500}
height={300}
/>
</Box>
<Box
sx={{
width: 400,
m: 'auto'
}}
>
<h1>Market Gaps</h1>
<p>{t('description')}</p>
</Box>
</Box>
<HelpModal
open={modalOpen}
onClose={closeModal}
/>
</>
)
}
export default Hero

98
app/pages/home/Table.js Normal file
View File

@@ -0,0 +1,98 @@
import { Link, Typography, Box } from '@mui/material'
import { DataGrid } from '@mui/x-data-grid'
import { Component } from 'react'
import { withTranslation } from 'react-i18next'
import Moment from 'react-moment'
class Table extends Component {
constructor ({ scanResults, t }) {
super()
this.scanResults = scanResults
? scanResults.map(row => ({
...row,
id: row._id
}))
: []
this.columns = [
{
field: 'name',
headerName: t('name'),
flex: 2,
renderCell: (cellValues) => {
return (
<Box
component='span'
sx={{
display: 'block'
}}
>
<Link
target='_blank'
href={cellValues.row.url}
rel='noreferrer'
>
{cellValues.row.fullName}
</Link>
<Typography variant='caption' display='block'>
<Moment
format='DD.MM HH:mm'
>
{cellValues.row.updatedAt}
</Moment>
</Typography>
</Box>
)
}
},
{
field: 'partsPrice',
headerName: t('parts_price'),
flex: 1
},
{
field: 'setPrice',
headerName: t('set_price'),
flex: 1
},
{
field: 'difference',
headerName: t('difference'),
flex: 1
}
]
}
static getInitialProps () {
return {
props: { scanResults: [] }
}
}
render () {
return (
<Box
sx={{
height: '90vh',
width: '100%'
}}
>
<DataGrid
rows={this.scanResults}
columns={this.columns}
autoPageSize
initialState={{
sorting: {
sortModel: [{ field: 'difference', sort: 'desc' }]
}
}}
sx={{
borderLeft: 'none',
borderRight: 'none'
}}
/>
</Box>
)
}
}
export default withTranslation('home')(Table)

View File

Before

Width:  |  Height:  |  Size: 1.7 MiB

After

Width:  |  Height:  |  Size: 1.7 MiB

View File

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

View File

Before

Width:  |  Height:  |  Size: 234 KiB

After

Width:  |  Height:  |  Size: 234 KiB

30
app/pages/home/index.js Normal file
View File

@@ -0,0 +1,30 @@
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
import { models } from 'shared-stuff'
import dbConnect from '../../lib/dbConnect'
import Hero from './Hero'
import Table from './Table'
export default function Home ({ scanResults }) {
return (
<>
<Hero />
<Table
scanResults={scanResults}
/>
</>
)
}
export async function getServerSideProps ({ locale }) {
await dbConnect()
const scanResults = await models.ScanResult.find({})
return {
props: {
scanResults: JSON.parse(JSON.stringify(scanResults)),
...(await serverSideTranslations(locale, ['common', 'home']))
}
}
}

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

View File

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

View File

Before

Width:  |  Height:  |  Size: 8.6 KiB

After

Width:  |  Height:  |  Size: 8.6 KiB

View File

Before

Width:  |  Height:  |  Size: 9.0 KiB

After

Width:  |  Height:  |  Size: 9.0 KiB

View File

View File

@@ -0,0 +1,11 @@
{
"description": "Find a profitable difference between the price of the set and the price of the sum of it's parts.",
"help_header": "How to use this site",
"help_body_1": "The main page of the site has a table, that contains info about prime items, that is updated in real time. The column \"Name\" has a link to the item on warframe.market and the time of the last update.",
"help_body_2": "Knowing the difference between the price of the set and the price of the sum of it's parts lets you save platinum buying or even make a profit by purchasing the parts and selling them as a set.",
"help_body_3": "Have fun!",
"name": "Name",
"parts_price": "Parts Price",
"set_price": "Set Price",
"difference": "Difference"
}

View File

View File

@@ -0,0 +1,11 @@
{
"description": "Находи выгодную разницу между ценой набора и ценой его частей по отдельности.",
"help_header": "Как использовать этот сайт?",
"help_body_1": "На главной странице сайта находится таблица, которая содержит информацию о прайм предметах, обновляемую в реальном времени. Колонка \"Наименование\" содержит ссылку на страницу предмета на warframe.market и время последнего обновления информации.",
"help_body_2": "Зная разницу между ценой частей и ценой комплекта можно, например сэкономить на покупке или даже заработать, если купить части по отдельности, а продать как сет.",
"help_body_3": "Приятного использования!",
"name": "Наименование",
"parts_price": "Цена частей",
"set_price": "Цена набора",
"difference": "Разница"
}

22
app/styles/_fonts.scss Normal file
View File

@@ -0,0 +1,22 @@
$text-font: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue,
sans-serif;
$font-h0: normal normal 300 56px/64px $text-font;
$font-h1: normal normal 300 48px/56px $text-font;
$font-h2: normal normal 300 40px/48px $text-font;
$font-h3: normal normal 300 36px/44px $text-font;
$font-h4: normal normal 300 32px/40px $text-font;
$font-h5: normal normal 300 28px/36px $text-font;
$font-h6: normal normal 300 24px/32px $text-font;
$font-h7: normal normal 300 20px/26px $text-font;
$font-h8: normal normal 300 18px/24px $text-font;
$font-h9: normal normal 300 16px/20px $text-font;
$font-r8: normal normal 300 24px/30px $text-font;
$font-r7: normal normal 300 22px/28px $text-font;
$font-r6: normal normal 300 20px/26px $text-font;
$font-r5: normal normal 300 18px/24px $text-font;
$font-r4: normal normal 300 16px/22px $text-font;
$font-r3: normal normal 300 14px/18px $text-font;
$font-r2: normal normal 300 12px/16px $text-font;

View File

@@ -0,0 +1 @@
@import './fonts';

31
app/styles/global.scss Normal file
View File

@@ -0,0 +1,31 @@
@import './variables';
html,
body {
padding: 0;
margin: 0;
font: $font-r4;
}
* {
box-sizing: border-box;
}
h1 {
font: $font-h1;
}
h2 {
font: $font-h2;
}
h3 {
font: $font-h3;
}
h4 {
font: $font-h4;
}
h5 {
font: $font-h5;
}
h6 {
font: $font-h6;
}

View File

@@ -1,9 +0,0 @@
module.exports = {
apps: [{
name: 'warframe-market-bot',
script: './src/index.js',
watch: true,
ignore_watch: ['node_modules', 'public'],
restart_delay: 1 * 60 * 1000
}]
}

9
gather/Dockerfile Normal file
View File

@@ -0,0 +1,9 @@
FROM node:20-alpine
LABEL authors="anatolykopyl"
WORKDIR /usr/node/app
COPY package*.json ./
RUN npm ci
COPY . .

View File

@@ -0,0 +1,9 @@
version: '3'
services:
warframe-center-gather:
image: git.radner.ru/anatolykopyl/warframe-center-gather:latest
container_name: warframe-center-gather
working_dir: /usr/node/app
command: >
sh -c "node src/index.js"

6816
gather/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

20
gather/package.json Normal file
View File

@@ -0,0 +1,20 @@
{
"name": "warframe-market-bot",
"version": "1.0.0",
"description": "The background job that collects data and stores it in a DB",
"main": "index.js",
"scripts": {
"start": "node src/index.js"
},
"author": "Anatoly Kopyl",
"license": "ISC",
"dependencies": {
"axios": "^0.26.0",
"dotenv": "^16.0.0",
"limiter": "^2.1.0",
"shared-stuff": "file:../shared-stuff"
},
"devDependencies": {
"eslint-config-standard": "^16.0.3"
}
}

View File

@@ -10,7 +10,16 @@ class Api {
async _get (url) { async _get (url) {
await this.limiter.removeTokens(1) await this.limiter.removeTokens(1)
return axios.get(url) return axios.get(url).catch((error) => {
console.error(error)
return {
data: {
payload: {
orders: []
}
}
}
})
} }
async getOrders (url) { async getOrders (url) {
@@ -22,6 +31,10 @@ class Api {
async getSortedOrders (url) { async getSortedOrders (url) {
const orders = await this.getOrders(url) const orders = await this.getOrders(url)
if (orders.length === 0) {
return []
}
return orders.sort(function (a, b) { return orders.sort(function (a, b) {
return a.platinum - b.platinum return a.platinum - b.platinum
}) })

50
gather/src/index.js Normal file
View File

@@ -0,0 +1,50 @@
require('dotenv').config()
const mongoose = require('mongoose')
const items = require('./items')
const { models } = require('shared-stuff')
async function initDB () {
await mongoose.connect(process.env.MONGODB_URI)
}
const main = async (firstLaunch) => {
if (firstLaunch) {
await initDB()
}
for (const item of items) {
process.stdout.write(`Looking at ${item.name}: `)
let partsPrice = 0
for (const part of item.parts) {
process.stdout.write(`${part.part} `)
partsPrice += await part.getPrice()
}
const setPrice = await item.set.getPrice()
await models.ScanResult.findOneAndUpdate(
{ name: item.name },
{
fullName: item.fullName,
url: item.set.url,
timestamp: new Date(),
partsPrice,
setPrice,
difference: setPrice - partsPrice
},
{ upsert: true }
)
console.log('✅')
}
await main()
}
main(true)
process.on('SIGINT', () => {
mongoose.disconnect().then(() => {
process.exit()
})
})

View File

@@ -1,8 +1,13 @@
const Part = require('./Part') const Part = require('./Part')
function capitalize (string) {
return string.charAt(0).toUpperCase() + string.slice(1)
}
module.exports = class Item { module.exports = class Item {
constructor (name) { constructor (name) {
this.name = name this.name = name
this.fullName = capitalize(name) + ' Prime'
this.set = new Part(name, 'set') this.set = new Part(name, 'set')
this.parts = [] this.parts = []

View File

@@ -3,12 +3,17 @@ const Api = require('../api')
module.exports = class Part { module.exports = class Part {
constructor (set, part, amount) { constructor (set, part, amount) {
this.part = part this.part = part
this.url = `${set}_prime_${part}` this.urlPath = `${set}_prime_${part}`
this.url = `https://warframe.market/items/${set}_prime_${part}`
this.amount = amount ?? 1 this.amount = amount ?? 1
} }
async getPrice () { async getPrice () {
const orders = await Api.getSortedOrders(this.url) const orders = await Api.getSortedOrders(this.urlPath)
if (orders.length === 0) {
return 0
}
return Number(orders[0].platinum) * this.amount return Number(orders[0].platinum) * this.amount
} }
} }

13797
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,21 +1,18 @@
{ {
"name": "warframe-market-bot", "name": "warframe-center",
"version": "1.0.0", "version": "1.0.0",
"description": "", "description": "Список Прайм предметов: https://warframe.fandom.com/wiki/Prime",
"main": "index.js", "repository": {
"scripts": { "type": "git",
"test": "echo \"Error: no test specified\" && exit 1" "url": "ssh://git@git.radner.ru:3036/anatolykopyl/warframe-center.git"
}, },
"author": "Anatoly Kopyl", "author": "Anatoly Kopyl",
"license": "ISC", "workspaces": [
"./app",
"./gather",
"./shared-stuff"
],
"dependencies": { "dependencies": {
"axios": "^0.26.0", "mongoose": "^6.2.7"
"dotenv": "^16.0.0",
"handlebars": "^4.7.7",
"limiter": "^2.1.0"
},
"devDependencies": {
"eslint-config-standard": "^16.0.3",
"pm2": "^5.2.0"
} }
} }

View File

@@ -1,25 +0,0 @@
const slider = document.getElementById('min-difference')
slider.addEventListener('input', function (event) {
const table = document.getElementById('items')
Array.from(table.children).forEach(row => {
if (row.dataset.difference < Number(event.target.value)) {
row.style.display = 'none'
} else {
row.style.display = 'table-row'
}
})
document.getElementById('filter-value').innerText = event.target.value
})
document.addEventListener('scroll', function () {
const gunner1 = document.getElementById('gunner1')
const gunner2 = document.getElementById('gunner2')
gunner1.style.transform = `translateX(${-window.scrollY}px)`
gunner2.style.transform = `translateX(${window.scrollY}px)`
gunner1.style.filter = `blur(${window.scrollY / 50}px)`
gunner2.style.filter = `blur(${window.scrollY / 50}px)`
})

View File

@@ -1,186 +0,0 @@
:root {
--bg-main: white;
--brand-clr: rgb(26, 34, 58);
--bg: rgb(16, 22, 25);
--text-clr: black;
--accent-clr: red;
}
* {
font-family: sans-serif;
font-weight: 100;
}
body {
position: relative;
margin: 0;
min-height: 100vh;
background: var(--bg);
color: var(--text-clr);
text-align: center;
}
a {
text-decoration: none;
color: var(--accent-clr);
}
.main-column {
width: 800px;
background: var(--bg-main);
padding: 32px 128px;
box-sizing: border-box;
margin: 128px auto;
border: 2px solid var(--accent-clr);
}
.main-column::before {
position: absolute;
left: 0px;
width: 50%;
border-top: 2px solid var(--accent-clr);
z-index: -1;
content: '';
}
.main-column::after {
position: absolute;
right: 0px;
width: 50%;
border-top: 2px solid var(--accent-clr);
z-index: -1;
content: '';
}
.hero {
width: 100%;
padding: 128px 0;
position: relative;
box-sizing: border-box;
background: white;
color: black;
text-align: center;
overflow: hidden;
}
.hero > .main {
position: relative;
margin: auto;
display: flex;
flex-direction: column;
z-index: 10;
color: var(--brand-clr);
}
.hero .logo, .hero .text {
margin: auto;
width: 400px;
}
#gunner1, #gunner2 {
position: absolute;
max-width: 500px;
height: 500px;
object-fit: contain;
transition: transform .1s;
top: calc(50% - 250px);
}
#gunner1 {
left: 0;
}
#gunner2 {
right: 0;
}
.languages {
position: absolute;
left: 0;
top: 0;
margin: 10px;
z-index: 20;
}
.languages > a {
position: relative;
display: inline-block;
padding: 5px;
height: 32px;
border-radius: 50%;
}
.languages img {
max-height: 100%;
}
.languages .selected {
background: rgb(233, 233, 233);
box-shadow: 0 0 5px rgba(0, 0, 0, 50%);
}
.controls {
margin: 32px auto;
display: flex;
align-items: center;
justify-content: center;
background: var(--bg-main);
padding: 8px;
width: max-content;
}
.controls > * {
margin: 0 4px;
}
table {
min-width: 500px;
margin: 32px auto;
border-spacing: 0;
background: var(--bg-main);
}
tr {
text-align: left;
}
td {
padding: 2px 16px;
}
th {
padding: 8px 16px;
font-weight: 400;
}
.name {
text-transform: capitalize;
}
.timestamp {
font-size: 14px;
padding-bottom: 16px;
position: sticky;
top: 100%;
color: white;
}
@media screen and (max-width: 500px) {
.hero .logo {
width: 100%;
}
#gunner1, #gunner2 {
display: none;
}
.main-column {
width: 100%;
border: none;
padding: 72px 0;
}
table {
min-width: 100%;
}
}

11
shared-stuff/dist/index.js vendored Normal file
View File

@@ -0,0 +1,11 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.models = void 0;
const ScanResult_1 = __importDefault(require("./models/ScanResult"));
const models = {
ScanResult: ScanResult_1.default
};
exports.models = models;

17
shared-stuff/dist/models/ScanResult.js vendored Normal file
View File

@@ -0,0 +1,17 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const mongoose_1 = __importDefault(require("mongoose"));
const scanResultSchema = new mongoose_1.default.Schema({
name: String,
fullName: String,
url: String,
partsPrice: Number,
setPrice: Number,
difference: Number
}, {
timestamps: true
});
exports.default = mongoose_1.default.models.ScanResult || mongoose_1.default.model('ScanResult', scanResultSchema);

9
shared-stuff/index.js Normal file
View File

@@ -0,0 +1,9 @@
const ScanResult = require('./models/ScanResult')
const models = {
ScanResult
}
module.exports = {
models
}

View File

@@ -0,0 +1,15 @@
const mongoose = require('mongoose')
const scanResultSchema = new mongoose.Schema({
name: String,
fullName: String,
url: String,
partsPrice: Number,
setPrice: Number,
difference: Number
},
{
timestamps: true
})
module.exports = mongoose.models.ScanResult || mongoose.model('ScanResult', scanResultSchema)

509
shared-stuff/package-lock.json generated Normal file
View File

@@ -0,0 +1,509 @@
{
"name": "shared-stuff",
"version": "1.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"version": "1.0.0",
"dependencies": {
"mongoose": "^6.2.6"
}
},
"node_modules/@types/node": {
"version": "17.0.21",
"resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.21.tgz",
"integrity": "sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ=="
},
"node_modules/@types/webidl-conversions": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-6.1.1.tgz",
"integrity": "sha512-XAahCdThVuCFDQLT7R7Pk/vqeObFNL3YqRyFZg+AqAP/W1/w3xHaIxuW7WszQqTbIBOPRcItYJIou3i/mppu3Q=="
},
"node_modules/@types/whatwg-url": {
"version": "8.2.1",
"resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.1.tgz",
"integrity": "sha512-2YubE1sjj5ifxievI5Ge1sckb9k/Er66HyR2c+3+I6VDUUg1TLPdYYTEbQ+DjRkS4nTxMJhgWfSfMRD2sl2EYQ==",
"dependencies": {
"@types/node": "*",
"@types/webidl-conversions": "*"
}
},
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
]
},
"node_modules/bson": {
"version": "4.6.1",
"resolved": "https://registry.npmjs.org/bson/-/bson-4.6.1.tgz",
"integrity": "sha512-I1LQ7Hz5zgwR4QquilLNZwbhPw0Apx7i7X9kGMBTsqPdml/03Q9NBtD9nt/19ahjlphktQImrnderxqpzeVDjw==",
"dependencies": {
"buffer": "^5.6.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/buffer": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"dependencies": {
"base64-js": "^1.3.1",
"ieee754": "^1.1.13"
}
},
"node_modules/debug": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz",
"integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==",
"dependencies": {
"ms": "2.1.2"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/debug/node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node_modules/denque": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/denque/-/denque-2.0.1.tgz",
"integrity": "sha512-tfiWc6BQLXNLpNiR5iGd0Ocu3P3VpxfzFiqubLgMfhfOw9WyvgJBd46CClNn9k3qfbjvT//0cf7AlYRX/OslMQ==",
"engines": {
"node": ">=0.10"
}
},
"node_modules/ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
]
},
"node_modules/ip": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz",
"integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo="
},
"node_modules/kareem": {
"version": "2.3.4",
"resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.4.tgz",
"integrity": "sha512-Vcrt8lcpVl0s8ePx634BxwRqmFo+5DcOhlmNadehxreMTIQi/9hOL/B3hZQQbK5DgMS7Lem3xABXV7/S3jy+7g=="
},
"node_modules/memory-pager": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz",
"integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==",
"optional": true
},
"node_modules/mongodb": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.3.1.tgz",
"integrity": "sha512-sNa8APSIk+r4x31ZwctKjuPSaeKuvUeNb/fu/3B6dRM02HpEgig7hTHM8A/PJQTlxuC/KFWlDlQjhsk/S43tBg==",
"dependencies": {
"bson": "^4.6.1",
"denque": "^2.0.1",
"mongodb-connection-string-url": "^2.4.1",
"socks": "^2.6.1"
},
"engines": {
"node": ">=12.9.0"
},
"optionalDependencies": {
"saslprep": "^1.0.3"
}
},
"node_modules/mongodb-connection-string-url": {
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.5.2.tgz",
"integrity": "sha512-tWDyIG8cQlI5k3skB6ywaEA5F9f5OntrKKsT/Lteub2zgwSUlhqEN2inGgBTm8bpYJf8QYBdA/5naz65XDpczA==",
"dependencies": {
"@types/whatwg-url": "^8.2.1",
"whatwg-url": "^11.0.0"
}
},
"node_modules/mongoose": {
"version": "6.2.6",
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-6.2.6.tgz",
"integrity": "sha512-OkPM1y7Ed9+Pa2/18mxegcD0OOe/aCXTQvOEyEn/MzVdaRsVSc+zE6myOS4LkWWi30c2tl4fpdJJvgC/MgXiww==",
"dependencies": {
"bson": "^4.2.2",
"kareem": "2.3.4",
"mongodb": "4.3.1",
"mpath": "0.8.4",
"mquery": "4.0.2",
"ms": "2.1.3",
"sift": "16.0.0"
},
"engines": {
"node": ">=12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mongoose"
}
},
"node_modules/mpath": {
"version": "0.8.4",
"resolved": "https://registry.npmjs.org/mpath/-/mpath-0.8.4.tgz",
"integrity": "sha512-DTxNZomBcTWlrMW76jy1wvV37X/cNNxPW1y2Jzd4DZkAaC5ZGsm8bfGfNOthcDuRJujXLqiuS6o3Tpy0JEoh7g==",
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/mquery": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/mquery/-/mquery-4.0.2.tgz",
"integrity": "sha512-oAVF0Nil1mT3rxty6Zln4YiD6x6QsUWYz927jZzjMxOK2aqmhEz5JQ7xmrKK7xRFA2dwV+YaOpKU/S+vfNqKxA==",
"dependencies": {
"debug": "4.x"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
},
"node_modules/punycode": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
"engines": {
"node": ">=6"
}
},
"node_modules/saslprep": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz",
"integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==",
"optional": true,
"dependencies": {
"sparse-bitfield": "^3.0.3"
},
"engines": {
"node": ">=6"
}
},
"node_modules/sift": {
"version": "16.0.0",
"resolved": "https://registry.npmjs.org/sift/-/sift-16.0.0.tgz",
"integrity": "sha512-ILTjdP2Mv9V1kIxWMXeMTIRbOBrqKc4JAXmFMnFq3fKeyQ2Qwa3Dw1ubcye3vR+Y6ofA0b9gNDr/y2t6eUeIzQ=="
},
"node_modules/smart-buffer": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
"integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==",
"engines": {
"node": ">= 6.0.0",
"npm": ">= 3.0.0"
}
},
"node_modules/socks": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/socks/-/socks-2.6.2.tgz",
"integrity": "sha512-zDZhHhZRY9PxRruRMR7kMhnf3I8hDs4S3f9RecfnGxvcBHQcKcIH/oUcEWffsfl1XxdYlA7nnlGbbTvPz9D8gA==",
"dependencies": {
"ip": "^1.1.5",
"smart-buffer": "^4.2.0"
},
"engines": {
"node": ">= 10.13.0",
"npm": ">= 3.0.0"
}
},
"node_modules/sparse-bitfield": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
"integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=",
"optional": true,
"dependencies": {
"memory-pager": "^1.0.2"
}
},
"node_modules/tr46": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz",
"integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==",
"dependencies": {
"punycode": "^2.1.1"
},
"engines": {
"node": ">=12"
}
},
"node_modules/webidl-conversions": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
"integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
"engines": {
"node": ">=12"
}
},
"node_modules/whatwg-url": {
"version": "11.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz",
"integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==",
"dependencies": {
"tr46": "^3.0.0",
"webidl-conversions": "^7.0.0"
},
"engines": {
"node": ">=12"
}
}
},
"dependencies": {
"@types/node": {
"version": "17.0.21",
"resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.21.tgz",
"integrity": "sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ=="
},
"@types/webidl-conversions": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-6.1.1.tgz",
"integrity": "sha512-XAahCdThVuCFDQLT7R7Pk/vqeObFNL3YqRyFZg+AqAP/W1/w3xHaIxuW7WszQqTbIBOPRcItYJIou3i/mppu3Q=="
},
"@types/whatwg-url": {
"version": "8.2.1",
"resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.1.tgz",
"integrity": "sha512-2YubE1sjj5ifxievI5Ge1sckb9k/Er66HyR2c+3+I6VDUUg1TLPdYYTEbQ+DjRkS4nTxMJhgWfSfMRD2sl2EYQ==",
"requires": {
"@types/node": "*",
"@types/webidl-conversions": "*"
}
},
"base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="
},
"bson": {
"version": "4.6.1",
"resolved": "https://registry.npmjs.org/bson/-/bson-4.6.1.tgz",
"integrity": "sha512-I1LQ7Hz5zgwR4QquilLNZwbhPw0Apx7i7X9kGMBTsqPdml/03Q9NBtD9nt/19ahjlphktQImrnderxqpzeVDjw==",
"requires": {
"buffer": "^5.6.0"
}
},
"buffer": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
"requires": {
"base64-js": "^1.3.1",
"ieee754": "^1.1.13"
}
},
"debug": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz",
"integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==",
"requires": {
"ms": "2.1.2"
},
"dependencies": {
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
}
}
},
"denque": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/denque/-/denque-2.0.1.tgz",
"integrity": "sha512-tfiWc6BQLXNLpNiR5iGd0Ocu3P3VpxfzFiqubLgMfhfOw9WyvgJBd46CClNn9k3qfbjvT//0cf7AlYRX/OslMQ=="
},
"ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="
},
"ip": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz",
"integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo="
},
"kareem": {
"version": "2.3.4",
"resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.4.tgz",
"integrity": "sha512-Vcrt8lcpVl0s8ePx634BxwRqmFo+5DcOhlmNadehxreMTIQi/9hOL/B3hZQQbK5DgMS7Lem3xABXV7/S3jy+7g=="
},
"memory-pager": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz",
"integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==",
"optional": true
},
"mongodb": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.3.1.tgz",
"integrity": "sha512-sNa8APSIk+r4x31ZwctKjuPSaeKuvUeNb/fu/3B6dRM02HpEgig7hTHM8A/PJQTlxuC/KFWlDlQjhsk/S43tBg==",
"requires": {
"bson": "^4.6.1",
"denque": "^2.0.1",
"mongodb-connection-string-url": "^2.4.1",
"saslprep": "^1.0.3",
"socks": "^2.6.1"
}
},
"mongodb-connection-string-url": {
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.5.2.tgz",
"integrity": "sha512-tWDyIG8cQlI5k3skB6ywaEA5F9f5OntrKKsT/Lteub2zgwSUlhqEN2inGgBTm8bpYJf8QYBdA/5naz65XDpczA==",
"requires": {
"@types/whatwg-url": "^8.2.1",
"whatwg-url": "^11.0.0"
}
},
"mongoose": {
"version": "6.2.6",
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-6.2.6.tgz",
"integrity": "sha512-OkPM1y7Ed9+Pa2/18mxegcD0OOe/aCXTQvOEyEn/MzVdaRsVSc+zE6myOS4LkWWi30c2tl4fpdJJvgC/MgXiww==",
"requires": {
"bson": "^4.2.2",
"kareem": "2.3.4",
"mongodb": "4.3.1",
"mpath": "0.8.4",
"mquery": "4.0.2",
"ms": "2.1.3",
"sift": "16.0.0"
}
},
"mpath": {
"version": "0.8.4",
"resolved": "https://registry.npmjs.org/mpath/-/mpath-0.8.4.tgz",
"integrity": "sha512-DTxNZomBcTWlrMW76jy1wvV37X/cNNxPW1y2Jzd4DZkAaC5ZGsm8bfGfNOthcDuRJujXLqiuS6o3Tpy0JEoh7g=="
},
"mquery": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/mquery/-/mquery-4.0.2.tgz",
"integrity": "sha512-oAVF0Nil1mT3rxty6Zln4YiD6x6QsUWYz927jZzjMxOK2aqmhEz5JQ7xmrKK7xRFA2dwV+YaOpKU/S+vfNqKxA==",
"requires": {
"debug": "4.x"
}
},
"ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
},
"punycode": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
},
"saslprep": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz",
"integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==",
"optional": true,
"requires": {
"sparse-bitfield": "^3.0.3"
}
},
"sift": {
"version": "16.0.0",
"resolved": "https://registry.npmjs.org/sift/-/sift-16.0.0.tgz",
"integrity": "sha512-ILTjdP2Mv9V1kIxWMXeMTIRbOBrqKc4JAXmFMnFq3fKeyQ2Qwa3Dw1ubcye3vR+Y6ofA0b9gNDr/y2t6eUeIzQ=="
},
"smart-buffer": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
"integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="
},
"socks": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/socks/-/socks-2.6.2.tgz",
"integrity": "sha512-zDZhHhZRY9PxRruRMR7kMhnf3I8hDs4S3f9RecfnGxvcBHQcKcIH/oUcEWffsfl1XxdYlA7nnlGbbTvPz9D8gA==",
"requires": {
"ip": "^1.1.5",
"smart-buffer": "^4.2.0"
}
},
"sparse-bitfield": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
"integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=",
"optional": true,
"requires": {
"memory-pager": "^1.0.2"
}
},
"tr46": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz",
"integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==",
"requires": {
"punycode": "^2.1.1"
}
},
"webidl-conversions": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
"integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g=="
},
"whatwg-url": {
"version": "11.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz",
"integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==",
"requires": {
"tr46": "^3.0.0",
"webidl-conversions": "^7.0.0"
}
}
}
}

View File

@@ -0,0 +1,9 @@
{
"name": "shared-stuff",
"version": "1.0.0",
"description": "The stuff that is shared between the background job and the Next app",
"main": "index.js",
"dependencies": {
"mongoose": "^6.2.6"
}
}

View File

@@ -1,21 +0,0 @@
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()
})()

View File

@@ -1,10 +0,0 @@
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
}
}

View File

@@ -1,9 +0,0 @@
function ifEq (a, b, opts) {
if (a === b) {
return opts.fn(this)
} else {
return opts.inverse(this)
}
}
module.exports = { ifEq }

View File

@@ -1,35 +0,0 @@
{
"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": "Згенеровано в"
}
}

View File

@@ -1,48 +0,0 @@
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()

View File

@@ -1,102 +0,0 @@
<!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>