Comparing version 1.7.1 to 1.7.2
@@ -1,2 +0,2 @@ | ||
import { Audio, ContentTypes, Document, IAnswerCallbackQueryOptions, ICopyMessageOptions, IDeleteWebhookConfig, IFile, IForwardMessageOptions, IMessage, IMessageId, InputSupportedMedia, IOptions, ISendAnimationOptions, ISendAudioOptions, ISendDocumentOptions, ISendMediaGroupOptions, ISendPhotoOptions, ISendVideoNoteOptions, ISendVideoOptions, ISendVoiceOptions, IUser, IWebhookConfig, Keyboard, MessageCreator, Photo, Video, Voice } from '..'; | ||
import { Audio, ContentTypes, Document, IAnswerCallbackQueryOptions, ICopyMessageOptions, IDeleteWebhookConfig, IFile, IForwardMessageOptions, IMessage, IMessageId, InputSupportedMedia, IOptions, ISendAnimationOptions, ISendAudioOptions, ISendDocumentOptions, ISendLocationOptions, ISendMediaGroupOptions, ISendPhotoOptions, ISendVideoNoteOptions, ISendVideoOptions, ISendVoiceOptions, IUser, IWebhookConfig, Keyboard, MessageCreator, Photo, Video, Voice, IStopMessageLiveLocationOptions } from '..'; | ||
import { Animation, VideoNote } from './Media'; | ||
@@ -24,2 +24,3 @@ export declare class Api { | ||
sendMediaGroup(chatId: string | number, mediaGroup: InputSupportedMedia[], moreOptions?: ISendMediaGroupOptions): Promise<IMessage[]>; | ||
sendLocation(chatId: number | string, latitude: number, longitude: number, moreOptions?: ISendLocationOptions): Promise<IMessage>; | ||
answerCallbackQuery(callback_query_id: string, moreOptions?: IAnswerCallbackQueryOptions): Promise<boolean>; | ||
@@ -31,2 +32,3 @@ alert(callback_query_id: string, text: string, moreOptions?: IAnswerCallbackQueryOptions): Promise<boolean>; | ||
copy(msgId: number, fromChatId: number | string, toChatId: number | string, keyboard?: Keyboard | null, moreOptions?: ICopyMessageOptions): Promise<IMessageId>; | ||
stopLiveLocation(chatId: number | string | null, msgId: number | null, keyboard?: Keyboard | null, moreOptions?: IStopMessageLiveLocationOptions): Promise<IMessage | true>; | ||
} |
@@ -95,2 +95,5 @@ "use strict"; | ||
} | ||
else if (content instanceof __1.Location) { | ||
return this.sendLocation(chatId, content.latitude, content.longitude, moreOptions); | ||
} | ||
} | ||
@@ -229,2 +232,10 @@ if (content instanceof Media_1.Media) { | ||
} | ||
sendLocation(chatId, latitude, longitude, moreOptions = {}) { | ||
return this.callApi('sendLocation', { | ||
chat_id: chatId, | ||
latitude, | ||
longitude, | ||
...moreOptions, | ||
}); | ||
} | ||
answerCallbackQuery(callback_query_id, moreOptions = {}) { | ||
@@ -263,4 +274,13 @@ return this.callApi('answerCallbackQuery', { | ||
} | ||
stopLiveLocation(chatId, msgId, keyboard = null, moreOptions = {}) { | ||
if (keyboard) | ||
moreOptions.reply_markup = keyboard.buildMarkup(); | ||
return this.callApi('stopMessageLiveLocation', { | ||
chat_id: chatId, | ||
message_id: msgId, | ||
...moreOptions, | ||
}); | ||
} | ||
} | ||
exports.Api = Api; | ||
//# sourceMappingURL=Api.js.map |
@@ -1,2 +0,2 @@ | ||
import { ISendOptions, IMessage, IUpdate, ContentTypes, Keyboard, IAnswerCallbackQueryOptions, IFile, IForwardMessageOptions, ICopyMessageOptions } from '../..'; | ||
import { ISendOptions, IMessage, IUpdate, ContentTypes, Keyboard, IAnswerCallbackQueryOptions, IFile, IForwardMessageOptions, ICopyMessageOptions, IStopMessageLiveLocationOptions } from '../..'; | ||
import { MessageCreator } from '../Message'; | ||
@@ -10,2 +10,3 @@ import { Api } from '../Api'; | ||
send(content: MessageCreator | ContentTypes, keyboard?: Keyboard | null, moreOptions?: ISendOptions): Promise<IMessage | IMessage[]>; | ||
stopLiveLocation(keyboard?: Keyboard | null, moreOptions?: IStopMessageLiveLocationOptions): Promise<IMessage | true>; | ||
alert(text: string, moreOptions?: IAnswerCallbackQueryOptions): Promise<boolean>; | ||
@@ -12,0 +13,0 @@ toast(text: string, moreOptions?: IAnswerCallbackQueryOptions): Promise<boolean>; |
@@ -21,2 +21,11 @@ "use strict"; | ||
} | ||
stopLiveLocation(keyboard = null, moreOptions = {}) { | ||
const chatId = Filter_1.Filter.getChatId(this.update); | ||
if (!chatId) | ||
throw (0, logger_1.error)(`Can't find chatId from update`); | ||
const msgId = Filter_1.Filter.getMsgId(this.update); | ||
if (!msgId) | ||
throw (0, logger_1.error)(`Can't find msgId from update`); | ||
return this.api.stopLiveLocation(chatId, msgId, keyboard, moreOptions); | ||
} | ||
alert(text, moreOptions = {}) { | ||
@@ -23,0 +32,0 @@ const queryId = Filter_1.Filter.getCallbackQueryId(this.update); |
import { IUpdate } from '../../types'; | ||
export declare class FileLogger { | ||
private readonly limit; | ||
nestgramInfoDirPath: string; | ||
logsFilePath: string; | ||
constructor(); | ||
constructor(limit: number); | ||
private setupLogsFile; | ||
saveLog(update: IUpdate): Promise<void>; | ||
} | ||
export declare const fileLogger: FileLogger; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.fileLogger = exports.FileLogger = void 0; | ||
exports.FileLogger = void 0; | ||
const fs = require("fs/promises"); | ||
const path = require("path"); | ||
class FileLogger { | ||
constructor() { | ||
constructor(limit) { | ||
this.limit = limit; | ||
this.nestgramInfoDirPath = path.resolve(process.cwd(), 'nestgram'); | ||
@@ -30,6 +31,10 @@ this.logsFilePath = path.resolve(this.nestgramInfoDirPath, 'logs.md'); | ||
const updateText = JSON.stringify(update, null, 2); | ||
const titleLines = oldLogsFileText | ||
.split('\n') | ||
.filter((line) => line.startsWith('#')); | ||
let newLogsFileText; | ||
newLogsFileText = `# ${update.update_id} (${date})`; | ||
newLogsFileText += `\n\n${updateText}`; | ||
newLogsFileText += `\n\n${oldLogsFileText || ''}`; | ||
if (titleLines.length < this.limit) | ||
newLogsFileText += `\n\n${oldLogsFileText || ''}`; | ||
await fs.writeFile(this.logsFilePath, newLogsFileText); | ||
@@ -39,3 +44,2 @@ } | ||
exports.FileLogger = FileLogger; | ||
exports.fileLogger = new FileLogger(); | ||
//# sourceMappingURL=FileLogger.js.map |
@@ -6,2 +6,3 @@ import { MiddlewareFunction, MessageEntityTypes, MediaFileTypes } from '../..'; | ||
static forward(): MiddlewareFunction; | ||
static location(): MiddlewareFunction; | ||
static command(commandText?: string): MiddlewareFunction; | ||
@@ -8,0 +9,0 @@ static text(text?: string): MiddlewareFunction; |
@@ -20,2 +20,11 @@ "use strict"; | ||
} | ||
static location() { | ||
return function use(update, answer, params, next, fail) { | ||
if (!update.message) | ||
return fail(); | ||
else if (!update.message.location) | ||
return fail(); | ||
next(); | ||
}; | ||
} | ||
static command(commandText) { | ||
@@ -22,0 +31,0 @@ return function use(update, answer, params, next, fail) { |
@@ -1,2 +0,2 @@ | ||
import { KeyboardTypes, IButton, ReplyMarkup } from '../..'; | ||
import { KeyboardTypes, IButton, IReplyMarkup } from '../..'; | ||
export declare class Keyboard<T = any> { | ||
@@ -20,3 +20,3 @@ readonly keyboardType: KeyboardTypes; | ||
use(layoutName: string): Keyboard; | ||
buildMarkup(): ReplyMarkup; | ||
buildMarkup(): IReplyMarkup; | ||
} |
import { IHandler, IUpdate } from '../../types'; | ||
import { FileLogger } from '../Helpers/FileLogger'; | ||
export declare class Handler { | ||
@@ -6,3 +7,6 @@ private readonly token; | ||
private readonly logging?; | ||
constructor(token: string, handlers: IHandler[], logging?: true); | ||
private readonly fileLogging?; | ||
private readonly fileLoggingLimit?; | ||
fileLogger: FileLogger; | ||
constructor(token: string, handlers: IHandler[], logging?: boolean, fileLogging?: boolean, fileLoggingLimit?: number); | ||
private getNextFunction; | ||
@@ -9,0 +13,0 @@ private handleMiddleware; |
@@ -7,9 +7,12 @@ "use strict"; | ||
const Message_1 = require("../Message"); | ||
const FileLogger_1 = require("../Helpers/FileLogger"); | ||
const logger_1 = require("../../logger"); | ||
const FileLogger_1 = require("../Helpers/FileLogger"); | ||
class Handler { | ||
constructor(token, handlers, logging) { | ||
constructor(token, handlers, logging, fileLogging, fileLoggingLimit) { | ||
this.token = token; | ||
this.handlers = handlers; | ||
this.logging = logging; | ||
this.fileLogging = fileLogging; | ||
this.fileLoggingLimit = fileLoggingLimit; | ||
this.fileLogger = new FileLogger_1.FileLogger(this.fileLoggingLimit); | ||
} | ||
@@ -103,3 +106,4 @@ getNextFunction(update, answer, params, middlewares, middlewareIndex, handlerIndex, handler, failFunction) { | ||
(0, logger_1.info)('Got new update!', `(${update.update_id})`.grey); | ||
FileLogger_1.fileLogger.saveLog(update); | ||
if (this.fileLogging) | ||
this.fileLogger.saveLog(update); | ||
} | ||
@@ -106,0 +110,0 @@ const answer = new Answer_1.Answer(this.token, update); |
@@ -9,7 +9,9 @@ import { IPollingConfig, IHandler } from '../../types'; | ||
private readonly logging?; | ||
private readonly fileLogging?; | ||
private readonly fileLoggingLimit?; | ||
api: Api; | ||
handler: Handler; | ||
constructor(token: string, handlers: IHandler[], config?: IPollingConfig | null, logging?: true); | ||
constructor(token: string, handlers: IHandler[], config?: IPollingConfig | null, logging?: boolean, fileLogging?: boolean, fileLoggingLimit?: number); | ||
start(): Promise<void>; | ||
private updateGetter; | ||
} |
@@ -8,3 +8,3 @@ "use strict"; | ||
class Polling { | ||
constructor(token, handlers, config, logging) { | ||
constructor(token, handlers, config, logging, fileLogging, fileLoggingLimit) { | ||
this.token = token; | ||
@@ -14,4 +14,6 @@ this.handlers = handlers; | ||
this.logging = logging; | ||
this.fileLogging = fileLogging; | ||
this.fileLoggingLimit = fileLoggingLimit; | ||
this.api = new Api_1.Api(this.token); | ||
this.handler = new Handler_1.Handler(this.token, this.handlers, this.logging); | ||
this.handler = new Handler_1.Handler(this.token, this.handlers, this.logging, this.fileLogging, this.fileLoggingLimit); | ||
if (!this.token) | ||
@@ -18,0 +20,0 @@ throw (0, logger_1.error)(`You can't run bot without token`); |
@@ -11,6 +11,8 @@ /// <reference types="node" /> | ||
private readonly logging?; | ||
private readonly fileLogging?; | ||
private readonly fileLoggingLimit?; | ||
api: Api; | ||
handler: Handler; | ||
server: http.Server; | ||
constructor(token: string, handlers: IHandler[], config?: IWebhookConfig | null, logging?: true); | ||
constructor(token: string, handlers: IHandler[], config?: IWebhookConfig | null, logging?: boolean, fileLogging?: boolean, fileLoggingLimit?: number); | ||
} |
@@ -9,3 +9,3 @@ "use strict"; | ||
class Webhook { | ||
constructor(token, handlers, config, logging) { | ||
constructor(token, handlers, config, logging, fileLogging, fileLoggingLimit) { | ||
this.token = token; | ||
@@ -15,4 +15,6 @@ this.handlers = handlers; | ||
this.logging = logging; | ||
this.fileLogging = fileLogging; | ||
this.fileLoggingLimit = fileLoggingLimit; | ||
this.api = new Api_1.Api(this.token); | ||
this.handler = new Handler_1.Handler(this.token, this.handlers, this.logging); | ||
this.handler = new Handler_1.Handler(this.token, this.handlers, this.logging, this.fileLogging, this.fileLoggingLimit); | ||
if (!this.token) | ||
@@ -19,0 +21,0 @@ throw (0, logger_1.error)(`You can't run bot without token`); |
@@ -5,3 +5,4 @@ export * from './MessageCreator'; | ||
export * from './Toast'; | ||
export * from './Location'; | ||
export * from './Forward'; | ||
export * from './Copy'; |
@@ -21,4 +21,5 @@ "use strict"; | ||
__exportStar(require("./Toast"), exports); | ||
__exportStar(require("./Location"), exports); | ||
__exportStar(require("./Forward"), exports); | ||
__exportStar(require("./Copy"), exports); | ||
//# sourceMappingURL=index.js.map |
@@ -20,3 +20,4 @@ import { MediaFileTypes, MessageEntityTypes } from '../../types'; | ||
export declare const OnDocument: () => MethodDecorator; | ||
export declare const OnLocation: () => MethodDecorator; | ||
export declare const OnUpdate: () => MethodDecorator; | ||
export declare const OnForward: () => MethodDecorator; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.OnForward = exports.OnUpdate = exports.OnDocument = exports.OnAnimation = exports.OnVoice = exports.OnVideoNote = exports.OnAudio = exports.OnVideo = exports.OnPhoto = exports.OnMedia = exports.OnClick = exports.OnEntity = exports.OnPostEdit = exports.OnPost = exports.OnMessageEdit = exports.OnMessage = exports.OnText = exports.OnCommand = exports.buildUpdateDecorator = void 0; | ||
exports.OnForward = exports.OnUpdate = exports.OnLocation = exports.OnDocument = exports.OnAnimation = exports.OnVoice = exports.OnVideoNote = exports.OnAudio = exports.OnVideo = exports.OnPhoto = exports.OnMedia = exports.OnClick = exports.OnEntity = exports.OnPostEdit = exports.OnPost = exports.OnMessageEdit = exports.OnMessage = exports.OnText = exports.OnCommand = exports.buildUpdateDecorator = void 0; | ||
const setup_arguments_1 = require("./setup-arguments"); | ||
@@ -47,2 +47,4 @@ const middleware_decorator_1 = require("./middleware.decorator"); | ||
exports.OnDocument = OnDocument; | ||
const OnLocation = () => buildUpdateDecorator('location'); | ||
exports.OnLocation = OnLocation; | ||
const OnUpdate = () => buildUpdateDecorator('update'); | ||
@@ -49,0 +51,0 @@ exports.OnUpdate = OnUpdate; |
@@ -10,3 +10,9 @@ "use strict"; | ||
class NestGram { | ||
constructor(token, module, config, runConfig = { port: 80, runType: 'polling', logging: true }) { | ||
constructor(token, module, config, runConfig = { | ||
port: 80, | ||
runType: 'polling', | ||
logging: true, | ||
fileLogging: true, | ||
fileLoggingLimit: 20, | ||
}) { | ||
this.token = token; | ||
@@ -24,4 +30,8 @@ this.module = module; | ||
runConfig.runType = 'polling'; | ||
if (!runConfig.fileLoggingLimit) | ||
runConfig.fileLoggingLimit = 20; | ||
if (typeof runConfig.logging !== 'boolean') | ||
runConfig.logging = true; | ||
if (typeof runConfig.fileLogging !== 'boolean') | ||
runConfig.fileLogging = true; | ||
if (module) | ||
@@ -95,3 +105,3 @@ this.setupEntry(module); | ||
await this.api.deleteWebhook(this.runConfig); | ||
this.polling = new Polling_1.Polling(this.token, this.handlers, this.config, this.runConfig.logging); | ||
this.polling = new Polling_1.Polling(this.token, this.handlers, this.config, this.runConfig.logging, this.runConfig.fileLogging, this.runConfig.fileLoggingLimit); | ||
this.polling.start(); | ||
@@ -102,3 +112,3 @@ } | ||
throw (0, logger_1.error)('If you want to use webhooks, you need to pass webhook url in config'); | ||
this.webhook = new Webhook_1.Webhook(this.token, this.handlers, this.config, this.runConfig.logging); | ||
this.webhook = new Webhook_1.Webhook(this.token, this.handlers, this.config, this.runConfig.logging, this.runConfig.fileLogging, this.runConfig.fileLoggingLimit); | ||
} | ||
@@ -105,0 +115,0 @@ (0, logger_1.success)('Bot started on', `@${this.info.username}`.gray); |
import { IMessageEntity } from './update.types'; | ||
import { ReplyMarkup } from './keyboard.types'; | ||
import { IInlineKeyboard, IReplyMarkup } from './keyboard.types'; | ||
import { Thumb } from '../classes'; | ||
@@ -11,5 +11,3 @@ import { InputMediaTypes } from './media.types'; | ||
} | ||
export interface ISendOptions { | ||
parse_mode?: ParseModes; | ||
entities?: IMessageEntity[]; | ||
export interface IDefaultOptions { | ||
disable_notification?: boolean; | ||
@@ -19,4 +17,8 @@ protect_content?: boolean; | ||
allow_sending_without_reply?: boolean; | ||
reply_markup?: ReplyMarkup; | ||
reply_markup?: IReplyMarkup; | ||
} | ||
export interface ISendOptions extends IDefaultOptions { | ||
parse_mode?: ParseModes; | ||
entities?: IMessageEntity[]; | ||
} | ||
export interface ISendMediaGroupFetchOptions extends ISendMediaGroupOptions { | ||
@@ -26,8 +28,2 @@ chat_id: number | string; | ||
} | ||
export interface ISendMediaGroupOptions { | ||
disable_notification?: boolean; | ||
protect_content?: boolean; | ||
reply_to_message_id?: number; | ||
allow_sending_without_reply?: boolean; | ||
} | ||
export interface ISendPhotoFetchOptions extends ISendPhotoOptions { | ||
@@ -59,4 +55,2 @@ chat_id: number | string; | ||
} | ||
export interface ISendDocumentOptions extends ISendPhotoOptions { | ||
} | ||
export interface ISendDocumentFetchOptions extends ISendDocumentOptions { | ||
@@ -125,3 +119,3 @@ chat_id: number | string; | ||
allow_sending_without_reply?: boolean; | ||
reply_markup?: ReplyMarkup; | ||
reply_markup?: IReplyMarkup; | ||
} | ||
@@ -135,1 +129,24 @@ export interface ICopyMessageFetchOptions extends ICopyMessageOptions, IMessageId { | ||
} | ||
export interface ISendLocationFetchOptions extends ISendLocationOptions { | ||
chat_id: number | string; | ||
latitude: number; | ||
longitude: number; | ||
} | ||
export interface ISendLocationOptions extends IDefaultOptions { | ||
horizontal_accuracy?: number; | ||
live_period?: number; | ||
heading?: number; | ||
proximity_alert_radius?: number; | ||
} | ||
export interface IStopMessageLiveLocationFetchOptions { | ||
chat_id?: number | string; | ||
message_id?: number; | ||
} | ||
export interface IStopMessageLiveLocationOptions { | ||
inline_message_id?: number; | ||
reply_markup?: IInlineKeyboard; | ||
} | ||
export interface ISendMediaGroupOptions extends IDefaultOptions { | ||
} | ||
export interface ISendDocumentOptions extends ISendPhotoOptions { | ||
} |
@@ -30,4 +30,6 @@ export declare type RunTypes = 'polling' | 'webhook'; | ||
runType?: RunTypes; | ||
logging?: true; | ||
logging?: boolean; | ||
port?: number; | ||
fileLogging?: boolean; | ||
fileLoggingLimit?: number; | ||
} | ||
@@ -34,0 +36,0 @@ export interface IDeleteWebhookConfig extends IGlobalWebhookConfig { |
import { ControllerClass } from './decorators.types'; | ||
import { MiddlewareFunction } from './middleware.types'; | ||
import { IMessage, IMessageEntity, IUpdate } from './update.types'; | ||
import { Answer, Media, MessageCreator } from '../classes'; | ||
import { Answer, Location, Media, MessageCreator } from '../classes'; | ||
import { IUser } from './chat.types'; | ||
@@ -22,3 +22,3 @@ export interface IHandler { | ||
]; | ||
export declare type ContentTypes = Media | string | undefined | null; | ||
export declare type ContentTypes = Media | Location | string | undefined | null; | ||
export declare type HandlerMethod = ((...args: ArgsTypes) => MessageCreator | ContentTypes) & { | ||
@@ -25,0 +25,0 @@ prototype: { |
import { KeyboardTypes } from '../enums'; | ||
export interface ReplyMarkup { | ||
export interface IReplyMarkup extends IInlineKeyboard, IKeyboard, IPlaceholder { | ||
} | ||
export interface IInlineKeyboard { | ||
inline_keyboard?: IButton[][]; | ||
} | ||
export interface IKeyboard { | ||
keyboard?: IButton[][]; | ||
} | ||
export interface IPlaceholder { | ||
placeholder?: string; | ||
@@ -6,0 +12,0 @@ } |
import { MediaFileTypes } from './media.types'; | ||
export declare type SendTypes = 'send' | 'alert' | 'toast' | 'forward' | 'copy'; | ||
export declare type MessageCreatorTypes = MediaFileTypes | 'text'; | ||
export declare type SendTypes = 'send' | 'alert' | 'toast' | 'forward' | 'copy' | 'location'; | ||
export declare type MessageCreatorTypes = MediaFileTypes | 'text' | 'location'; |
import { IChat, IUser } from './chat.types'; | ||
import { ReplyMarkup } from './keyboard.types'; | ||
import { IReplyMarkup } from './keyboard.types'; | ||
export declare type MessageEntityTypes = 'mention' | 'hashtag' | 'cashtag' | 'bot_command' | 'url' | 'email' | 'phone_number' | 'bold' | 'italic' | 'underline' | 'strikethrough' | 'spoiler' | 'code' | 'pre' | 'text_link' | 'text_mention'; | ||
@@ -68,3 +68,3 @@ export interface IUpdate { | ||
venue?: any; | ||
location?: any; | ||
location?: ILocation; | ||
new_chat_members?: IUser[]; | ||
@@ -92,3 +92,3 @@ left_chat_member?: IUser; | ||
web_app_data?: any; | ||
reply_markup?: ReplyMarkup; | ||
reply_markup?: IReplyMarkup; | ||
} | ||
@@ -138,2 +138,10 @@ interface IDefaultFileOptions { | ||
} | ||
export interface ILocation { | ||
longitude: number; | ||
latitude: number; | ||
horizontal_accuracy?: number; | ||
live_period?: number; | ||
heading?: number; | ||
proximity_alert_radius?: number; | ||
} | ||
export {}; |
{ | ||
"name": "nestgram", | ||
"description": "Framework for working with Telegram Bot API on TypeScript like Nest.js", | ||
"version": "1.7.1", | ||
"version": "1.7.2", | ||
"main": "dist/index.js", | ||
@@ -6,0 +6,0 @@ "types": "dist/index.d.ts", |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
190106
180
2610