рдлреАрд╕ рдХреЗ рд▓рд┐рдП рдмреАрдУрдЯреАред рдирдИ рддрдХрдиреАрдХреЛрдВ рдХреЗ рд╕рд╛рде рдлреБрдЯрдмреЙрд▓ рдореЗрдВ рдЬрд╛рдирд╛

рдкрд░рд┐рдЪрдп


рд╕рднреА рдХреЛ рдирдорд╕реНрдХрд╛рд░ред рдЗрд╕ рд▓реЗрдЦ рдореЗрдВ рдореИрдВ NodeJS рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддреЗ рд╣реБрдП рдЯреЗрд▓реАрдЧреНрд░рд╛рдо рдореИрд╕реЗрдЬрд┐рдВрдЧ рд╕реЗрд╡рд╛ рдФрд░ рд╡реАрдХреЗ рд╕реЛрд╢рд▓ рдиреЗрдЯрд╡рд░реНрдХ рдХреЗ рд▓рд┐рдП рдЕрдкрдиреЗ рдЪреИрдЯрдмреЙрдЯ рдХрд╛ рд╡рд░реНрдгрди рдХрд░реВрдВрдЧрд╛ред


рдЗрд╕ рдмрд┐рдВрджреБ рдкрд░, рдХрдИ рдкрд╛рдардХреЛрдВ рдХреЛ рдХреБрдЫ рддреЛрдбрд╝рдирд╛ рдЪрд╛рд╣рд┐рдП рдЬреИрд╕реЗ: "рдХрдм рддрдХ!" рдпрд╛ "рдХреНрдпрд╛, рдлрд┐рд░ рд╕реЗ!"
рд╣рд╛рдБ, рдЗрд╕реА рддрд░рд╣ рдХреЗ рдкреНрд░рдХрд╛рд╢рди рдкрд╣рд▓реЗ рд╕реЗ рд╣реА рдПрдХ рд╣рдм рдкрд░ рдереЗред рд▓реЗрдХрд┐рди, рдлрд┐рд░ рднреА, рдореБрдЭреЗ рд╡рд┐рд╢реНрд╡рд╛рд╕ рд╣реИ рдХрд┐ рд▓реЗрдЦ рдЙрдкрдпреЛрдЧреА рд╣реЛрдЧрд╛ред рд╕рдВрдХреНрд╖реЗрдк рдореЗрдВ рдмреЙрдЯ рдХреЗ рддрдХрдиреАрдХреА рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рдХрд╛ рдкреНрд░рддрд┐рдирд┐рдзрд┐рддреНрд╡ рдХрд░рддрд╛ рд╣реИ:


  1. рдЕрдиреБрдкреНрд░рдпреЛрдЧ рдХреЗ рд▓рд┐рдП рдПрдХ рд░реВрдкрд░реЗрдЦрд╛ рдХреЗ рд░реВрдк рдореЗрдВ, NestJS рд░реВрдкрд░реЗрдЦрд╛ рд▓реЛрдХрдкреНрд░рд┐рдпрддрд╛ рдкреНрд░рд╛рдкреНрдд рдХрд░ рд░рд╣реА рд╣реИред
  2. рдЯреЗрд▓реАрдЧреНрд░рд╛рдо рдПрдкреАрдЖрдИ рдХреЗ рд╕рд╛рде рдмрд╛рддрдЪреАрдд рдХреЗ рд▓рд┐рдП рдЯреЗрд▓реАрдЧреНрд░рд╛рдл рдкреБрд╕реНрддрдХрд╛рд▓рдпред
  3. рд╡реАрдХреЗ рдПрдкреАрдЖрдИ рдХреЗ рд╕рд╛рде рдмрд╛рддрдЪреАрдд рдХреЗ рд▓рд┐рдП рдиреЛрдб-рд╡реАрдХреЗ-рдмреЙрдЯ-рдПрдкреА рд▓рд╛рдЗрдмреНрд░реЗрд░реАред
  4. рдбреЗрдЯрд╛ рд╕рдВрдЧреНрд░рд╣рдг рдкрд░рдд рдХреЛ рд╡реНрдпрд╡рд╕реНрдерд┐рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рд▓рд╛рдЗрдмреНрд░реЗрд░реА рдЯрд╛рдЗрдк рдХрд░реЗрдВ ред
  5. рдореЛрдЪрд╛ рдФрд░ рдЪрд╛рдИ рдкреБрд╕реНрддрдХрд╛рд▓рдп рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддреЗ рд╣реБрдП рдкрд░реАрдХреНрд╖рдгред
  6. рдкрд░реАрдХреНрд╖рдг рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдЯреНрд░реИрд╡рд┐рд╕ рд╕реАрдЖрдИ рдФрд░ рдбреЙрдХ рдЫрд╡рд┐рдпреЛрдВ рдХреЛ рддреИрдирд╛рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдЧрд┐рдЯрд╣рдм рдХреНрд░рд┐рдпрд╛рдУрдВ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддреЗ рд╣реБрдП рд╕реАрдЖрдИред

рдПрдХ рд╕рд╛рдЗрдб рдЬреЙрдм рдХреЗ рд░реВрдк рдореЗрдВ, рд╣рдо рдЕрдкрдиреЗ рдмреЙрдЯ рджреЛрд╕реНрддреЛрдВ рдХреЛ рд╡рд╛рдЗрдмрд░ рдХреЗ рд╕рд╛рде рдмрдирд╛рдиреЗ рдХреА рдХреЛрд╢рд┐рд╢ рдХрд░рддреЗ рд╣реИрдВ, рдЬрд┐рд╕рд╕реЗ рдпрд╣ рдХрдИ рдореИрд╕реЗрдЬрд┐рдВрдЧ рд╕реЗрд╡рд╛рдУрдВ рдореЗрдВ рдЙрдкрдпреЛрдЧ рдХреЗ рд▓рд┐рдП рдЗрддрдирд╛ рд╕рд╛рд░реНрд╡рднреМрдорд┐рдХ рд╣реИред


рдЙрди рд▓реЛрдЧреЛрдВ рдХреЗ рд▓рд┐рдП рдЬреЛ рдпрд╣ рдЬрд╛рдирдирд╛ рдЪрд╛рд╣рддреЗ рд╣реИрдВ рдХрд┐ рдмрд┐рд▓реНрд▓реА рдХрд╛ рдХреНрдпрд╛ рд╕реНрд╡рд╛рдЧрдд рд╣реИред


рд╕рдорд╕реНрдпрд╛ рдХрд╛ рдмрдпрд╛рди


рдПрдХ рдЙрдкрдпреЛрдЧреА рд╢рд╛рд░реАрд░рд┐рдХ рдЧрддрд┐рд╡рд┐рдзрд┐ рдХреЗ рд░реВрдк рдореЗрдВ, рдореБрдЭреЗ рдлреБрдЯрдмреЙрд▓ рдкрд╕рдВрдж рд╣реИред рдПрдХ рд╢реМрдХрд┐рдпрд╛ рд╕реНрддрд░ рдкрд░, рдмрд┐рд▓реНрдХреБрд▓ред Vk, viber рдФрд░ telegram рдореЗрдВ рдХрдИ рдЪреИрдирд▓реЛрдВ рдФрд░ рдЪреИрдЯ рдХреЗ рд╕рджрд╕реНрдп рд╣реЛрдиреЗ рдХреЗ рдирд╛рддреЗ, рдЖрдкрдХреЛ рдЕрдХреНрд╕рд░ рдирд┐рдореНрдирд▓рд┐рдЦрд┐рдд рдЪрд┐рддреНрд░ рджреЗрдЦрдирд╛ рд╣реЛрдЧрд╛:


рдЫрд╡рд┐


рд╣рдо рдпрд╣рд╛рдБ рдХреНрдпрд╛ рджреЗрдЦ рд░рд╣реЗ рд╣реИрдВ:


  1. "рдкреА" рдиреЗ рдЦреБрдж рдХреЛ рдЬреЛрдбрд╝рд╛ред
  2. рдЙрдирдХреЗ рдмрд╛рдж, 3 рдФрд░ рд▓реЛрдЧреЛрдВ рдХреЛ рдЬреЛрдбрд╝рд╛ рдЧрдпрд╛, рдЬрд┐рдирдХреЗ рдмреАрдЪ рдПрдХ рдирд┐рд╢реНрдЪрд┐рдд "рд╕реА" рдерд╛ред
  3. "рдкреА" рдиреЗ рдЕрдкрдиреЗ рд╕рд╛рдереА рдХреЛ "рдбрд┐рдореЛрди" рдирд╛рдо рджрд┐рдпрд╛ред
  4. рдЙрд╕рдХреЗ рдмрд╛рдж, "рдкреА" рдмрд╣реБрдд рдЖрд▓рд╕реА рдирд╣реАрдВ рдерд╛ рдФрд░ рдЧрдгрдирд╛ рдХреА рдЧрдИ рдХрд┐ рдЗрд╕ рд╕рдордп рдкрд╣рд▓реЗ рд╕реЗ рд╣реА 5 рд▓реЛрдЧреЛрдВ рдХреЗ рд░реВрдк рдореЗрдВ рд╣реИрдВред
  5. рд╣рд╛рд▓рд╛рдВрдХрд┐, рдпрд╣ рдЬрд╛рдирдХрд╛рд░реА рд▓рдВрдмреЗ рд╕рдордп рддрдХ рдкреНрд░рд╛рд╕рдВрдЧрд┐рдХ рдирд╣реАрдВ рдереА, рдХреНрдпреЛрдВрдХрд┐ рдореИрдВрдиреЗ рднреА рджреМрдбрд╝рдиреЗ рдХрд╛ рдлреИрд╕рд▓рд╛ рдХрд┐рдпрд╛ рдФрд░ рдЦреБрдж рдХреЛ рдЬреЛрдбрд╝рд╛ред

рд╡рд┐рд╢реЗрд╖ рд░реВрдк рд╕реЗ рдЙрдкреЗрдХреНрд╖рд┐рдд рдорд╛рдорд▓реЛрдВ рдореЗрдВ, рд▓реЛрдЧ "+" рдбрд╛рд▓ рд╕рдХрддреЗ рд╣реИрдВ рдФрд░ рдлрд┐рд░ рдЕрдкрдиреА рднрд╛рдЧреАрджрд╛рд░реА рдХреЛ рд░рджреНрдж рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ, рджреЛрд╕реНрддреЛрдВ рдХреЛ рдЪреИрдЯ рд╕реЗ рдирд╣реАрдВ рдмреБрд▓рд╛ рд╕рдХрддреЗ рд╣реИрдВ, рдпрд╛ рдЕрдиреНрдп рдЪреИрдЯ рдкреНрд░рддрд┐рднрд╛рдЧрд┐рдпреЛрдВ рдХреЗ рд▓рд┐рдП рд╕рд╛рдЗрди рдЕрдк рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ рдФрд░ рдЕрдиреНрдп рдЧрддрд┐рд╡рд┐рдзрд┐рдпрд╛рдВ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВред рдЕрдВрдд рдореЗрдВ, рдпрд╣ рдЗрд╕ рддрдереНрдп рдХреА рдУрд░ рдЬрд╛рддрд╛ рд╣реИ рдХрд┐ рдЬрдм рд╕рднреА рдЕрдВрддрддрдГ рдЦреЗрд▓рдиреЗ рдХреЗ рд▓рд┐рдП рдЖрддреЗ рд╣реИрдВ, рддреЛ рдпрд╣ рдкрддрд╛ рдЪрд▓рд╛ рд╣реИ рдХрд┐:


  1. рд╡рд╛рд╕реНрдпрд╛ рдиреЗ +1 рдХрд╛ рдЕрд░реНрде рди рдХреЗрд╡рд▓ рдЦреБрдж, рдмрд▓реНрдХрд┐ рдЕрдкрдиреЗ рджреЛрд╕реНрдд рдЧреЛрд╢ рд╕реЗ рднреА рд▓рдЧрд╛рдпрд╛ред
  2. рдХреЛрд▓реНрдпрд╛ рдиреЗ рд▓рд┐рдЦрд╛ рд╣реИ рдХрд┐ рд╡рд╣ рдЖрдПрдВрдЧреЗ, рд▓реЗрдХрд┐рди рдЖрдпреЛрдЬрдХ рд╕реНрдкрд┐рд░рд┐рдбрди рдиреЗ рдЕрдкрдиреА рдЖрдВрдЦреЛрдВ рдХреЗ рд╕рд╛рде рдХреЗрд╡рд▓ рдкреНрд▓рд╕рд╕ рдкрд░ рд╡рд┐рдЪрд╛рд░ рдХрд┐рдпрд╛ред

рдкрд░рд┐рдгрд╛рдорд╕реНрд╡рд░реВрдк, рд╣рдорд╛рд░реЗ рдкрд╛рд╕ рд╕рдВрднрд╡рддрдГ "рдЕрддрд┐рд░рд┐рдХреНрдд рдЧреЛрд╢" рдЯреАрдореЛрдВ рдХреА рд╕рдВрдЦреНрдпрд╛ рдореЗрдВ рдЕрд╕рдорд╛рди рд╣реИ, рдФрд░ рдЕрдкреНрд░рддреНрдпрд╛рд╢рд┐рдд рд░реВрдк рд╕реЗ рдХреЛрд▓реНрдпрд╛ рджрд┐рдЦрд╛рдИ рджрд┐рдпрд╛ред


рдЗрд╕ рддрд░рд╣ рдХреЗ рдЯрдХрд░рд╛рд╡реЛрдВ рд╕реЗ рдмрдЪрдиреЗ рдХреЗ рд▓рд┐рдП, рдореИрдВрдиреЗ рдПрдХ рд╕рд╛рдзрд╛рд░рдг рдмреЙрдЯ рд▓рд┐рдЦрд╛ рд╣реИ рдЬреЛ рдЖрдЧрд╛рдореА рдЧреЗрдо рдХреЗ рдкреНрд░рддрд┐рднрд╛рдЧрд┐рдпреЛрдВ рдХреА рд░рдЪрдирд╛ рдХреЛ рдиреЗрддреНрд░рд╣реАрди рд░реВрдк рд╕реЗ рдкреНрд░рджрд░реНрд╢рд┐рдд рдХрд░рдиреЗ рдореЗрдВ рдорджрдж рдХрд░рддрд╛ рд╣реИ рдФрд░ рдЗрд╕реЗ рдЬреЛрдбрд╝рдиреЗ (рдУрдВ) / рд╣рдЯрд╛рдиреЗ рдХреЗ рд▓рд┐рдП рд╕реБрд╡рд┐рдзрд╛рдЬрдирдХ рд╣реИред


рдЗрд╕рд▓рд┐рдП, рдореЗрд░реЗ рдореВрд▓ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рдореЗрдВ, рдореЗрд░реА рд░рд╛рдп рдореЗрдВ, рдмреЙрдЯ рдХреЛ рд╕рдХреНрд╖рдо рд╣реЛрдирд╛ рдЪрд╛рд╣рд┐рдП:


  1. рдХрд┐рд╕реА рднреА рд╕рдВрдЦреНрдпрд╛ рдореЗрдВ рдЪреИрдЯ рдореЗрдВ рдПрдореНрдмреЗрдб рдХрд░реЗрдВред рдкреНрд░рддреНрдпреЗрдХ рдЪреИрдЯ рдХреЗ рд▓рд┐рдП рдЕрдкрдиреЗ рд░рд╛рдЬреНрдп рдХреЛ рдЕрд▓рдЧ рд╕реЗ рд╕реНрдЯреЛрд░ рдХрд░реЗрдВред
  2. рдХрдорд╛рдВрдб / Event_add рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ, рдХреБрдЫ рдирд┐рд╢реНрдЪрд┐рдд рдЬрд╛рдирдХрд╛рд░реА рдФрд░ рдЦрд┐рд▓рд╛рдбрд╝рд┐рдпреЛрдВ рдХреА рдПрдХ рдЦрд╛рд▓реА рд╕реВрдЪреА рдХреЗ рд╕рд╛рде рдПрдХ рдирдпрд╛ рд╕рдХреНрд░рд┐рдп рдИрд╡реЗрдВрдЯ рдмрдирд╛рдПрдВред рдкрд┐рдЫрд▓реА рд╕рдХреНрд░рд┐рдп рдШрдЯрдирд╛ рдирд┐рд╖реНрдХреНрд░рд┐рдп рд╣реЛ рдЬрд╛рдиреА рдЪрд╛рд╣рд┐рдПред
  3. / Event_remove рдХрдорд╛рдВрдб рдХреЛ рд╡рд░реНрддрдорд╛рди рдореЗрдВ рд╕рдХреНрд░рд┐рдп рдЗрд╡реЗрдВрдЯ рдХреЛ рд░рджреНрдж рдХрд░рдирд╛ рдЪрд╛рд╣рд┐рдПред
  4. рд╡рд░реНрддрдорд╛рди рдореЗрдВ рд╕рдХреНрд░рд┐рдп рдИрд╡реЗрдВрдЯ рдореЗрдВ / рдПрдб рдХрдорд╛рдВрдб рдХреЛ рдПрдХ рдирдпрд╛ рд╕рджрд╕реНрдп рдЬреЛрдбрд╝рдирд╛ рдЪрд╛рд╣рд┐рдПред рдЗрд╕рдХреЗ рдЕрд▓рд╛рд╡рд╛, рдЕрдЧрд░ рдХрдорд╛рдВрдб рдХреЛ рдмрд┐рдирд╛ рдХрд┐рд╕реА рдЕрддрд┐рд░рд┐рдХреНрдд рдкрд╛рда рдХреЗ рдмреБрд▓рд╛рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ, рддреЛ рдХрдорд╛рдВрдб рдЯрд╛рдЗрдк рдХрд░рдиреЗ рд╡рд╛рд▓реЗ рдХреЛ рдЬреЛрдбрд╝рд╛ рдЬрд╛рдирд╛ рдЪрд╛рд╣рд┐рдПред рдЕрдиреНрдпрдерд╛, рдПрдХ рд╡реНрдпрдХреНрддрд┐ рдЬреЛрдбрд╝рд╛ рдЬрд╛рддрд╛ рд╣реИ рдЬрд┐рд╕рдХрд╛ рдирд╛рдо рдирд┐рд░реНрджрд┐рд╖реНрдЯ рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ рдЬрдм рдХрдорд╛рдВрдб рдХрд╣рд╛ рдЬрд╛рддрд╛ рд╣реИред
  5. / рдРрдб рдХрдорд╛рдВрдб рдХреЗ рд▓рд┐рдП рд╡рд░реНрдгрд┐рдд рдирд┐рдпрдореЛрдВ рдХреЗ рдЕрдиреБрд╕рд╛рд░ / рд░рд┐рдореВрд╡ рдХрдорд╛рдВрдб рдХрд┐рд╕реА рд╡реНрдпрдХреНрддрд┐ рдХреЛ рдкреНрд░рддрд┐рднрд╛рдЧрд┐рдпреЛрдВ рдХреА рд╕реВрдЪреА рд╕реЗ рд╣рдЯрд╛ рджреЗрддреА рд╣реИред
  6. / рд╕реВрдЪрдирд╛ рдЖрджреЗрд╢ рдЖрдкрдХреЛ рдпрд╣ рджреЗрдЦрдиреЗ рдХреА рдЕрдиреБрдорддрд┐ рджреЗрддрд╛ рд╣реИ рдХрд┐ рдЦреЗрд▓ рдХреЗ рд▓рд┐рдП рдХреМрди рдкрд╣рд▓реЗ рд╕реЗ рд╣реА рд╕рд╛рдЗрди рдЕрдк рдХрд░ рдЪреБрдХрд╛ рд╣реИред
  7. рдпрд╣ рдЕрддреНрдпрдзрд┐рдХ рд╡рд╛рдВрдЫрдиреАрдп рд╣реИ рдХрд┐ рдмреЙрдЯ рдЙрд╕реА рднрд╛рд╖рд╛ рдореЗрдВ рдкреНрд░рддрд┐рдХреНрд░рд┐рдпрд╛ рдХрд░рддрд╛ рд╣реИ рдЬреИрд╕реЗ рдЦрд┐рд▓рд╛рдбрд╝реА рдХреЗ рдХреЙрдиреНрдлрд╝рд┐рдЧрд░ (рдпрд╛ рдбрд┐рдлрд╝реЙрд▓реНрдЯ рд░реВрдк рд╕реЗ рдЕрдВрдЧреНрд░реЗрдЬреА рдореЗрдВ)ред

рдпрд╣ рджреЗрдЦрдиреЗ рдХреЗ рд▓рд┐рдП рдХрд┐ рдЕрдВрдд рдореЗрдВ рдХреНрдпрд╛ рд╣реБрдЖ рдерд╛, рдЖрдк рддреБрд░рдВрдд рдЕрдВрддрд┐рдо рдЦрдВрдб "рдкрд░рд┐рдгрд╛рдо рдФрд░ рдирд┐рд╖реНрдХрд░реНрд╖" рдкрд░ рдЬрд╛ рд╕рдХрддреЗ рд╣реИрдВред рдареАрдХ рд╣реИ, рдпрджрд┐ рдЖрдк рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рд╡рд┐рд╡рд░рдг рдореЗрдВ рд░реБрдЪрд┐ рд░рдЦрддреЗ рд╣реИрдВ, рддреЛ рдпрд╣ рдЬрд╛рдирдХрд╛рд░реА рдиреАрдЪреЗ рдкрд░реНрдпрд╛рдкреНрдд рд╡рд┐рд╡рд░рдг рдореЗрдВ рд╡рд░реНрдгрд┐рдд рд╣реИред


рдбрд┐рдЬрд╛рдЗрди рдФрд░ рд╡рд┐рдХрд╛рд╕


рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдбрд┐рдЬрд╛рдЗрди рдХрд░рддреЗ рд╕рдордп, рдирд┐рдореНрдирд▓рд┐рдЦрд┐рдд рдпреЛрдЬрдирд╛ рдореЗрд░реЗ рд╕рд┐рд░ рдореЗрдВ рджрд┐рдЦрд╛рдИ рджреА:


рдЫрд╡рд┐


рдпрд╣рд╛рдВ, рдореБрдЦреНрдп рд╡рд┐рдЪрд╛рд░ рд╡рд┐рднрд┐рдиреНрди рдореИрд╕реЗрдЬрд┐рдВрдЧ рд╕рд┐рд╕реНрдЯрдо рдХреЗ рд╕рд╛рде рдХрд╛рдо рдХрд░рдиреЗ рдХреА рд╕рднреА рдмрд╛рд░реАрдХрд┐рдпреЛрдВ рдХреЛ рдЙрдкрдпреБрдХреНрдд рдПрдбреЗрдкреНрдЯрд░ рдореЗрдВ рд▓рд╛рдирд╛ рдФрд░ рд╕рдВрдмрдВрдзрд┐рдд рдПрдкреАрдЖрдИ рдХреЛ рд▓рд╛рдЧреВ рдХрд░рдиреЗ рд╡рд╛рд▓реЗ рдкреБрд╕реНрддрдХрд╛рд▓рдпреЛрдВ рдХреЗ рд╕рд╛рде рдмрд╛рддрдЪреАрдд рдХреЗ рддрд░реНрдХ рдХреЛ рдПрдиреНрдХреИрдкреНрд╕реБрд▓реЗрдЯ рдХрд░рдирд╛, рдкреНрд░рд╕рдВрд╕реНрдХрд░рдг рдФрд░ рдбреЗрдЯрд╛ рдХреЛ рдПрдХ рд╣реА рд░реВрдк рдореЗрдВ рд▓рд╛рдирд╛ рдерд╛ред


рдЗрдВрдЯрд░рдлрд╝реЗрд╕ рдФрд░ рд╕рдВрджреЗрд╢ рдореЙрдбрд▓


рд╕рдВрджреЗрд╢реЛрдВ рдХреЗ рд▓рд┐рдП, рдирд┐рдореНрдирд▓рд┐рдЦрд┐рдд IMessage рдЗрдВрдЯрд░рдлрд╝реЗрд╕ рдХреЛ рдбрд┐рдЬрд╝рд╛рдЗрди рдХрд░рдирд╛ рд╕рдВрднрд╡ рдерд╛, рдЬреЛ рдЗрд╕ рдбреЗрдЯрд╛ рдХреЗ рд╕рд╛рде рдХрд╛рдо рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рд╡рд┐рднрд┐рдиреНрди рдкреНрд░рдгрд╛рд▓рд┐рдпреЛрдВ рдФрд░ рд╡рд┐рдзрд┐рдпреЛрдВ рдХреЗ рд▓рд┐рдП рдЖрдиреЗ рд╡рд╛рд▓реЗ рд╕рдВрджреЗрд╢ рдбреЗрдЯрд╛ рдХреЛ рд╕рдВрдЧреНрд░рд╣реАрдд рдХрд░рдиреЗ рд╡рд╛рд▓реА рдХрдХреНрд╖рд╛рдУрдВ рджреНрд╡рд╛рд░рд╛ рд╕рдВрддреБрд╖реНрдЯ рд╣реИ:


export interface IMessage { chatId: number; lang: string; text: string; fullText: string; command: string; name: string; getReplyStatus: () => string; getReplyData: () => any; setStatus: (status: string) => IMessage; withData: (data: any) => IMessage; answer: (args: any) => string | void; } 

рдЗрд╕ рдЗрдВрдЯрд░рдлрд╝реЗрд╕ рдХреЛ рд▓рд╛рдЧреВ рдХрд░рдиреЗ рд╡рд╛рд▓реЗ рдмреЗрд╕ рдХреНрд▓рд╛рд╕ BaseMessage рдХреЗ рдирд┐рдореНрди рд░реВрдк рд╣реИрдВ:


BaseMessage
 import {IMessage} from '../message/i-message'; export class BaseMessage implements IMessage { public chatId: number; public lang: string; public text: string; public fullText: string; public command: string; protected firstName: string; protected lastName: string; protected replyStatus: string; protected replyData: any; get name(): string { const firstName: string = this.firstName || ''; const lastName: string = this.lastName || ''; return `${firstName} ${lastName}`.trim(); } public getReplyStatus(): string { return this.replyStatus; } public getReplyData(): any { return this.replyData; } public setStatus(status: string): IMessage { this.replyStatus = status; return this; } public withData(data: any): IMessage { this.replyData = data; return this; } public answer(args: any): string | void { throw new Error('not implemented'); } } } 

рдЯреЗрд▓реАрдЧреНрд░рд╛рдо рдХреЗ рд▓рд┐рдП рд╕рдВрджреЗрд╢ рд╡рд░реНрдЧ:


TelegramMessage
 import {BaseMessage} from '../message/base.message'; import {IMessage} from '../message/i-message'; export class TelegramMessage extends BaseMessage implements IMessage { private ctx: any; constructor(ctx) { super(); this.ctx = ctx; const {message} = this.ctx.update; this.chatId = message.chat.id; this.fullText = message.text; this.command = this.ctx.command; this.text = this.fullText.replace(`/${this.command}`, ''); this.lang = message.from.language_code; this.firstName = message.from.first_name; this.lastName = message.from.last_name; } public answer(args: any): string | void { return this.ctx.replyWithHTML(args); } } 

рдФрд░ рд╡реАрдХреЗ рдХреЗ рд▓рд┐рдП:


VKMessage
 import {BaseMessage} from '../message/base.message'; import {IMessage} from '../message/i-message'; export class VKMessage extends BaseMessage implements IMessage { private ctx: any; constructor(ctx) { super(); this.ctx = ctx; const {message} = this.ctx; this.chatId = this.getChatId(this.ctx); this.fullText = message.text; this.command = this.ctx.command; this.text = this.fullText.replace(`/${this.command}`, ''); this.lang = 'ru'; this.firstName = message.from.first_name; this.lastName = message.from.last_name; } public answer(args: any) { const answer: string = `${args}`.replace(/<\/?(strong|i)>/gm, ''); this.ctx.reply(answer); } private getChatId({message, bot}): number { const peerId: number = +`${message.peer_id}`.replace(/[0-9]0+/, ''); const groupId: number = bot.settings.group_id; return peerId + groupId; } } 

рдЖрдкрдХреЛ рдХрд┐рди рдмрд╛рддреЛрдВ рдкрд░ рдзреНрдпрд╛рди рджреЗрдирд╛ рдЪрд╛рд╣рд┐рдП:


  1. рдЪреИрдЯрдЖрдИрдб рдЪреИрдЯ рдХреЗ рд▓рд┐рдП рд╡рд┐рд╢рд┐рд╖реНрдЯ рдкрд╣рдЪрд╛рдирдХрд░реНрддрд╛ рд╣реИред рдЯреЗрд▓реАрдЧреНрд░рд╛рдо рдХреЗ рд▓рд┐рдП, рдпрд╣ рд╕реНрдкрд╖реНрдЯ рд░реВрдк рд╕реЗ рдкреНрд░рддреНрдпреЗрдХ рд╕рдВрджреЗрд╢ рдХреА рд╕рдВрд░рдЪрдирд╛ рдореЗрдВ рдЖрддрд╛ рд╣реИред рд╣рд╛рд▓рд╛рдВрдХрд┐, рд╡реАрдХреЗ рдХреЗ рдорд╛рдорд▓реЗ рдореЗрдВ рд╕реНрдкрд╖реНрдЯ рд░реВрдк рд╕реЗ рдРрд╕рд╛ рдХреЛрдИ рдкрд╣рдЪрд╛рдирдХрд░реНрддрд╛ рдирд╣реАрдВ рд╣реИред рдХреИрд╕реЗ рд╣реЛ рд╕рдХрддрд╛ рд╣реИ? рдЗрд╕ рдкреНрд░рд╢реНрди рдХрд╛ рдЙрддреНрддрд░ рджреЗрдиреЗ рдХреЗ рд▓рд┐рдП рдЖрдкрдХреЛ рдпрд╣ рд╕рдордЭрдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИ рдХрд┐ рдмреЙрдЯ рд╡реАрдХреЗ рдХреЗ рднреАрддрд░ рдХреИрд╕реЗ рдХрд╛рд░реНрдп рдХрд░рддрд╛ рд╣реИред рдЗрд╕ рдкреНрд░рдгрд╛рд▓реА рдореЗрдВ, рд╕рдореВрд╣ рдкрддреНрд░рд╛рдЪрд╛рд░ рдореЗрдВ рдПрдХ рдмреЙрдЯ рд╕рдореБрджрд╛рдп рдХреА рдУрд░ рд╕реЗ рдХрд╛рд░реНрдп рдХрд░рддрд╛ рд╣реИ рдЬрд┐рд╕рдХреЗ рд▓рд┐рдП рдпрд╣ рд╢реБрд░реВ рд╣реЛрддрд╛ рд╣реИред рдпрд╛рдиреА рдкреНрд░рддреНрдпреЗрдХ рдмреЙрдЯ рд╕рдВрджреЗрд╢ рдореЗрдВ рдПрдХ group_id рд╕рдореБрджрд╛рдп рдкрд╣рдЪрд╛рдирдХрд░реНрддрд╛ рд╣реЛрддрд╛ рд╣реИред рдЗрд╕рдХреЗ рдЕрд▓рд╛рд╡рд╛, peer_id рд╕рдВрджреЗрд╢ рдореЗрдВ рдЖрддрд╛ рд╣реИ (рдЕрдзрд┐рдХ рд╡рд┐рд╡рд░рдг рдпрд╣рд╛рдВ рдкрдврд╝рд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИ ) рд╣рдорд╛рд░реЗ рдорд╛рдорд▓реЗ рдореЗрдВ рд╕рдореВрд╣ рд╡рд╛рд░реНрддрд╛рд▓рд╛рдк рдХреЗ рдкрд╣рдЪрд╛рдирдХрд░реНрддрд╛ рдХреЗ рд░реВрдк рдореЗрдВред рдЗрди рджреЛ рдкрд╣рдЪрд╛рдирдХрд░реНрддрд╛рдУрдВ рдХреЗ рдЖрдзрд╛рд░ рдкрд░, рдЖрдк рдЕрдкрдиреЗ рдЪреИрдЯ рдкрд╣рдЪрд╛рдирдХрд░реНрддрд╛ рдХрд╛ рдирд┐рд░реНрдорд╛рдг рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ, рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП:


     private getChatId({message, bot}): number { const peerId: number = +(`${message.peer_id}`.replace(/[0-9]0+/, '')); const groupId: number = bot.settings.group_id; return peerId + groupId; } 

    рдпрд╣ рд╡рд┐рдзрд┐ рдирд┐рд╢реНрдЪрд┐рдд рд░реВрдк рд╕реЗ рд╕рд╣реА рдирд╣реАрдВ рд╣реИ, рд▓реЗрдХрд┐рди рд╡рд░реНрддрдорд╛рди рдХрд╛рд░реНрдп рдХреЗ рд▓рд┐рдП рд▓рд╛рдЧреВ рд╣реИред


  2. рдкреВрд░реНрдг рдкрд╛рда рдФрд░ рдкрд╛рда ред рдЬреИрд╕рд╛ рдХрд┐ рдЪрд░ рдХрд╛ рдирд╛рдо рдЗрдВрдЧрд┐рдд рдХрд░рддрд╛ рд╣реИ , рдкреВрд░реНрдг рдкрд╛рда рдореЗрдВ рд╕рдВрджреЗрд╢ рдХрд╛ рдкреВрд░рд╛ рдкрд╛рда рд╣реЛрддрд╛ рд╣реИ, рдЬрдмрдХрд┐ рдкрд╛рда рдореЗрдВ рдХреЗрд╡рд▓ рд╡рд╣реА рднрд╛рдЧ рд╣реЛрддрд╛ рд╣реИ рдЬреЛ рдХрдорд╛рдВрдб рдХреЗ рдирд╛рдо рдХреЗ рдмрд╛рдж рдЖрддрд╛ рд╣реИред



рдпрд╣ рд╕реБрд╡рд┐рдзрд╛рдЬрдирдХ рд╣реИ рдХреНрдпреЛрдВрдХрд┐ рдпрд╣ рдЖрдкрдХреЛ рддреИрдпрд╛рд░ рдХрд┐рдП рдЧрдП рдкрд╛рда рдХреНрд╖реЗрддреНрд░ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдиреЗ рдХреА рдЕрдиреБрдорддрд┐ рджреЗрддрд╛ рд╣реИ рдЬрд┐рд╕рдореЗрдВ рдирд┐рд╣рд┐рдд рд╕рд╣рд╛рдпрдХ рдЬрд╛рдирдХрд╛рд░реА, рдЬреИрд╕реЗ рдХрд┐ рдШрдЯрдирд╛ рдХреА рддрд╛рд░реАрдЦ рдпрд╛ рдЦрд┐рд▓рд╛рдбрд╝реА рдХрд╛ рдирд╛рдоред


  1. рдЯреЗрд▓реАрдЧреНрд░рд╛рдо рдХреЗ рд╡рд┐рдкрд░реАрдд, рд╡реАрдХреЗ рд╕рдВрджреЗрд╢ <b> рдпрд╛ <i> рдЬреИрд╕реЗ рдкрд╛рда рдХреЛ рдЙрдЬрд╛рдЧрд░ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдЯреИрдЧ рдХрд╛ рдПрдХ рд╕реЗрдЯ рдХрд╛ рд╕рдорд░реНрдерди рдирд╣реАрдВ рдХрд░рддреЗ рд╣реИрдВ, рдЗрд╕рд▓рд┐рдП, рдЬрдм рдЬрд╡рд╛рдм рджреЗрддреЗ рд╣реИрдВ, рддреЛ рдЗрди рдЯреИрдЧ рдХреЛ рдирд┐рдпрдорд┐рдд рдЕрднрд┐рд╡реНрдпрдХреНрддрд┐ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рд╣рдЯрд╛ рджрд┐рдпрд╛ рдЬрд╛рдирд╛ рдЪрд╛рд╣рд┐рдПред

рд╕рдВрджреЗрд╢ рдкреНрд░рд╛рдкреНрдд рдХрд░рдиреЗ рдФрд░ рднреЗрдЬрдиреЗ рдХреЗ рд▓рд┐рдП рдПрдбреЗрдкреНрдЯрд░


рдЯреЗрд▓реАрдЧреНрд░рд╛рдо рдФрд░ рд╡реАрдХреЗ рд╕рдВрджреЗрд╢реЛрдВ рдХреЗ рд▓рд┐рдП рдЗрдВрдЯрд░рдлрд╝реЗрд╕ рдФрд░ рдбреЗрдЯрд╛ рд╕рдВрд░рдЪрдирд╛рдПрдВ рдмрдирд╛рдиреЗ рдХреЗ рдмрд╛рдж, рдЖрдиреЗ рд╡рд╛рд▓реА рдШрдЯрдирд╛рдУрдВ рдХреЗ рдкреНрд░рд╕рдВрд╕реНрдХрд░рдг рдХреЗ рд▓рд┐рдП рд╕реЗрд╡рд╛рдУрдВ рдХреЛ рд▓рд╛рдЧреВ рдХрд░рдиреЗ рдХрд╛ рд╕рдордп рдЖ рдЧрдпрд╛ рд╣реИред


рдЫрд╡рд┐


рдЗрди рд╕реЗрд╡рд╛рдУрдВ рдХреЛ рдЯреЗрд▓реАрдЧреНрд░рд╛рдо рдФрд░ рд╡реАрдХреЗ рдПрдкреАрдЖрдИ рдХреЗ рд╕рд╛рде рдмрд╛рддрдЪреАрдд рдХрд░рдиреЗ рдФрд░ рд▓рдВрдмреЗ рд╕рдордп рд╕реЗ рдорддрджрд╛рди рддрдВрддреНрд░ рд▓реЙрдиреНрдЪ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рддреАрд╕рд░реЗ рдкрдХреНрд╖ рдХреЗ рдореЙрдбреНрдпреВрд▓ рдХреЛ рд╢реБрд░реВ рдХрд░рдирд╛ рд╣реЛрдЧрд╛, рд╕рд╛рде рд╣реА рд╕рд╛рде рдЖрдиреЗ рд╡рд╛рд▓реА рдХрдорд╛рдВрдб рдФрд░ рдЖрдВрддрд░рд┐рдХ рдШрдЯрдирд╛рдУрдВ рдХреЗ рдмреАрдЪ рдПрдХ рд╕рдВрдмрдВрдз рд╣реЛрдирд╛ рдЪрд╛рд╣рд┐рдП рдЬреЛ рд╕рдВрджреЗрд╢ рд╕реЗрд╡рд╛рдУрдВ рд╕реЗ рдбреЗрдЯрд╛ рдкреНрд░рд╛рдкреНрдд рдХрд░рддреЗ рд╕рдордп рд╕рд┐рд╕реНрдЯрдо рдореЗрдВ рд╣реЛрддрд╛ рд╣реИред


TelegramService
 import Telegraf from 'telegraf'; import {Injectable} from '@nestjs/common'; import * as SocksAgent from 'socks5-https-client/lib/Agent'; import {ConfigService} from '../common/config.service'; import {AppEmitter} from '../common/event-bus.service'; import {TelegramMessage} from './telegram.message'; @Injectable() export class TelegramService { private bot: Telegraf<any>; constructor(config: ConfigService, appEmitter: AppEmitter) { const botToken: string = config.get('TELEGRAM_BOT_TOKEN'); this.bot = config.get('TELEGRAM_USE_PROXY') ? new Telegraf(botToken, { telegram: {agent: this.getProxy(config)}, }) : new Telegraf(botToken); this.getCommandEventMapping(appEmitter).forEach(([command, event]) => { this.bot.command(command, ctx => { ctx.command = command; appEmitter.emit(event, new TelegramMessage(ctx)); }); }); } public launch(): void { this.bot.launch(); } private getProxy(config: ConfigService): SocksAgent { return new SocksAgent({ socksHost: config.get('TELEGRAM_PROXY_HOST'), socksPort: config.get('TELEGRAM_PROXY_PORT'), socksUsername: config.get('TELEGRAM_PROXY_LOGIN'), socksPassword: config.get('TELEGRAM_PROXY_PASSWORD'), }); } private getCommandEventMapping( appEmitter: AppEmitter, ): Array<[string, string]> { return [ ['event_add', appEmitter.EVENT_ADD], ['event_remove', appEmitter.EVENT_REMOVE], ['info', appEmitter.EVENT_INFO], ['add', appEmitter.PLAYER_ADD], ['remove', appEmitter.PLAYER_REMOVE], ]; } } 

VKService
 import * as VkBot from 'node-vk-bot-api'; import {Injectable} from '@nestjs/common'; import {ConfigService} from '../common/config.service'; import {AppEmitter} from '../common/event-bus.service'; import {VKMessage} from './vk.message'; @Injectable() export class VKService { private bot: VkBot<any>; constructor(config: ConfigService, appEmitter: AppEmitter) { const botToken: string = config.get('VK_TOKEN'); this.bot = new VkBot(botToken); this.getCommandEventMapping(appEmitter).forEach(([command, event]) => { this.bot.command(`/${command}`, async ctx => { const [from] = await this.bot.execute('users.get', { user_ids: ctx.message.from_id, }); ctx.message.from = from; ctx.command = command; appEmitter.emit(event, new VKMessage(ctx)); }); }); } public launch(): void { this.bot.startPolling(); } private getCommandEventMapping( appEmitter: AppEmitter, ): Array<[string, string]> { return [ ['event_add', appEmitter.EVENT_ADD], ['event_remove', appEmitter.EVENT_REMOVE], ['info', appEmitter.EVENT_INFO], ['add', appEmitter.PLAYER_ADD], ['remove', appEmitter.PLAYER_REMOVE], ]; } } 

рдЖрдкрдХреЛ рдХрд┐рди рдмрд╛рддреЛрдВ рдкрд░ рдзреНрдпрд╛рди рджреЗрдирд╛ рдЪрд╛рд╣рд┐рдП:


  1. рдЯреЗрд▓реАрдЧреНрд░рд╛рдо рд╕реЗрд╡рд╛ (hi Roskomnadzor) рддрдХ рдкрд╣реБрдВрдЪ рдХреЗ рд╕рд╛рде рдХреБрдЫ рд╕рдорд╕реНрдпрд╛рдУрдВ рдХреЗ рдХрд╛рд░рдг, рд╡реИрдХрд▓реНрдкрд┐рдХ рд░реВрдк рд╕реЗ рдПрдХ рдкреНрд░реЙрдХреНрд╕реА рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдирд╛ рдЖрд╡рд╢реНрдпрдХ рдерд╛ рдЬреЛ рдореЛрдЬрд╝реЗ 5-https-client рдкреИрдХреЗрдЬ рдкреНрд░рджрд╛рди рдХрд░рддрд╛ рд╣реИред
  2. рдХрд┐рд╕реА рдИрд╡реЗрдВрдЯ рдХреЛ рд╕рдВрд╕рд╛рдзрд┐рдд рдХрд░рддреЗ рд╕рдордп, рдХрд┐рд╕реА рдлрд╝реАрд▓реНрдб рдХреЛ рдЙрд╕ рдХрдорд╛рдВрдб рдХреЗ рдорд╛рди рдХреЗ рд╕рд╛рде рд╕рдВрджрд░реНрдн рдореЗрдВ рдЬреЛрдбрд╝рд╛ рдЬрд╛рддрд╛ рд╣реИ рдЬрд┐рд╕рдХреЗ рд╕рд╛рде рд╕рдВрджреЗрд╢ рдкреНрд░рд╛рдкреНрдд рдХрд┐рдпрд╛ рдЧрдпрд╛ рдерд╛ред
  3. рд╡реАрдХреЗ рдХреЗ рд▓рд┐рдП, рдЖрдкрдХреЛ рдЙрд╕ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдХрд╛ рдбреЗрдЯрд╛ рдЕрд▓рдЧ рд╕реЗ рдбрд╛рдЙрдирд▓реЛрдб рдХрд░рдирд╛ рд╣реЛрдЧрд╛ рдЬрд┐рд╕рдиреЗ рдПрдХ рдЕрд▓рдЧ рдПрдкреАрдЖрдИ рдХреЙрд▓ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рд╕рдВрджреЗрд╢ рднреЗрдЬрд╛ рд╣реИ:
     const [from] = await this.bot.execute('users.get', { user_ids: ctx.message.from_id, }); 

рдбреЗрдЯрд╛ рдореЙрдбрд▓


рдмреЙрдЯ рдХреА рдШреЛрд╖рд┐рдд рдХрд╛рд░реНрдпрдХреНрд╖рдорддрд╛ рдХреЛ рд▓рд╛рдЧреВ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рддреАрди рдореЙрдбрд▓ рдкрд░реНрдпрд╛рдкреНрдд рдереЗ:


  1. Chat - рдмрд╛рддрдЪреАрдд рдпрд╛ рдмрд╛рддрдЪреАрддред
  2. Event - рдПрдХ рд╡рд┐рд╢реЗрд╖ рддрд┐рдерд┐ рдФрд░ рд╕рдордп рдХреЗ рд▓рд┐рдП рдирд┐рд░реНрдзрд╛рд░рд┐рдд рдЪреИрдЯ рдХреЗ рд▓рд┐рдП рдПрдХ рдШрдЯрдирд╛ред
  3. Player - рдЖрдпреЛрдЬрди рдореЗрдВ рднрд╛рдЧ рд▓реЗрдиреЗ рд╡рд╛рд▓рд╛ рдпрд╛ рдЦрд┐рд▓рд╛рдбрд╝реАред

рдбреЗрдЯрд╛ рд╕реНрдХреАрдорд╛


рдЯрд╛рдЗрдкреЙрд░реНрдо рд▓рд╛рдЗрдмреНрд░реЗрд░реА рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдиреЗ рд╡рд╛рд▓реЗ рдореЙрдбрд▓ рдХреЗ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рд╣реИрдВ:


рдЪреИрдЯ
 import { Entity, PrimaryGeneratedColumn, Column, OneToMany, JoinColumn, Index, } from 'typeorm'; import {Event} from './event'; @Entity() export class Chat { @PrimaryGeneratedColumn() id: number; @Index({unique: true}) @Column() chatId: number; @OneToMany(type => Event, event => event.chat) @JoinColumn() events: Event[]; } 

рдШрдЯрдирд╛
 import { Entity, PrimaryGeneratedColumn, Column, OneToMany, ManyToOne, JoinColumn, } from 'typeorm'; import {Chat} from './chat'; import {Player} from './player'; @Entity() export class Event { @PrimaryGeneratedColumn() id: number; @Column() date: Date; @Column() active: boolean; @ManyToOne(type => Chat, chat => chat.events) chat: Chat; @OneToMany(type => Player, player => player.event) @JoinColumn() players: Player[]; } 

рдЦрд┐рд▓рд╛рдбрд╝реА
 import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn, } from 'typeorm'; import {Event} from './event'; @Entity() export class Player { @PrimaryGeneratedColumn() id: number; @Column() name: string; @ManyToOne(type => Event, event => event.players) @JoinColumn() event: Event; } 

рдпрд╣рд╛рдВ рдПрдХ рдмрд╛рд░ рдлрд┐рд░ рдЪреИрдЯ , рдЗрд╡реЗрдВрдЯ рдФрд░ рдкреНрд▓реЗрдпрд░ рдореЙрдбрд▓ рдореЗрдВ рд╣реЗрд░рдлреЗрд░ рдХрд░рдиреЗ рдХреЗ рдорд╛рдорд▓реЗ рдореЗрдВ рд╡рд┐рднрд┐рдиреНрди рдЯреАрдореЛрдВ рдХреЗ рд▓рд┐рдП рдмреЙрдЯ рдХреЗ рддрдВрддреНрд░ рдкрд░ рдЪрд░реНрдЪрд╛ рдХрд░рдирд╛ рдЖрд╡рд╢реНрдпрдХ рд╣реИред


  1. рдХрд┐рд╕реА рднреА рдЖрджреЗрд╢ рдХреЗ рдкреНрд░рд╛рдкреНрдд рд╣реЛрдиреЗ рдкрд░, DB рдореЗрдВ chatId рд╕рд╛рде рдЪреИрдЯ рдХреЗ рдЕрд╕реНрддрд┐рддреНрд╡ рдХреЗ рд▓рд┐рдП рдПрдХ рдЪреЗрдХ рдмрдирд╛рдпрд╛ рдЬрд╛рддрд╛ рд╣реИред рдпрджрд┐ рдХреЛрдИ рд╕рдВрдЧрдд рд░рд┐рдХреЙрд░реНрдб рдирд╣реАрдВ рд╣реИ, рддреЛ рдЗрд╕реЗ рдмрдирд╛рдпрд╛ рдЬрд╛рддрд╛ рд╣реИред
  2. рддрд░реНрдХ рдХреЛ рд╕рд░рд▓ рдмрдирд╛рдиреЗ рдХреЗ рд▓рд┐рдП, рдкреНрд░рддреНрдпреЗрдХ рдЪреИрдЯ рдореЗрдВ рдХреЗрд╡рд▓ рдПрдХ рд╕рдХреНрд░рд┐рдп рдИрд╡реЗрдВрдЯ ( рдИрд╡реЗрдВрдЯ ) рд╣реЛ рд╕рдХрддрд╛ рд╣реИред рдпрд╛рдиреА /event_add рдкреНрд░рд╛рдкреНрдд рд╣реЛрдиреЗ рдкрд░, рд╕рд┐рд╕реНрдЯрдо рдореЗрдВ рдирд┐рд░реНрджрд┐рд╖реНрдЯ рддрд┐рдерд┐ рдХреЗ рд╕рд╛рде рдПрдХ рдирдпрд╛ рд╕рдХреНрд░рд┐рдп рдИрд╡реЗрдВрдЯ рдмрдирд╛рдпрд╛ рдЬрд╛рддрд╛ рд╣реИред
    рд╡рд░реНрддрдорд╛рди рд╕рдХреНрд░рд┐рдп рдШрдЯрдирд╛ (рдпрджрд┐ рдпрд╣ рдореМрдЬреВрдж рд╣реИ) рдирд┐рд╖реНрдХреНрд░рд┐рдп рд╣реЛ рдЬрд╛рддреА рд╣реИред
  3. /event_remove рд╡рд░реНрддрдорд╛рди рдЪреИрдЯ рдореЗрдВ рд╕рдХреНрд░рд┐рдп рдИрд╡реЗрдВрдЯ рдкрд╛рддрд╛ рд╣реИ рдФрд░ рдЗрд╕реЗ рдирд┐рд╖реНрдХреНрд░рд┐рдп рдХрд░ рджреЗрддрд╛ рд╣реИред
  4. /info рд╡рд░реНрддрдорд╛рди рдЪреИрдЯ рдореЗрдВ рдПрдХ рд╕рдХреНрд░рд┐рдп рдИрд╡реЗрдВрдЯ рдкрд╛рддрд╛ рд╣реИ, рдЗрд╕ рдИрд╡реЗрдВрдЯ рдкрд░ рдЬрд╛рдирдХрд╛рд░реА рдФрд░ рдЦрд┐рд▓рд╛рдбрд╝рд┐рдпреЛрдВ ( рдкреНрд▓реЗрдпрд░ ) рдХреА рдПрдХ рд╕реВрдЪреА рдкреНрд░рджрд░реНрд╢рд┐рдд рдХрд░рддрд╛ рд╣реИред
  5. рд╕рдХреНрд░рд┐рдп рдИрд╡реЗрдВрдЯ рдХреЛ рдЬреЛрдбрд╝рдиреЗ рдФрд░ рдЙрд╕рдореЗрдВ рд╕реЗ рдЦрд┐рд▓рд╛рдбрд╝рд┐рдпреЛрдВ рдХреЛ /remove рдХреЗ рд╕рд╛рде рдХрд╛рдо /add /remove ред

рдбреЗрдЯрд╛ рдХреЗ рд╕рд╛рде рдХрд╛рдо рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рд╕рдВрдмрдВрдзрд┐рдд рд╕реЗрд╡рд╛ рдЗрд╕ рдкреНрд░рдХрд╛рд░ рд╣реИ:


StorageService
 import {Injectable} from '@nestjs/common'; import {InjectConnection} from '@nestjs/typeorm'; import {Connection, Repository, UpdateResult} from 'typeorm'; import {Chat} from './models/chat'; import {Event} from './models/event'; import {Player} from './models/player'; @Injectable() export class StorageService { private chatRepository: Repository<Chat>; private eventRepository: Repository<Event>; private playerRepository: Repository<Player>; constructor(@InjectConnection() private readonly dbConnection: Connection) { this.chatRepository = this.dbConnection.getRepository(Chat); this.eventRepository = this.dbConnection.getRepository(Event); this.playerRepository = this.dbConnection.getRepository(Player); } public get connection() { return this.dbConnection; } public async ensureChat(chatId: number): Promise<Chat> { let chat: Chat = await this.chatRepository.findOne({chatId}); if (chat) { return chat; } chat = new Chat(); chat.chatId = chatId; return this.chatRepository.save(chat); } public markChatEventsInactive(chat: Chat): Promise<UpdateResult> { return this.dbConnection .createQueryBuilder() .update(Event) .set({active: false}) .where({chat}) .execute(); } public appendChatActiveEvent(chat: Chat, date: Date): Promise<Event> { const event: Event = new Event(); event.chat = chat; event.active = true; event.date = date; return this.eventRepository.save(event); } public findChatActiveEvent(chat: Chat): Promise<Event | null> { return this.eventRepository.findOne({where: {chat, active: true}}); } public getPlayers(event: Event): Promise<Player[]> { return this.playerRepository.find({where: {event}}); } public findPlayer(event: Event, name: string): Promise<Player | null> { return this.playerRepository.findOne({where: {event, name}}); } public addPlayer(event: Event, name: string): Promise<Player> { const player: Player = new Player(); player.event = event; player.name = name; return this.playerRepository.save(player); } public removePlayer(player: Player): Promise<Player> { return this.playerRepository.remove(player); } } 

рдЯреЗрдореНрдкрд▓реЗрдЯ рдХреА рдкреНрд░рддрд┐рдХреНрд░рд┐рдпрд╛


рдбреЗрдЯрд╛ рдХреЗ рд╕рд╛рде рдХрд╛рдо рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рд╡рд░реНрдгрд┐рдд StorageService рд╕реЗрд╡рд╛ рдХреЗ рдЕрд▓рд╛рд╡рд╛, рдПрдХ рд╕рдорд░реНрдкрд┐рдд рдЯреЗрдореНрдкрд▓реЗрдЯ рд╕реЗрд╡рд╛ рд╕реЗрд╡рд╛ рдХреЛ рд▓рд╛рдЧреВ рдХрд░рдиреЗ рдХреА рднреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рдереА рдЬрд┐рд╕рдХреЗ рдХрд╛рд░реНрдп рдЗрд╕ рд╕рдордп рд╣реИрдВ:


  1. рд╡рд┐рднрд┐рдиреНрди рднрд╛рд╖рд╛рдУрдВ рдореЗрдВ рд╡рд┐рднрд┐рдиреНрди рдХрдорд╛рдВрдб рдХреЗ рд▓рд┐рдП рдЙрддреНрддрд░ рд╡рд┐рдХрд▓реНрдкреЛрдВ рдХреЗ рд▓рд┐рдП рд╣реИрдВрдбрд▓рдмрд╛рд░ рдЯреЗрдореНрдкрд▓реЗрдЯ рдбрд╛рдЙрдирд▓реЛрдб рдФрд░ рд╕рдВрдХрд▓рд┐рдд рдХрд░реЗрдВред
  2. рд╡рд░реНрддрдорд╛рди рдШрдЯрдирд╛, рдкреНрд░рддрд┐рдХреНрд░рд┐рдпрд╛ рдХреА рд╕реНрдерд┐рддрд┐ рдФрд░ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдХреА рднрд╛рд╖рд╛ рдХреЗ рдЖрдзрд╛рд░ рдкрд░ рдПрдХ рдЙрдкрдпреБрдХреНрдд рдЯреЗрдореНрдкрд▓реЗрдЯ рдХрд╛ рдЪрдпрди рдХрд░рдирд╛ред
  3. рдХрдорд╛рдВрдб рдХреЗ рдкрд░рд┐рдгрд╛рдорд╕реНрд╡рд░реВрдк рдкреНрд░рд╛рдкреНрдд рдЖрдВрдХрдбрд╝реЛрдВ рдХреЗ рд╕рд╛рде рдЯреЗрдореНрдкрд▓реЗрдЯ рднрд░рдирд╛ред

TemplateService
 import * as path from 'path'; import * as fs from 'fs'; import * as handlebars from 'handlebars'; import {readDirDeepSync} from 'read-dir-deep'; import {Injectable, Logger} from '@nestjs/common'; export interface IParams { action: string; status: string; lang?: string; } @Injectable() export class TemplateService { private readonly DEFAULT_LANG: string = 'en'; private readonly TEMPLATE_PATH: string = 'templates'; private logger: Logger; private templatesMap: Map<string, (d: any) => string>; constructor(logger: Logger) { this.logger = logger; this.load(); } public apply(params: IParams, data: any): string { this.logger.log( `apply template: ${params.action} ${params.status} ${params.lang}`, ); let template = this.getTemplate(params); if (!template) { params.lang = this.DEFAULT_LANG; template = this.getTemplate(params); } if (!template) { throw new Error('template-not-found'); } return template(data); } private getTemplate(params: IParams): (data: any) => string { const {lang, action, status} = params; return this.templatesMap.get(this.getTemplateKey(lang, action, status)); } private load() { const templatesDir: string = path.join( process.cwd(), this.TEMPLATE_PATH, ); const templateFileNames: string[] = readDirDeepSync(templatesDir); this.templatesMap = templateFileNames.reduce((acc, fileName) => { const template = fs.readFileSync(fileName, {encoding: 'utf-8'}); const [, lang, action, status] = fileName .replace(/\.hbs$/, '') .split('/'); return acc.set( this.getTemplateKey(lang, action, status), handlebars.compile(template), ); }, new Map()); } private getTemplateKey( lang: string, action: string, status: string, ): string { return `${lang}-${action}-${status}`; } } 

рдЖрдкрдХреЛ рдХрд┐рди рдмрд╛рддреЛрдВ рдкрд░ рдзреНрдпрд╛рди рджреЗрдирд╛ рдЪрд╛рд╣рд┐рдП:


  1. рдЯреЗрдореНрдкрд▓реЗрдЯ рдлрд╛рдЗрд▓реЗрдВ рдПрдХ рдЕрд▓рдЧ ./templates рдирд┐рд░реНрджреЗрд╢рд┐рдХрд╛ рдореЗрдВ рдкрд░рд┐рдпреЛрдЬрдирд╛ рдХреА рдЬрдбрд╝ рдореЗрдВ рд╣реИрдВ рдФрд░ рднрд╛рд╖рд╛, рдХреНрд░рд┐рдпрд╛ (рдХрдорд╛рдВрдб) рдФрд░ рдкреНрд░рддрд┐рдХреНрд░рд┐рдпрд╛ рдХреА рд╕реНрдерд┐рддрд┐ рджреНрд╡рд╛рд░рд╛ рд╕рдВрд░рдЪрд┐рдд рд╣реИрдВред


     - templates - en - event_add - invalid_date.hbs - success.hbs - event_info - ru 

    рдЬрдм рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдХреЛ рдЖрд░рдВрднреАрдХреГрдд рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ, рддреЛ рд╕рднреА рдкреНрд░рддрд┐рдХреНрд░рд┐рдпрд╛ рдЯреЗрдореНрдкрд▓реЗрдЯреНрд╕ рдХреЛ рдореЗрдореЛрд░реА рдореЗрдВ рд▓реЛрдб рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ рдФрд░ рдЙрди рдорд╛рдирдЪрд┐рддреНрд░реЛрдВ рдХреЛ рдкреЙрдкреНрдпреБрд▓реЗрдЯ рдХрд░рддрд╛ рд╣реИ рдЬреЛ рд╡рд┐рд╢рд┐рд╖реНрдЯ рд░реВрдк рд╕реЗ рдЯреЗрдореНрдкрд▓реЗрдЯ рдХреА рдкрд╣рдЪрд╛рди рдХрд░рддреЗ рд╣реИрдВ:


     private getTemplateKey(lang: string, action: string, status: string): string { return `${lang}-${action}-${status}`; } 

  2. рд╡рд░реНрддрдорд╛рди рдореЗрдВ, рд╕рд┐рд╕реНрдЯрдо рдореЗрдВ рдЯреЗрдореНрдкрд▓реЗрдЯреНрд╕ рдХреЗ 2 рд╕реЗрдЯ рд╣реИрдВ: рд░реВрд╕реА рдФрд░ рдЕрдВрдЧреНрд░реЗрдЬреА рдХреЗ рд▓рд┐рдПред
    рд╡рд░реНрддрдорд╛рди рдШрдЯрдирд╛ рдХреА рднрд╛рд╖рд╛ рдХреЗ рд▓рд┐рдП рдЖрд╡рд╢реНрдпрдХ рдЯреЗрдореНрдкрд▓реЗрдЯ рдХреА рдЕрдиреБрдкрд╕реНрдерд┐рддрд┐ рдореЗрдВ рдбрд┐рдлрд╝реЙрд▓реНрдЯ рднрд╛рд╖рд╛ (рдЕрдВрдЧреНрд░реЗрдЬреА) рдореЗрдВ рдлрд╝реЙрд▓рдмреИрдХ рдкреНрд░рджрд╛рди рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИред


  3. рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП, рд╣реИрдВрдбрд▓рдмрд╛рд░ рдЦреБрдж рдХреЛ рдЯреЗрдВрдкрд▓реЗрдЯ рдХрд░рддреЗ рд╣реИрдВ:


     Player <strong>{{name}}</strong> will take part in the game List of players: {{#each players}} {{index}}: <i>{{name}}</i> {{/each}} Total: <strong>{{total}}</strong> 

    рджреЛрдиреЛрдВ рдкрд╛рд░рдВрдкрд░рд┐рдХ рдкреНрд▓реЗрд╕рд╣реЛрд▓реНрдбрд░реНрд╕ рдФрд░ рдЯреЗрд▓реАрдЧреНрд░рд╛рдо рдореЗрдВ рдкреНрд░рддрд┐рдХреНрд░рд┐рдпрд╛рдУрдВ рдХреЛ рдкреНрд░рд╛рд░реВрдкрд┐рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдорд╛рдиреНрдп рдЯреИрдЧ рдХрд╛ рдПрдХ рд╕реЗрдЯ рд╢рд╛рдорд┐рд▓ рд╣реИрдВ ред



рдХрдорд╛рдВрдб рдкреНрд░реЛрд╕реЗрд╕рд┐рдВрдЧ рд╕рд░реНрд╡рд┐рд╕реЗрдЬ (рдХреНрд░рд┐рдпрд╛рдПрдБ)


рдЕрдм рдЬрдм рдореВрд▓ рд╕рдорд░реНрдерди рд╕реЗрд╡рд╛рдУрдВ рдХрд╛ рд╡рд░реНрдгрди рдХрд┐рдпрд╛ рдЧрдпрд╛ рд╣реИ, рддреЛ рдпрд╣ рд╕рдордп рд╣реИ рдХрд┐ рдЖрдк рд╕реНрд╡рдпрдВ рдмреЙрдЯ рдХреЗ рд╡реНрдпрд╛рд╡рд╕рд╛рдпрд┐рдХ рддрд░реНрдХ рдХреЛ рд▓рд╛рдЧреВ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдЖрдЧреЗ рдмрдврд╝реЗрдВред рдпреЛрдЬрдирд╛рдмрджреНрдз рд░реВрдк рд╕реЗ, рдореЙрдбреНрдпреВрд▓ рдХреЗ рдмреАрдЪ рд╕рдВрдмрдВрдз рдпрд╣рд╛рдВ рдкреНрд░рд╕реНрддреБрдд рдХрд┐рдпрд╛ рдЧрдпрд╛ рд╣реИ:


рдХрд╛рд░реНрд░рд╡рд╛рдИ


рдмреЗрд╕рдПрд╢рди рдмреЗрд╕ рдХреНрд▓рд╛рд╕ рдХреЗ рдХрд╛рд░реНрдпреЛрдВ рдореЗрдВ рд╢рд╛рдорд┐рд▓ рд╣реИрдВ:


  1. AppEmitter рд╕реЗ рдПрдХ рд╡рд┐рд╢реЗрд╖ рдШрдЯрдирд╛ рдХреЗ рд▓рд┐рдП рд╕рджрд╕реНрдпрддрд╛ рд▓реЗрдВред
  2. рдШрдЯрдирд╛ рдкреНрд░рд╕рдВрд╕реНрдХрд░рдг рдХреА рд╕рдордЧреНрд░ рдХрд╛рд░реНрдпрдХреНрд╖рдорддрд╛, рдЕрд░реНрдерд╛рддреН:
    • рдореМрдЬреВрджрд╛ рдПрдХ рдХреЗ рд▓рд┐рдП рдЦреЛрдЬ рдХрд░рдирд╛ рдпрд╛ рдЯреАрдо рдХреЗ рд╕рдВрджрд░реНрдн рдореЗрдВ рдПрдХ рдирдИ рдЪреИрдЯ рдмрдирд╛рдирд╛ред
    • doAction рдЯреЗрдореНрдкрд▓реЗрдЯ рд╡рд┐рдзрд┐ рдХреЗ рд▓рд┐рдП рдХреЙрд▓ doAction рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди doAction рдХреЗ рдкреНрд░рддреНрдпреЗрдХ рд╡рд░реНрдЧ рдХреЗ рд▓рд┐рдП рдЕрд▓рдЧ-рдЕрд▓рдЧ рд╣реИред
    • рдЯреЗрдореНрдкреНрд▓реЗрдЯ рд╕реЗрд╡рд╛ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рдкрд░рд┐рдгрд╛рдорд╕реНрд╡рд░реВрдк doAction рдкреНрд░рддрд┐рдХреНрд░рд┐рдпрд╛ рдореЗрдВ рдЯреЗрдореНрдкрд▓реЗрдЯ рд▓рд╛рдЧреВ рдХрд░рдирд╛ред

рдЖрдзрд╛рд░ рдХреНрд░рд┐рдпрд╛
 import {Injectable, Logger} from '@nestjs/common'; import {IMessage} from '../message/i-message'; import {ConfigService} from '../common/config.service'; import {AppEmitter} from '../common/event-bus.service'; import {TemplateService} from '../common/template.service'; import {StorageService} from '../storage/storage.service'; import {Chat} from '../storage/models/chat'; @Injectable() export class BaseAction { protected appEmitter: AppEmitter; protected config: ConfigService; protected logger: Logger; protected templateService: TemplateService; protected storageService: StorageService; protected event: string; constructor( config: ConfigService, appEmitter: AppEmitter, logger: Logger, templateService: TemplateService, storageService: StorageService, ) { this.config = config; this.logger = logger; this.appEmitter = appEmitter; this.templateService = templateService; this.storageService = storageService; this.setEvent(); this.logger.log(`subscribe on "${this.event}" event`); this.appEmitter.on(this.event, this.handleEvent.bind(this)); } protected setEvent(): void { throw new Error('not implemented'); } protected async doAction(chat: Chat, message: IMessage): Promise<IMessage> { throw new Error('not implemented'); } private async handleEvent(message: IMessage) { try { this.logger.log(`"${this.event}" event received`); const chatId: number = message.chatId; const chat: Chat = await this.storageService.ensureChat(chatId); message = await this.doAction(chat, message); message.answer( this.templateService.apply( { action: this.event, status: message.getReplyStatus(), lang: message.lang, }, message.getReplyData(), ), ); } catch (error) { this.logger.error(error); message.answer(error.message); } } } 

рдмреЗрд╕рдПрд╢рди рдЪрд╛рдЗрд▓реНрдб рдХреНрд▓рд╛рд╕реЗрд╕ рдХрд╛ рдХрд╛рд░реНрдп рдЗрдирдкреБрдЯ рдХреЗ doAction рд╡рд┐рдзрд┐ рдХреЛ рдирд┐рд╖реНрдкрд╛рджрд┐рдд рдХрд░рдирд╛ рд╣реИ:


  1. рдЪреИрдЯ рдЬреЛ рдмреЗрд╕ рдХреНрд▓рд╛рд╕ рдореЗрдВ рдмрдирд╛рдИ рдпрд╛ рдкрд░рд┐рднрд╛рд╖рд┐рдд рдХреА рдЧрдИ рдереА
  2. IMessage рдкреНрд░реЛрдЯреЛрдХреЙрд▓ рдХрд╛ рдЕрдиреБрдкрд╛рд▓рди рдХрд░рдиреЗ рд╡рд╛рд▓реА рд╡рд╕реНрддреБред

рдЗрд╕ рдкрджреНрдзрддрд┐ рдХреЛ рдирд┐рд╖реНрдкрд╛рджрд┐рдд рдХрд░рдиреЗ рдХреЗ рдкрд░рд┐рдгрд╛рдорд╕реНрд╡рд░реВрдк, IMessage рднреА рд▓реМрдЯрд╛рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ, рд▓реЗрдХрд┐рди рд╕рд╣реА рдЯреЗрдореНрдкрд▓реЗрдЯ рдЪреБрдирдиреЗ рдХреЗ рд▓рд┐рдП рд╕реНрдерд╛рдкрд┐рдд рд╕реНрдерд┐рддрд┐ рдФрд░ рдкреНрд░рддрд┐рдХреНрд░рд┐рдпрд╛ рдЯреЗрдореНрдкрд▓реЗрдЯ рдореЗрдВ рднрд╛рдЧ рд▓реЗрдиреЗ рд╡рд╛рд▓реЗ рдбреЗрдЯрд╛ рдХреЗ рд╕рд╛рдеред


EventAddAction
 import {Injectable} from '@nestjs/common'; import * as statuses from './statuses'; import {parseEventDate, formatEventDate} from '../common/utils'; import {BaseAction} from './base.action'; import {Chat} from '../storage/models/chat'; import {Event} from '../storage/models/event'; import {IMessage} from '../message/i-message'; @Injectable() export class EventAddAction extends BaseAction { protected setEvent(): void { this.event = this.appEmitter.EVENT_ADD; } protected async doAction(chat: Chat, message: IMessage): Promise<IMessage> { await this.storageService.markChatEventsInactive(chat); const eventDate: Date = parseEventDate(message.text.trim()); if (!eventDate) { return message.setStatus(statuses.STATUS_INVALID_DATE); } const event: Event = await this.storageService.appendChatActiveEvent( chat, eventDate, ); return message.setStatus(statuses.STATUS_SUCCESS).withData({ date: formatEventDate(event.date), }); } } 

EventRemoveAction
 import {Injectable} from '@nestjs/common'; import * as statuses from './statuses'; import {formatEventDate} from '../common/utils'; import {BaseAction} from './base.action'; import {Chat} from '../storage/models/chat'; import {Event} from '../storage/models/event'; import {IMessage} from '../message/i-message'; @Injectable() export class EventRemoveAction extends BaseAction { protected setEvent(): void { this.event = this.appEmitter.EVENT_REMOVE; } protected async doAction(chat: Chat, message: IMessage): Promise<IMessage> { const activeEvent: Event = await this.storageService.findChatActiveEvent( chat, ); await this.storageService.markChatEventsInactive(chat); if (activeEvent) { return message.setStatus(statuses.STATUS_SUCCESS).withData({ date: formatEventDate(activeEvent.date), }); } else { return message.setStatus(statuses.STATUS_NO_EVENT); } } } 

EventInfoAction
 import {Injectable, Logger} from '@nestjs/common'; import * as statuses from './statuses'; import {formatEventDate} from '../common/utils'; import {ConfigService} from '../common/config.service'; import {AppEmitter} from '../common/event-bus.service'; import {TemplateService} from '../common/template.service'; import {StorageService} from '../storage/storage.service'; import {BaseAction} from './base.action'; import {PlayerHelper} from './player.helper'; import {Chat} from '../storage/models/chat'; import {Event} from '../storage/models/event'; import {Player} from '../storage/models/player'; import {IMessage} from '../message/i-message'; @Injectable() export class EventInfoAction extends BaseAction { private playerHelper: PlayerHelper; constructor( config: ConfigService, appEmitter: AppEmitter, logger: Logger, templateService: TemplateService, playerHelper: PlayerHelper, storageService: StorageService, ) { super(config, appEmitter, logger, templateService, storageService); this.playerHelper = playerHelper; } protected setEvent(): void { this.event = this.appEmitter.EVENT_INFO; } protected async doAction(chat: Chat, message: IMessage): Promise<IMessage> { const activeEvent: Event = await this.storageService.findChatActiveEvent( chat, ); if (!activeEvent) { return message.setStatus(statuses.STATUS_NO_EVENT); } const players: Player[] = await this.storageService.getPlayers( activeEvent, ); return message.setStatus(statuses.STATUS_SUCCESS).withData({ date: formatEventDate(activeEvent.date), ...(await this.playerHelper.getPlayersList(activeEvent)), }); } } 

PlayerAddAction
 import {Injectable, Logger} from '@nestjs/common'; import * as statuses from './statuses'; import {ConfigService} from '../common/config.service'; import {AppEmitter} from '../common/event-bus.service'; import {TemplateService} from '../common/template.service'; import {StorageService} from '../storage/storage.service'; import {BaseAction} from './base.action'; import {PlayerHelper} from './player.helper'; import {Chat} from '../storage/models/chat'; import {Event} from '../storage/models/event'; import {Player} from '../storage/models/player'; import {IMessage} from '../message/i-message'; @Injectable() export class PlayerAddAction extends BaseAction { private playerHelper: PlayerHelper; constructor( config: ConfigService, appEmitter: AppEmitter, logger: Logger, templateService: TemplateService, playerHelper: PlayerHelper, storageService: StorageService, ) { super(config, appEmitter, logger, templateService, storageService); this.playerHelper = playerHelper; } protected setEvent(): void { this.event = this.appEmitter.PLAYER_ADD; } protected async doAction(chat: Chat, message: IMessage): Promise<IMessage> { const activeEvent: Event = await this.storageService.findChatActiveEvent( chat, ); if (!activeEvent) { return message.setStatus(statuses.STATUS_NO_EVENT); } const name: string = this.playerHelper.getPlayerName(message); const existedPlayer: Player = await this.storageService.findPlayer( activeEvent, name, ); if (existedPlayer) { return message .setStatus(statuses.STATUS_ALREADY_ADDED) .withData({name}); } const newPlayer: Player = await this.storageService.addPlayer( activeEvent, name, ); return message.setStatus(statuses.STATUS_SUCCESS).withData({ name: newPlayer.name, ...(await this.playerHelper.getPlayersList(activeEvent)), }); } } 

PlayerRemoveAction
 import {Injectable, Logger} from '@nestjs/common'; import * as statuses from './statuses'; import {ConfigService} from '../common/config.service'; import {AppEmitter} from '../common/event-bus.service'; import {TemplateService} from '../common/template.service'; import {StorageService} from '../storage/storage.service'; import {BaseAction} from './base.action'; import {PlayerHelper} from './player.helper'; import {Chat} from '../storage/models/chat'; import {Event} from '../storage/models/event'; import {Player} from '../storage/models/player'; import {IMessage} from '../message/i-message'; @Injectable() export class PlayerRemoveAction extends BaseAction { private playerHelper: PlayerHelper; constructor( config: ConfigService, appEmitter: AppEmitter, logger: Logger, templateService: TemplateService, playerHelper: PlayerHelper, storageService: StorageService, ) { super(config, appEmitter, logger, templateService, storageService); this.playerHelper = playerHelper; } protected setEvent(): void { this.event = this.appEmitter.PLAYER_REMOVE; } protected async doAction(chat: Chat, message: IMessage): Promise<IMessage> { const activeEvent: Event = await this.storageService.findChatActiveEvent( chat, ); if (!activeEvent) { return message.setStatus(statuses.STATUS_NO_EVENT); } const name: string = this.playerHelper.getPlayerName(message); const existedPlayer: Player = await this.storageService.findPlayer( activeEvent, name, ); if (!existedPlayer) { return message .setStatus(statuses.STATUS_NO_PLAYER) .withData({name}); } await this.storageService.removePlayer(existedPlayer); return message.setStatus(statuses.STATUS_SUCCESS).withData({ name, ...(await this.playerHelper.getPlayersList(activeEvent)), }); } } 

рдЖрдкрдХреЛ рдХрд┐рди рдмрд╛рддреЛрдВ рдкрд░ рдзреНрдпрд╛рди рджреЗрдирд╛ рдЪрд╛рд╣рд┐рдП:


  1. рдХрдорд╛рдВрдб рдХреЗ рдмрд╛рдж рдЙрд╕ рдЦрд┐рд▓рд╛рдбрд╝реА рдХреЛ рдЬреЛрдбрд╝реЗрдВ / рдирд┐рдХрд╛рд▓реЗрдВ / рд╣рдЯрд╛рдПрдВ рдЬрд┐рд╕рдХрд╛ рдирд╛рдо рдЙрд╕ рдЦрд┐рд▓рд╛рдбрд╝реА рд╕реЗ рд╣реИ / рдЬрд┐рд╕рдХрд╛ рдирд╛рдо рд╣реИ рдпрджрд┐ рдирд╛рдо рдирд┐рд░реНрджрд┐рд╖реНрдЯ рдирд╣реАрдВ рдХрд┐рдпрд╛ рдЧрдпрд╛ рд╣реИ, рддреЛ рдЯреАрдо рдХреЛ рдХреЙрд▓ рдХрд░рдиреЗ рд╡рд╛рд▓реЗ рдЦрд┐рд▓рд╛рдбрд╝реА рдХреЛ рдЬреЛрдбрд╝рд╛ / рд╣рдЯрд╛ рджрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИред
  2. /add , /remove рдФрд░ /info рдЖрджреЗрд╢ рдХреЗ рдЬрд╡рд╛рдм рдореЗрдВ, рд╕рдХреНрд░рд┐рдп рдШрдЯрдирд╛ рдХреЗ рд▓рд┐рдП рдЦрд┐рд▓рд╛рдбрд╝рд┐рдпреЛрдВ рдХреА рдПрдХ рдЕрджреНрдпрддрди рд╕реВрдЪреА рдкреНрд░рджрд░реНрд╢рд┐рдд рдХреА рдЬрд╛рддреА рд╣реИред

рд▓рд╛рдЧреВ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдЖрд╡рд╢реНрдпрдХ рдХрд╛рд░реНрдпрдХреНрд╖рдорддрд╛ (1) рдФрд░ (2) рдХреЛ рдПрдХ рд╡рд┐рд╢реЗрд╖ рд╕рд╣рд╛рдпрдХ рд╡рд░реНрдЧ рдореЗрдВ рд▓реЗ рдЬрд╛рдпрд╛ рдЧрдпрд╛:


PlayerHelper
 import {Injectable} from '@nestjs/common'; import {StorageService} from '../storage/storage.service'; import {Event} from '../storage/models/event'; import {Player} from '../storage/models/player'; import {IMessage} from '../message/i-message'; @Injectable() export class PlayerHelper { protected storageService: StorageService; constructor(storageService: StorageService) { this.storageService = storageService; } public getPlayerName(message: IMessage) { const name = message.text.trim(); return name.length > 0 ? name : message.name; } public async getPlayersList(event: Event) { const players: Player[] = await this.storageService.getPlayers(event); return { total: players.length, players: players.map((player, index) => ({ index: index + 1, name: player.name, })), }; } } 

рдпрд╣ рд╕рдм рдПрдХ рд╕рд╛рде рд░рдЦрдирд╛


рдЬреИрд╕рд╛ рдХрд┐ рд▓реЗрдЦ рдХреА рд╢реБрд░реБрдЖрдд рдореЗрдВ рдЙрд▓реНрд▓реЗрдЦ рдХрд┐рдпрд╛ рдЧрдпрд╛ рд╣реИ, рдиреЗрд╕реНрдЯрдЬреЗрдПрд╕ рдлреНрд░реЗрдорд╡рд░реНрдХ рдХрд╛ рдЙрдкрдпреЛрдЧ рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдлреНрд░реЗрдорд╡рд░реНрдХ рдХреЗ рд░реВрдк рдореЗрдВ рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИред рдПрдХ рд╕рдордп, рдореИрдВ рдЗрд╕ рдЕрджреНрднреБрдд рдкреБрд╕реНрддрдХрд╛рд▓рдп рдХреЗ рдирд┐рд░реНрдорд╛рддрд╛ рд╕реЗ рд╡реНрдпрдХреНрддрд┐рдЧрдд рд░реВрдк рд╕реЗ рд░рд┐рдкреЛрд░реНрдЯ рдореЗрдВ рднрд╛рдЧ рд▓реЗрдиреЗ рдХреЗ рд▓рд┐рдП рднрд╛рдЧреНрдпрд╢рд╛рд▓реА рдерд╛ред


рдХрдИ NodeJS рдмреЙрдпрд▓рд░рдкреНрд▓реЗрдЯреНрд╕, рдореИрдиреБрдЕрд▓ рдФрд░ рд▓рд╛рдЗрдмреНрд░реЗрд░реАрдЬрд╝ рдЕрдХреНрд╕рд░ рдореЙрдбреНрдпреВрд▓ рдХреЗ рдмреАрдЪ рдХрд┐рд╕реА рднреА рд╕рд╛рдиреЗ рдЗрдирд┐рд╢рд┐рдпрд▓рд╛рдЗрдЬрд╝реЗрд╢рди рд╕реНрдЯреНрд░реЗрдЯреЗрдЬреА рдФрд░ рдХрдиреЗрдХреНрд╢рди рдХреА рдкреЗрд╢рдХрд╢ рди рдХрд░рдХреЗ рдкрд╛рдк рдХрд░рддреЗ рд╣реИрдВред рдЕрдиреБрдкреНрд░рдпреЛрдЧ рдХреЗ рд╡рд┐рдХрд╛рд╕ рдХреЗ рд╕рд╛рде, рдЙрдЪрд┐рдд рдзреНрдпрд╛рди рдХреЗ рдЕрднрд╛рд╡ рдореЗрдВ, рдХреЛрдб рдЖрдзрд╛рд░ рдореЙрдбреНрдпреВрд▓ рдХреЗ рдмреАрдЪ рдЖрд╡рд╢реНрдпрдХрддрд╛-рдПрд╕ рдХрд╛ рдПрдХ рдорд┐рд╢рд╛рд▓ рдмрди рдЬрд╛рддрд╛ рд╣реИ, рдЕрдХреНрд╕рд░ рдЪрдХреНрд░реАрдп рдирд┐рд░реНрднрд░рддрд╛ рдпрд╛ рд╕рдВрдмрдВрдзреЛрдВ рдХреЗ рд▓рд┐рдП рднреА рдЕрдЧреНрд░рдгреА рд╣реЛрддрд╛ рд╣реИ, рдЬрд╣рд╛рдВ рд╕рд┐рджреНрдзрд╛рдВрдд рд░реВрдк рдореЗрдВ, рдЙрдиреНрд╣реЗрдВ рдирд╣реАрдВ рд╣реЛрдирд╛ рдЪрд╛рд╣рд┐рдПред Dependency Injection awilix , NestJS .


DI , NodeJS , , .




.


  1. TELEGRAM_BOT_TOKEN тАФ Telegram .
  2. TELEGRAM_USE_PROXY тАФ TelegramAPI.
  3. TELEGRAM_PROXY_HOST тАФ TelegramAPI.
  4. TELEGRAM_PROXY_PORT тАФ TelegramAPI.
  5. TELEGRAM_PROXY_LOGIN тАФ TelegramAPI.
  6. TELEGRAM_PROXY_PASSWORD тАФ TelegramAPI.
  7. VK_TOKEN тАФ VK .
  8. DATABASE_URL тАФ . production .

:


  1. TELEGRAM_BOT_TOKEN Telegram.
  2. VK_TOKEN тАФ , VK . рдпрд╛рдиреА , . .
  3. DATABASE_URL тАФ production PostgreSQL. DBaaS elephantsql .

CI


" - тАФ ". , TravisCI :


 language: node_js node_js: - '8' - '10' - '12' script: - npm run lint - npm run build - npm run test:cov after_script: - npm install -g codecov - codecov 

codecov .


Docker Docker Hub Gonf CI/CD GitHub Actions Python . Continuous Deployment, Github:


 name: Release on: release: types: [published] jobs: build: runs-on: ubuntu-latest env: LOGIN: ${{ secrets.DOCKER_LOGIN }} NAME: ${{ secrets.DOCKER_NAME }} steps: - uses: actions/checkout@v1 - name: Install Node.js uses: actions/setup-node@v1 - name: Login to docker.io run: echo ${{ secrets.DOCKER_PWD }} | docker login -u ${{ secrets.DOCKER_LOGIN }} --password-stdin - name: npm install and build run: npm ci && npm run build - name: Build the Docker image run: docker build -t $LOGIN/$NAME:${GITHUB_REF:10} -f Dockerfile . - name: Push image to docker.io run: docker push $LOGIN/$NAME:${GITHUB_REF:10} 


. Telegram :
.


demo1


.


demo2


.


demo3


VK <b> <i> .
.


demo4


.


demo6


Viber , . , Viber webhooks long-polling. , Viber . . (?) .


Telegram @Tormozz48bot .


Github . , .


PS .

Source: https://habr.com/ru/post/hi483194/


All Articles