Commit 5a711f4b authored by Geovanny's avatar Geovanny

Added session to keep user logged in. Added functionality to assign a unit to a user

parent 2141fad0
server/database/config.json server/database/config.json
server/node_modules/ server/node_modules/
server/.env
discord/.env discord/.env
discord/__pycache__ discord/__pycache__
*.pyc *.pyc
__pycache__ __pycache__
.vscode
\ No newline at end of file
import asyncio import asyncio
import discord import discord
import discord
from requests import Session
from discord.ext import commands from discord.ext import commands
from unit.manager import UnitManager from unit.manager import UnitManager
from user.manager import UserManager
from util.command_error_handler import CommandErrorHandler from util.command_error_handler import CommandErrorHandler
from util.api_requests import Api
from settings import DISCORD_TOKEN from settings import DISCORD_TOKEN
class ConqBot(commands.Bot):
def __init__(self, command_prefix='>'):
super().__init__(command_prefix)
self.id_to_session = {}
async def getUserSession(self, user:discord.User):
if user.id in self.id_to_session:
return self.id_to_session[user.id]
new_session = Session()
await Api.postSession('/user/d-login', {"discordId": user.id}, new_session);
self.id_to_session[user.id] = new_session
return new_session
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
bot = commands.Bot(command_prefix='>') bot = ConqBot(command_prefix='>')
bot.add_cog(CommandErrorHandler(bot)) bot.add_cog(CommandErrorHandler(bot))
bot.add_cog(UserManager(bot))
bot.add_cog(UnitManager(bot)) bot.add_cog(UnitManager(bot))
@bot.event @bot.event
......
...@@ -7,6 +7,7 @@ from unit.model import Unit ...@@ -7,6 +7,7 @@ from unit.model import Unit
from util.api_requests import Api, ApiError from util.api_requests import Api, ApiError
from .unit_parameters import * from .unit_parameters import *
from discord_argparse import ArgumentConverter from discord_argparse import ArgumentConverter
from util.embed_style import EmbedStyle
class UnitManager(commands.Cog): class UnitManager(commands.Cog):
...@@ -111,8 +112,6 @@ class UnitManager(commands.Cog): ...@@ -111,8 +112,6 @@ class UnitManager(commands.Cog):
except ApiError as error: except ApiError as error:
return await self.handleApiError(ctx, error) return await self.handleApiError(ctx, error)
if data==None:
return await ctx.send('No unit found for: ' + unit)
unit_data = Unit(**data) unit_data = Unit(**data)
embed = discord.Embed(); embed = discord.Embed();
...@@ -159,63 +158,7 @@ class UnitManager(commands.Cog): ...@@ -159,63 +158,7 @@ class UnitManager(commands.Cog):
await self.createUnitTable(ctx, units_data) await self.createUnitTable(ctx, units_data)
async def createUnitTable(self, ctx, data): async def createUnitTable(self, ctx, data):
max_rows = 5 return await EmbedStyle.createPages(self.bot, ctx, data, 5, self.createUnitPage)
max_pages = len(data) // max_rows
rem = len(data) % max_rows
cur_page = 0;
first_run = True
if rem!=0:
max_pages+=1
while True:
if first_run:
embed = self.createUnitPage(data, max_rows*cur_page, max_rows*cur_page + max_rows)
first_run = False
msg = await ctx.send(embed=embed)
reactmoji = []
if max_pages == 1 and cur_page == 0:
pass
elif cur_page == 0:
reactmoji.append('⏩')
elif cur_page == max_pages-1:
reactmoji.append('⏪')
elif cur_page > 0 and cur_page < max_pages-1:
reactmoji.extend(['⏪', '⏩'])
for react in reactmoji:
await msg.add_reaction(react)
def check_react(reaction, user):
if reaction.message.id != msg.id:
return False
if user != ctx.message.author:
return False
if str(reaction.emoji) not in reactmoji:
return False
return True
try:
res, user = await self.bot.wait_for('reaction_add', timeout=30.0, check=check_react)
except asyncio.TimeoutError:
return await msg.clear_reactions()
if '⏪' in str(res.emoji):
cur_page-= 1
embed = self.createUnitPage(data, max_rows*cur_page, max_rows*cur_page + max_rows)
await msg.clear_reactions()
await msg.edit(embed=embed)
if '⏩' in str(res.emoji):
cur_page+= 1
embed = self.createUnitPage(data, max_rows*cur_page, max_rows*cur_page + max_rows)
await msg.clear_reactions()
await msg.edit(embed=embed)
def createUnitPage(self, data, minI, maxI): def createUnitPage(self, data, minI, maxI):
embed = discord.Embed(color=0x19212d) embed = discord.Embed(color=0x19212d)
......
...@@ -2,7 +2,9 @@ class Unit(object): ...@@ -2,7 +2,9 @@ class Unit(object):
def __init__(self, id, name, type, stars=0, hp=0, pap=0, pd=0, sap=0, sd=0, bap=0, bd=0, pdf=0, sdf=0, bdf=0, leadership=0, troop_count=0, hero_level=0, speed=0, range=0, ammo=0, labour=0, def __init__(self, id, name, type, stars=0, hp=0, pap=0, pd=0, sap=0, sd=0, bap=0, bd=0, pdf=0, sdf=0, bdf=0, leadership=0, troop_count=0, hero_level=0, speed=0, range=0, ammo=0, labour=0,
img=None, img=None,
vet_img=None): vet_img=None,
unit_level=0,
elite_flg=None):
self.id = id self.id = id
self.name = name self.name = name
self.type = type self.type = type
...@@ -31,6 +33,9 @@ class Unit(object): ...@@ -31,6 +33,9 @@ class Unit(object):
if vet_img == None: if vet_img == None:
self.vet_img = 'https://www.conquerorsblade.com/static/img/Conqueror.cd5398b.png' self.vet_img = 'https://www.conquerorsblade.com/static/img/Conqueror.cd5398b.png'
self.unit_level = unit_level
self.elite_flg = elite_flg
@classmethod @classmethod
def from_dict(cls, **data): def from_dict(cls, **data):
return cls(**data) return cls(**data)
......
import asyncio
import discord
import json
from discord.ext import commands
from discord.user import User
from unit.model import Unit
from util.api_requests import Api, ApiError
from util.embed_style import EmbedStyle
from .uu_parameters import *
class UserManager(commands.Cog):
def __init__(self, bot, loop=None):
self.bot = bot
self.loop = loop or asyncio.get_event_loop()
async def handleApiError(self, ctx, error):
return await ctx.send(error.message)
@commands.command()
async def registerUser(self, ctx):
req_body = {'discordId': ctx.message.author.id}
try:
await Api.post('/user/discord-register', req_body)
await ctx.send('Register succesful')
except ApiError as error:
return await self.handleApiError(ctx, error)
@commands.command()
async def removeAssign(self, ctx, term:str):
data = None
try:
session = await self.bot.getUserSession(ctx.message.author)
data = await Api.getSession('/user/unit/{0}'.format(term), session)
except ApiError as error:
return await self.handleApiError(ctx, error)
await ctx.send('Do you want to remove \'{0}\'? Yes to confirm'.format(data['name']))
def check(m):
return m.channel == ctx.channel and m.author == ctx.message.author
msg = ''
try:
msg = await self.bot.wait_for('message', timeout=30.0, check=check)
except asyncio.TimeoutError:
return await ctx.send('Operation aborted')
try:
session = await self.bot.getUserSession(ctx.message.author)
await Api.deleteSession('/user/unit/{0}'.format(data['id']), session)
return await ctx.send('Unit Assignment Removed')
except ApiError as error:
return await self.handleApiError(ctx, error);
@commands.command()
async def modUnit(self, ctx, term:str, *, params:param_converter=param_converter.defaults()):
data = None
try:
session = await self.bot.getUserSession(ctx.message.author)
data = await Api.getSession('/user/unit/{0}'.format(term), session)
except ApiError as error:
return await self.handleApiError(ctx, error)
await ctx.send('Do you want to modify \'{0}\'? Yes to confirm'.format(data['name']))
def check(m):
return m.channel == ctx.channel and m.author == ctx.message.author
msg = ''
try:
msg = await self.bot.wait_for('message', timeout=30.0, check=check)
except asyncio.TimeoutError:
return await ctx.send('Operation aborted')
try:
session = await self.bot.getUserSession(ctx.message.author)
body = params
await Api.putSession('/user/unit/{0}'.format(data['id']), body, session)
return await ctx.send('Unit Modified Successfully')
except ApiError as error:
return await self.handleApiError(ctx, error);
@commands.command()
async def assignUnit(self, ctx, term:str, *, params:param_converter=param_converter.defaults()):
data = None
try:
data = await Api.get('/unit/{0}'.format(term))
except ApiError as error:
return await self.handleApiError(ctx, error)
await ctx.send('Do you want to assign \'{0}\'? Yes to confirm'.format(data['name']))
def check(m):
return m.channel == ctx.channel and m.author == ctx.message.author
msg = ''
try:
msg = await self.bot.wait_for('message', timeout=30.0, check=check)
except asyncio.TimeoutError:
return await ctx.send('Operation aborted')
try:
session = await self.bot.getUserSession(ctx.message.author)
body = params
body['unit_id'] = data['id']
print(body)
await Api.postSession('/user/unit', body, session)
return await ctx.send('Unit Assigned Successfully')
except ApiError as error:
return await self.handleApiError(ctx, error);
@commands.command()
async def myUnits(self, ctx):
try:
session = await self.bot.getUserSession(ctx.message.author)
data = await Api.getSession('/user/units', session)
units_data = []
for item in data['units']:
units_data.append(Unit(**item))
return await EmbedStyle.createPages(self.bot, ctx, units_data, 5, self.createUnitPage)
except ApiError as error:
return await self.handleApiError(ctx, error)
def createUnitPage(self, data, minI, maxI):
embed = discord.Embed(color=0x19212d)
embed.set_author(name='TestBot')
units_str = '';
for i in range(minI, maxI):
if i < len(data):
unit = data[i]
units_str+= '[{0}] {1}\t| {2}\t| {3}\t| {4}\n'.format(unit.id, unit.name, unit.type, unit.stars, unit.unit_level)
embed.add_field(name='id\t| name\t| type\t| stars\t| level', value=units_str)
return embed
from discord_argparse import *
param_converter = ArgumentConverter(
unit_level = OptionalArgument(
str,
doc='Level of Unit',
),
eltie_flg = OptionalArgument(
bool,
doc='Is Unit Elite',
)
)
\ No newline at end of file
...@@ -6,6 +6,7 @@ from settings import API_URL_BASE ...@@ -6,6 +6,7 @@ from settings import API_URL_BASE
class ApiError(Exception): class ApiError(Exception):
def __init__(self, error_code:int, message:str=None): def __init__(self, error_code:int, message:str=None):
self.error_code = error_code self.error_code = error_code
self.message = message self.message = message
...@@ -38,27 +39,38 @@ class Api: ...@@ -38,27 +39,38 @@ class Api:
return Api.status_code_handling(response) return Api.status_code_handling(response)
@staticmethod
async def delete(url, cookies=None):
req_url = API_URL_BASE + url
response = requests.delete(req_url, cookies=cookies)
return Api.status_code_handling(response)
@staticmethod @staticmethod
async def getWithUser(url, uid): async def getSession(url, session):
enc_id = Crypto.encrypt(str(uid)) req_url = API_URL_BASE + url
cookies = {'discord_id': enc_id} response = session.get(req_url)
print('ENC: ' + enc_id)
return await Api.get(url, cookies) return Api.status_code_handling(response)
@staticmethod @staticmethod
async def postWithUser(url, data, uid): async def postSession(url, data, session):
enc_id = Crypto.encrypt(str(uid)) req_url = API_URL_BASE + url
cookies = {'discord_id': enc_id} response = session.post(req_url, json=data)
print('ENC: ' + enc_id) return Api.status_code_handling(response)
return await Api.post(url, data, cookies)
@staticmethod
async def putSession(url, data, session):
req_url = API_URL_BASE + url
response = session.put(req_url, json=data)
return Api.status_code_handling(response)
@staticmethod @staticmethod
async def putWithUser(url, data, uid): async def deleteSession(url, session):
enc_id = Crypto.encrypt(str(uid)) req_url = API_URL_BASE + url
cookies = {'discord_id': enc_id} response = session.delete(req_url)
print('ENC: ' + enc_id)
return await Api.put(url, data, cookies) return Api.status_code_handling(response)
@staticmethod @staticmethod
...@@ -79,7 +91,6 @@ class Api: ...@@ -79,7 +91,6 @@ class Api:
elif response.status_code >= 400: elif response.status_code >= 400:
error_str = '[!] [{0}] Bad Request'.format(response.status_code); error_str = '[!] [{0}] Bad Request'.format(response.status_code);
print(error_str) print(error_str)
print(content)
raise ApiError(response.status_code, content) raise ApiError(response.status_code, content)
elif response.status_code >= 300: elif response.status_code >= 300:
error_str = '[!] [{0}] Unexpected redirect.'.format(response.status_code) error_str = '[!] [{0}] Unexpected redirect.'.format(response.status_code)
......
import traceback
import sys
from discord.ext import commands from discord.ext import commands
import discord_argparse.errors as da_errors import discord_argparse.errors as da_errors
...@@ -43,4 +45,8 @@ class CommandErrorHandler(commands.Cog): ...@@ -43,4 +45,8 @@ class CommandErrorHandler(commands.Cog):
if ctx.command.qualified_name == 'tag list': # Check if the command being invoked is 'tag list' if ctx.command.qualified_name == 'tag list': # Check if the command being invoked is 'tag list'
return await ctx.send('I could not find that member. Please try again.') return await ctx.send('I could not find that member. Please try again.')
# All other Errors not returned come here... And we can just print the default TraceBack.
print('Ignoring exception in command {}:'.format(ctx.command), file=sys.stderr)
traceback.print_exception(type(error), error, error.__traceback__, file=sys.stderr)
...@@ -7,33 +7,40 @@ from settings import ENC_DEC_MEDTHOD ...@@ -7,33 +7,40 @@ from settings import ENC_DEC_MEDTHOD
class Crypto: class Crypto:
"""Someday, I will implement encryption and decryption. Today is not that day"""
@staticmethod @staticmethod
def encrypt(str_to_enc): def encrypt(str_to_enc):
try:
aes_obj = AES.new(CRYPT_KEY, AES.MODE_CFB, CRYPT_SALT) # try:
hx_enc = aes_obj.encrypt(str_to_enc.encode('utf8')) # aes_obj = AES.new(CRYPT_KEY, AES.MODE_CFB, CRYPT_SALT)
mret = b64encode(hx_enc).decode(ENC_DEC_MEDTHOD) # hx_enc = aes_obj.encrypt(str_to_enc.encode('utf8'))
return mret # mret = b64encode(hx_enc).decode(ENC_DEC_MEDTHOD)
except ValueError as value_error: # return mret
if value_error.args[0] == 'IV must be 16 bytes long': # except ValueError as value_error:
raise ValueError('Encryption Error: SALT must be 16 characters long') # if value_error.args[0] == 'IV must be 16 bytes long':
elif value_error.args[0] == 'AES key must be either 16, 24, or 32 bytes long': # raise ValueError('Encryption Error: SALT must be 16 characters long')
raise ValueError('Encryption Error: Encryption key must be either 16, 24, or 32 characters long') # elif value_error.args[0] == 'AES key must be either 16, 24, or 32 bytes long':
else: # raise ValueError('Encryption Error: Encryption key must be either 16, 24, or 32 characters long')
raise ValueError(value_error) # else:
# raise ValueError(value_error)
return base64encode(str_to_enc.encode('utf8')).decode('utf-8')
@staticmethod @staticmethod
def decrypt(enc_str): def decrypt(enc_str):
try: # try:
aes_obj = AES.new(CRYPT_KEY.encode('utf8'), AES.MODE_CFB, CRYPT_SALT) # aes_obj = AES.new(CRYPT_KEY.encode('utf8'), AES.MODE_CFB, CRYPT_SALT)
str_tmp = b64decode(enc_str.encode(ENC_DEC_MEDTHOD)) # str_tmp = b64decode(enc_str.encode(ENC_DEC_MEDTHOD))
str_dec = aes_obj.decrypt(str_tmp) # str_dec = aes_obj.decrypt(str_tmp)
mret = str_dec.decode(ENC_DEC_MEDTHOD) # mret = str_dec.decode(ENC_DEC_MEDTHOD)
return mret # return mret
except ValueError as value_error: # except ValueError as value_error:
if value_error.args[0] == 'IV must be 16 bytes long': # if value_error.args[0] == 'IV must be 16 bytes long':
raise ValueError('Decryption Error: SALT must be 16 characters long') # raise ValueError('Decryption Error: SALT must be 16 characters long')
elif value_error.args[0] == 'AES key must be either 16, 24, or 32 bytes long': # elif value_error.args[0] == 'AES key must be either 16, 24, or 32 bytes long':
raise ValueError('Decryption Error: Encryption key must be either 16, 24, or 32 characters long') # raise ValueError('Decryption Error: Encryption key must be either 16, 24, or 32 characters long')
else: # else:
raise ValueError(value_error) # raise ValueError(value_error)
\ No newline at end of file
return base64decode(enc_str.encode('utf-8')).decode('utf8')
import discord
from asyncio import TimeoutError
class EmbedStyle:
@staticmethod
async def createPages(bot, ctx, data, max_rows, createRowsFunc):
max_pages = len(data) // max_rows
rem = len(data) % max_rows
cur_page = 0;
first_run = True
if rem!=0:
max_pages+=1
while True:
if first_run:
embed = createRowsFunc(data, max_rows*cur_page, max_rows*cur_page + max_rows)
first_run = False
msg = await ctx.send(embed=embed)
reactmoji = []
if max_pages == 1 and cur_page == 0:
pass
elif cur_page == 0:
reactmoji.append('⏩')
elif cur_page == max_pages-1:
reactmoji.append('⏪')
elif cur_page > 0 and cur_page < max_pages-1:
reactmoji.extend(['⏪', '⏩'])
for react in reactmoji:
await msg.add_reaction(react)
def check_react(reaction, user):
if reaction.message.id != msg.id:
return False
if user != ctx.message.author:
return False
if str(reaction.emoji) not in reactmoji:
return False
return True
try:
res, user = await bot.wait_for('reaction_add', timeout=30.0, check=check_react)
except TimeoutError:
return await msg.clear_reactions()
if '⏪' in str(res.emoji):
cur_page-= 1
embed = createRowsFunc(data, max_rows*cur_page, max_rows*cur_page + max_rows)
await msg.clear_reactions()
await msg.edit(embed=embed)
if '⏩' in str(res.emoji):
cur_page+= 1
embed = createRowsFunc(data, max_rows*cur_page, max_rows*cur_page + max_rows)
await msg.clear_reactions()
await msg.edit(embed=embed)
\ No newline at end of file
'use strict'; 'use strict';
const fs = require('fs');
const MySQL = require('promise-mysql'); const MySQL = require('promise-mysql');
const ENV = require('../settings');
let config_file = fs.readFileSync('./database/config.json');
let db_config = JSON.parse(config_file);
const db = {} const db = {}
let connection = MySQL.createConnection(db_config); let connection = MySQL.createConnection({
host: ENV.DB_HOST,
port: ENV.DB_PORT,
user: ENV.DB_USER,
password: ENV.DB_PASS,
database: ENV.DB_NAME
});
connection.then((con) =>{ connection.then((con) =>{
console.log('Database Connected'); console.log('Database Connected');
......
...@@ -3,15 +3,22 @@ ...@@ -3,15 +3,22 @@
const Koa = require('koa'); const Koa = require('koa');
const Router = require('@koa/router'); const Router = require('@koa/router');
const bodyParser = require('koa-body'); const bodyParser = require('koa-body');
const session = require('koa-session')
const auth = require('./util/auth'); const userRouter = require('./user/route');
const unitsRouter = require('./unit/route'); const unitRouter = require('./unit/route');
const SESS_CONFIG = require('./session_config');
const ENV = require('./settings')
const app = new Koa(); const app = new Koa();
const router = new Router(); const router = new Router();
router.use('/unit', unitsRouter.routes(), unitsRouter.allowedMethods()); app.keys = [ENV.SESS_KEY]
app.use(bodyParser()); app.use(bodyParser());
app.use(session(SESS_CONFIG, app));
router.use('/unit', unitRouter.routes(), unitRouter.allowedMethods());
router.use('/user', userRouter.routes(), userRouter.allowedMethods());
//app.use(auth()); //app.use(auth());
app.use(router.routes()).use(router.allowedMethods()); app.use(router.routes()).use(router.allowedMethods());
......
...@@ -62,6 +62,11 @@ ...@@ -62,6 +62,11 @@
"resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
"integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=" "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8="
}, },
"base64-js": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
"integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g=="
},
"bignumber.js": { "bignumber.js": {
"version": "9.0.0", "version": "9.0.0",
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz", "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz",
...@@ -72,6 +77,15 @@ ...@@ -72,6 +77,15 @@
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
"integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="
}, },
"buffer": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz",
"integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==",
"requires": {
"base64-js": "^1.0.2",
"ieee754": "^1.1.4"
}
},
"bytes": { "bytes": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
...@@ -136,6 +150,14 @@ ...@@ -136,6 +150,14 @@
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
}, },
"crc": {
"version": "3.8.0",
"resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz",
"integrity": "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==",
"requires": {
"buffer": "^5.1.0"
}
},
"debug": { "debug": {
"version": "4.1.1", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
...@@ -233,6 +255,11 @@ ...@@ -233,6 +255,11 @@
"safer-buffer": ">= 2.1.2 < 3" "safer-buffer": ">= 2.1.2 < 3"
} }
}, },
"ieee754": {
"version": "1.1.13",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",
"integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg=="
},
"inflation": { "inflation": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/inflation/-/inflation-2.0.0.tgz",