diff --git a/package-lock.json b/package-lock.json index 368aee4..6d144ca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,6 @@ "@types/uuid": "^9.0.0", "body-parser": "^1.20.2", "clone-deep": "^4.0.1", - "cron": "^3.1.7", "discord.js": "^14.3.0", "dotenv": "^16.0.0", "express": "^4.18.2", @@ -748,15 +747,10 @@ } }, "node_modules/@discordjs/collection": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.0.tgz", - "integrity": "sha512-mLcTACtXUuVgutoznkh6hS3UFqYirDYAg5Dc1m8xn6OvPjetnUlf/xjtqnnc47OwWdaoCQnHmHh9KofhD6uRqw==", + "version": "2.0.0", "license": "Apache-2.0", "engines": { "node": ">=18" - }, - "funding": { - "url": "https://github.com/discordjs/discord.js?sponsor" } }, "node_modules/@discordjs/formatters": { @@ -770,54 +764,28 @@ } }, "node_modules/@discordjs/rest": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.3.0.tgz", - "integrity": "sha512-C1kAJK8aSYRv3ZwMG8cvrrW4GN0g5eMdP8AuN8ODH5DyOCbHgJspze1my3xHOAgwLJdKUbWNVyAeJ9cEdduqIg==", + "version": "2.2.0", "license": "Apache-2.0", "dependencies": { - "@discordjs/collection": "^2.1.0", - "@discordjs/util": "^1.1.0", - "@sapphire/async-queue": "^1.5.2", - "@sapphire/snowflake": "^3.5.3", - "@vladfrangu/async_event_emitter": "^2.2.4", - "discord-api-types": "0.37.83", - "magic-bytes.js": "^1.10.0", + "@discordjs/collection": "^2.0.0", + "@discordjs/util": "^1.0.2", + "@sapphire/async-queue": "^1.5.0", + "@sapphire/snowflake": "^3.5.1", + "@vladfrangu/async_event_emitter": "^2.2.2", + "discord-api-types": "0.37.61", + "magic-bytes.js": "^1.5.0", "tslib": "^2.6.2", - "undici": "6.13.0" + "undici": "5.27.2" }, "engines": { "node": ">=16.11.0" - }, - "funding": { - "url": "https://github.com/discordjs/discord.js?sponsor" } }, - "node_modules/@discordjs/rest/node_modules/@sapphire/snowflake": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.3.tgz", - "integrity": "sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ==", - "license": "MIT", - "engines": { - "node": ">=v14.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/@discordjs/rest/node_modules/discord-api-types": { - "version": "0.37.83", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.83.tgz", - "integrity": "sha512-urGGYeWtWNYMKnYlZnOnDHm8fVRffQs3U0SpE8RHeiuLKb/u92APS8HoQnPTFbnXmY1vVnXjXO4dOxcAn3J+DA==", - "license": "MIT" - }, "node_modules/@discordjs/util": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@discordjs/util/-/util-1.1.0.tgz", - "integrity": "sha512-IndcI5hzlNZ7GS96RV3Xw1R2kaDuXEp7tRIy/KlhidpN/BQ1qh1NZt3377dMLTa44xDUNKT7hnXkA/oUAzD/lg==", + "version": "1.0.2", "license": "Apache-2.0", "engines": { "node": ">=16.11.0" - }, - "funding": { - "url": "https://github.com/discordjs/discord.js?sponsor" } }, "node_modules/@discordjs/ws": { @@ -1705,9 +1673,7 @@ } }, "node_modules/@sapphire/async-queue": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.2.tgz", - "integrity": "sha512-7X7FFAA4DngXUl95+hYbUF19bp1LGiffjJtu7ygrZrbdCSsdDDBaSjB7Akw0ZbOu6k0xpXyljnJ6/RZUvLfRdg==", + "version": "1.5.0", "license": "MIT", "engines": { "node": ">=v14.0.0", @@ -1929,20 +1895,14 @@ "@types/node": "*" } }, - "node_modules/@types/luxon": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.4.2.tgz", - "integrity": "sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA==" - }, "node_modules/@types/mime": { "version": "3.0.4", "license": "MIT" }, "node_modules/@types/node": { - "version": "20.12.12", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.12.tgz", - "integrity": "sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==", - "license": "MIT", + "version": "20.12.11", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.11.tgz", + "integrity": "sha512-vDg9PZ/zi+Nqp6boSOT7plNuthRugEKixDv5sFTIpkE89MmNtEArAShI4mxuX2+UrLEe9pxC1vm2cjm9YlWbJw==", "dependencies": { "undici-types": "~5.26.4" } @@ -3881,15 +3841,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/cron": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/cron/-/cron-3.1.7.tgz", - "integrity": "sha512-tlBg7ARsAMQLzgwqVxy8AZl/qlTc5nibqYwtNGoCrd+cV+ugI+tvZC1oT/8dFH8W455YrywGykx/KMmAqOr7Jw==", - "dependencies": { - "@types/luxon": "~3.4.0", - "luxon": "~3.4.0" - } - }, "node_modules/cross-spawn": { "version": "7.0.3", "license": "MIT", @@ -7749,18 +7700,8 @@ "node": ">=10" } }, - "node_modules/luxon": { - "version": "3.4.4", - "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.4.tgz", - "integrity": "sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==", - "engines": { - "node": ">=12" - } - }, "node_modules/magic-bytes.js": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.10.0.tgz", - "integrity": "sha512-/k20Lg2q8LE5xiaaSkMXk4sfvI+9EGEykFS4b0CHHGWqDYU0bGUFSwchNOMA56D7TCs9GwVTkqe9als1/ns8UQ==", + "version": "1.6.0", "license": "MIT" }, "node_modules/make-dir": { diff --git a/package.json b/package.json index 004cfb8..178f288 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,6 @@ "@types/uuid": "^9.0.0", "body-parser": "^1.20.2", "clone-deep": "^4.0.1", - "cron": "^3.1.7", "discord.js": "^14.3.0", "dotenv": "^16.0.0", "express": "^4.18.2", diff --git a/src/bot.ts b/src/bot.ts index e5e25a3..87008c5 100644 --- a/src/bot.ts +++ b/src/bot.ts @@ -37,6 +37,7 @@ const client = new CoreClient([ ]); Registry.RegisterCommands(); +Registry.RegisterEvents(); Registry.RegisterButtonEvents(); if (!existsSync(`${process.env.DATA_DIR}/cards`) && process.env.GDRIVESYNC_AUTO && process.env.GDRIVESYNC_AUTO == "true") { diff --git a/src/buttonEvents/Sacrifice.ts b/src/buttonEvents/Sacrifice.ts deleted file mode 100644 index 6c4a1a6..0000000 --- a/src/buttonEvents/Sacrifice.ts +++ /dev/null @@ -1,147 +0,0 @@ -import { ActionRowBuilder, ButtonBuilder, ButtonInteraction, ButtonStyle, EmbedBuilder } from "discord.js"; -import { ButtonEvent } from "../type/buttonEvent"; -import Inventory from "../database/entities/app/Inventory"; -import CardDropHelperMetadata from "../helpers/CardDropHelperMetadata"; -import { CardRarityToString, GetSacrificeAmount } from "../constants/CardRarity"; -import EmbedColours from "../constants/EmbedColours"; -import User from "../database/entities/app/User"; - -export default class Sacrifice extends ButtonEvent { - public override async execute(interaction: ButtonInteraction) { - const subcommand = interaction.customId.split(" ")[1]; - - switch(subcommand) { - case "confirm": - await this.confirm(interaction); - break; - case "cancel": - await this.cancel(interaction); - break; - } - } - - private async confirm(interaction: ButtonInteraction) { - const userId = interaction.customId.split(" ")[2]; - const cardNumber = interaction.customId.split(" ")[3]; - - const cardInInventory = await Inventory.FetchOneByCardNumberAndUserId(userId, cardNumber); - - if (!cardInInventory) { - await interaction.reply("Unable to find card in inventory."); - return; - } - - const cardData = CardDropHelperMetadata.GetCardByCardNumber(cardNumber); - - if (!cardData) { - await interaction.reply("Unable to find card in the database."); - return; - } - - const user = await User.FetchOneById(User, userId); - - if (!user) { - await interaction.reply("Unable to find user in database."); - return; - } - - cardInInventory.RemoveQuantity(1); - - await cardInInventory.Save(Inventory, cardInInventory); - - const cardValue = GetSacrificeAmount(cardData.card.type); - const cardRarityString = CardRarityToString(cardData.card.type); - - user.AddCurrency(cardValue); - - await user.Save(User, user); - - const description = [ - `Card: ${cardData.card.name}`, - `Series: ${cardData.series.name}`, - `Rarity: ${cardRarityString}`, - `Quantity Owned: ${cardInInventory.Quantity}`, - `Sacrifice Amount: ${cardValue}`, - ]; - - const embed = new EmbedBuilder() - .setTitle("Card Sacrificed") - .setDescription(description.join("\n")) - .setColor(EmbedColours.Ok) - .setFooter({ text: `${interaction.user.username} · ${cardData.card.name}` }); - - const row = new ActionRowBuilder() - .addComponents([ - new ButtonBuilder() - .setCustomId(`sacrifice confirm ${interaction.user.id} ${cardNumber}`) - .setLabel("Confirm") - .setStyle(ButtonStyle.Success) - .setDisabled(true), - new ButtonBuilder() - .setCustomId("sacrifice cancel") - .setLabel("Cancel") - .setStyle(ButtonStyle.Secondary) - .setDisabled(true), - ]); - - await interaction.update({ - embeds: [ embed ], - components: [ row ], - }); - } - - private async cancel(interaction: ButtonInteraction) { - const userId = interaction.customId.split(" ")[2]; - const cardNumber = interaction.customId.split(" ")[3]; - - const cardInInventory = await Inventory.FetchOneByCardNumberAndUserId(userId, cardNumber); - - if (!cardInInventory) { - await interaction.reply("Unable to find card in inventory."); - return; - } - - const cardData = CardDropHelperMetadata.GetCardByCardNumber(cardNumber); - - if (!cardData) { - await interaction.reply("Unable to find card in the database."); - return; - } - - const cardValue = GetSacrificeAmount(cardData.card.type); - const cardRarityString = CardRarityToString(cardData.card.type); - - const description = [ - `Card: ${cardData.card.name}`, - `Series: ${cardData.series.name}`, - `Rarity: ${cardRarityString}`, - `Quantity Owned: ${cardInInventory.Quantity}`, - `Sacrifice Amount: ${cardValue}`, - ]; - - const embed = new EmbedBuilder() - .setTitle("Sacrifice Cancelled") - .setDescription(description.join("\n")) - .setColor(EmbedColours.Error) - .setFooter({ text: `${interaction.user.username} · ${cardData.card.name}` }); - - const row = new ActionRowBuilder() - .addComponents([ - new ButtonBuilder() - .setCustomId(`sacrifice confirm ${interaction.user.id} ${cardNumber}`) - .setLabel("Confirm") - .setStyle(ButtonStyle.Success) - .setDisabled(true), - new ButtonBuilder() - .setCustomId("sacrifice cancel") - .setLabel("Cancel") - .setStyle(ButtonStyle.Secondary) - .setDisabled(true), - ]); - - await interaction.update({ - embeds: [ embed ], - components: [ row ], - }); - } -} \ No newline at end of file diff --git a/src/client/client.ts b/src/client/client.ts index 2dd9a29..c694950 100644 --- a/src/client/client.ts +++ b/src/client/client.ts @@ -14,8 +14,6 @@ import Webhooks from "../webhooks"; import CardMetadataFunction from "../Functions/CardMetadataFunction"; import { SeriesMetadata } from "../contracts/SeriesMetadata"; import AppLogger from "./appLogger"; -import TimerHelper from "../helpers/TimerHelper"; -import GiveCurrency from "../timers/GiveCurrency"; export class CoreClient extends Client { private static _commandItems: ICommandItem[]; @@ -25,7 +23,6 @@ export class CoreClient extends Client { private _events: Events; private _util: Util; private _webhooks: Webhooks; - private _timerHelper: TimerHelper; public static ClaimId: string; public static Environment: Environment; @@ -62,7 +59,6 @@ export class CoreClient extends Client { this._events = new Events(); this._util = new Util(); this._webhooks = new Webhooks(); - this._timerHelper = new TimerHelper(); AppLogger.LogInfo("Client", `Environment: ${CoreClient.Environment}`); @@ -76,12 +72,7 @@ export class CoreClient extends Client { } await AppDataSource.initialize() - .then(() => { - AppLogger.LogInfo("Client", "App Data Source Initialised"); - - const timerId = this._timerHelper.AddTimer("*/30 * * * * *", "Europe/London", GiveCurrency, false); - this._timerHelper.StartTimer(timerId); - }) + .then(() => AppLogger.LogInfo("Client", "App Data Source Initialised")) .catch(err => { AppLogger.LogError("Client", "App Data Source Initialisation Failed"); AppLogger.LogError("Client", err); diff --git a/src/commands/sacrifice.ts b/src/commands/sacrifice.ts deleted file mode 100644 index c6dc7b3..0000000 --- a/src/commands/sacrifice.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { ActionRowBuilder, ButtonBuilder, ButtonStyle, CacheType, CommandInteraction, EmbedBuilder, SlashCommandBuilder } from "discord.js"; -import { Command } from "../type/command"; -import Inventory from "../database/entities/app/Inventory"; -import { CardRarityToString, GetSacrificeAmount } from "../constants/CardRarity"; -import CardDropHelperMetadata from "../helpers/CardDropHelperMetadata"; -import EmbedColours from "../constants/EmbedColours"; - -export default class Sacrifice extends Command { - constructor() { - super(); - - this.CommandBuilder = new SlashCommandBuilder() - .setName("sacrifice") - .setDescription("Sacrifices a card for currency") - .addStringOption(x => - x - .setName("cardnumber") - .setDescription("The card to sacrifice from your inventory") - .setRequired(true)); - } - - public override async execute(interaction: CommandInteraction): Promise { - const cardnumber = interaction.options.get("cardnumber", true); - - const cardInInventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, cardnumber.value! as string); - - if (!cardInInventory || cardInInventory.Quantity == 0) { - await interaction.reply("Unable to find card in your inventory."); - return; - } - - const cardData = CardDropHelperMetadata.GetCardByCardNumber(cardnumber.value! as string); - - if (!cardData) { - await interaction.reply("Unable to find card in the database."); - return; - } - - const cardValue = GetSacrificeAmount(cardData.card.type); - const cardRarityString = CardRarityToString(cardData.card.type); - - const description = [ - `Card: ${cardData.card.name}`, - `Series: ${cardData.series.name}`, - `Rarity: ${cardRarityString}`, - `Quantity Owned: ${cardInInventory.Quantity}`, - `Sacrifice Amount: ${cardValue}`, - ]; - - const embed = new EmbedBuilder() - .setTitle("Sacrifice") - .setDescription(description.join("\n")) - .setColor(EmbedColours.Grey) - .setFooter({ text: `${interaction.user.username} · ${cardData.card.name}` }); - - const row = new ActionRowBuilder() - .addComponents([ - new ButtonBuilder() - .setCustomId(`sacrifice confirm ${interaction.user.id} ${cardnumber.value!}`) - .setLabel("Confirm") - .setStyle(ButtonStyle.Success), - new ButtonBuilder() - .setCustomId(`sacrifice cancel ${interaction.user.id} ${cardnumber.value!}`) - .setLabel("Cancel") - .setStyle(ButtonStyle.Secondary), - ]); - - await interaction.reply({ - embeds: [ embed ], - components: [ row ], - }); - } -} \ No newline at end of file diff --git a/src/constants/CardRarity.ts b/src/constants/CardRarity.ts index 82ecee1..8202a12 100644 --- a/src/constants/CardRarity.ts +++ b/src/constants/CardRarity.ts @@ -58,21 +58,4 @@ export function CardRarityParse(rarity: string): CardRarity { default: return CardRarity.Unknown; } -} - -export function GetSacrificeAmount(rarity: CardRarity): number { - switch (rarity) { - case CardRarity.Bronze: - return 5; - case CardRarity.Silver: - return 15; - case CardRarity.Gold: - return 30; - case CardRarity.Manga: - return 50; - case CardRarity.Legendary: - return 100; - default: - return 0; - } } \ No newline at end of file diff --git a/src/contracts/AppBaseEntity.ts b/src/contracts/AppBaseEntity.ts index 3eb9684..b9ca565 100644 --- a/src/contracts/AppBaseEntity.ts +++ b/src/contracts/AppBaseEntity.ts @@ -27,12 +27,6 @@ export default class AppBaseEntity { await repository.save(entity); } - public static async SaveAll(target: EntityTarget, entities: DeepPartial[]): Promise { - const repository = AppDataSource.getRepository(target); - - await repository.save(entities); - } - public static async Remove(target: EntityTarget, entity: T): Promise { const repository = AppDataSource.getRepository(target); diff --git a/src/database/entities/app/Inventory.ts b/src/database/entities/app/Inventory.ts index a5d0026..bde4450 100644 --- a/src/database/entities/app/Inventory.ts +++ b/src/database/entities/app/Inventory.ts @@ -29,12 +29,6 @@ export default class Inventory extends AppBaseEntity { this.Quantity = quantity; } - public RemoveQuantity(amount: number) { - if (this.Quantity < amount) return; - - this.Quantity -= amount; - } - public AddClaim(claim: Claim) { this.Claims.push(claim); } diff --git a/src/database/entities/app/User.ts b/src/database/entities/app/User.ts index c3d8437..0823886 100644 --- a/src/database/entities/app/User.ts +++ b/src/database/entities/app/User.ts @@ -16,6 +16,10 @@ export default class User extends AppBaseEntity { @Column() LastUsedDaily?: Date; + public UpdateCurrency(currency: number) { + this.Currency = currency; + } + public AddCurrency(amount: number) { this.Currency += amount; } diff --git a/src/helpers/TimerHelper.ts b/src/helpers/TimerHelper.ts deleted file mode 100644 index 535a6a7..0000000 --- a/src/helpers/TimerHelper.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { CronJob } from "cron"; -import { v4 } from "uuid"; -import { Primitive } from "../type/primitive"; - -interface Timer { - id: string; - job: CronJob; - context: Map; - onTick: ((context: Map) => void) | ((context: Map) => Promise); - runOnStart: boolean; -} - -export default class TimerHelper { - private _timers: Timer[]; - - constructor() { - this._timers = []; - } - - public AddTimer( - cronTime: string, - timeZone: string, - onTick: ((context: Map) => void) | ((context: Map) => Promise), - runOnStart: boolean = false): string { - const context = new Map(); - - const job = new CronJob( - cronTime, - () => { - onTick(context); - }, - null, - false, - timeZone, - ); - - const id = v4(); - - this._timers.push({ - id, - job, - context, - onTick, - runOnStart, - }); - - return id; - } - - public StartAllTimers() { - this._timers.forEach(timer => this.StartJob(timer)); - } - - public StopAllTimers() { - this._timers.forEach(timer => timer.job.stop()); - } - - public StartTimer(id: string) { - const timer = this._timers.find(x => x.id == id); - - if (!timer) return; - - this.StartJob(timer); - } - - public StopTimer(id: string) { - const timer = this._timers.find(x => x.id == id); - - if (!timer) return; - - timer.job.stop(); - } - - private StartJob(timer: Timer) { - timer.job.start(); - - if (timer.runOnStart) { - timer.onTick(timer.context); - } - } -} \ No newline at end of file diff --git a/src/registry.ts b/src/registry.ts index dc2770d..f51df33 100644 --- a/src/registry.ts +++ b/src/registry.ts @@ -9,7 +9,6 @@ import Gdrivesync from "./commands/gdrivesync"; import Give from "./commands/give"; import Inventory from "./commands/inventory"; import Resync from "./commands/resync"; -import Sacrifice from "./commands/sacrifice"; import Series from "./commands/series"; import Trade from "./commands/trade"; import View from "./commands/view"; @@ -22,7 +21,6 @@ import Droprarity from "./commands/stage/droprarity"; import Claim from "./buttonEvents/Claim"; import InventoryButtonEvent from "./buttonEvents/Inventory"; import Reroll from "./buttonEvents/Reroll"; -import SacrificeButtonEvent from "./buttonEvents/Sacrifice"; import SeriesEvent from "./buttonEvents/Series"; import TradeButtonEvent from "./buttonEvents/Trade"; @@ -36,7 +34,6 @@ export default class Registry { CoreClient.RegisterCommand("give", new Give()); CoreClient.RegisterCommand("inventory", new Inventory()); CoreClient.RegisterCommand("resync", new Resync()); - CoreClient.RegisterCommand("sacrifice", new Sacrifice()); CoreClient.RegisterCommand("series", new Series()); CoreClient.RegisterCommand("trade", new Trade()); CoreClient.RegisterCommand("view", new View()); @@ -46,11 +43,14 @@ export default class Registry { CoreClient.RegisterCommand("droprarity", new Droprarity(), Environment.Test); } + public static RegisterEvents() { + + } + public static RegisterButtonEvents() { CoreClient.RegisterButtonEvent("claim", new Claim()); CoreClient.RegisterButtonEvent("inventory", new InventoryButtonEvent()); CoreClient.RegisterButtonEvent("reroll", new Reroll()); - CoreClient.RegisterButtonEvent("sacrifice", new SacrificeButtonEvent()); CoreClient.RegisterButtonEvent("series", new SeriesEvent()); CoreClient.RegisterButtonEvent("trade", new TradeButtonEvent()); } diff --git a/src/timers/GiveCurrency.ts b/src/timers/GiveCurrency.ts deleted file mode 100644 index ad1a21a..0000000 --- a/src/timers/GiveCurrency.ts +++ /dev/null @@ -1,16 +0,0 @@ -import AppLogger from "../client/appLogger"; -import User from "../database/entities/app/User"; - -export default async function GiveCurrency() { - AppLogger.LogInfo("Timers/GiveCurrency", "Giving currency to every known user"); - - const users = await User.FetchAll(User); - - for (const user of users) { - user.AddCurrency(5); - } - - User.SaveAll(User, users); - - AppLogger.LogInfo("Timers/GiveCurrency", `Successfully gave +5 currency to ${users.length} users`); -} \ No newline at end of file diff --git a/src/type/primitive.ts b/src/type/primitive.ts deleted file mode 100644 index b1e143b..0000000 --- a/src/type/primitive.ts +++ /dev/null @@ -1 +0,0 @@ -export type Primitive = string | number | boolean; \ No newline at end of file