Initial commit.

Signed-off-by: Pavel Kirilin <win10@list.ru>
This commit is contained in:
2021-06-07 02:15:45 +04:00
commit 2091068ef6
32 changed files with 2233 additions and 0 deletions

139
.dockerignore Normal file
View File

@ -0,0 +1,139 @@
# Byte-compiled / optimized / DLL files
.vscode
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/

87
.flake8 Normal file
View File

@ -0,0 +1,87 @@
[flake8]
max-complexity = 6
inline-quotes = double
max-line-length = 88
extend-ignore = E203
docstring_style=sphinx
ignore =
; Found `f` string
WPS305,
; Missing docstring in public module
D100,
; Missing docstring in magic method
D105,
; Missing docstring in __init__
D107,
; Found class without a base class
WPS306,
; Missing docstring in public nested class
D106,
; First line should be in imperative mood
D401,
; Found `__init__.py` module with logic
WPS412,
; Found implicit string concatenation
WPS326,
; Found string constant over-use
WPS226,
; Found upper-case constant in a class
WPS115,
; Found nested function
WPS430,
; Found using `@staticmethod`
WPS602,
; Found method without arguments
WPS605,
; Found overused expression
WPS204,
; Found too many module members
WPS202,
; Found too high module cognitive complexity
WPS232,
; line break before binary operator
W503,
; Found module with too many imports
WPS201,
; Found vague import that may cause confusion: X
WPS347,
; Inline strong start-string without end-string.
RST210,
; subprocess call with shell=True seems safe, but may be changed in the future.
S602,
; Starting a process with a partial executable path.
S607,
; Consider possible security implications associated with subprocess module.
S404,
; Found nested class
WPS431,
; Found wrong module name
WPS100,
; Found too many methods
WPS214,
; Found too long ``try`` body
WPS229,
; Found function with too much cognitive complexity
WPS231,
; all init files
__init__.py:
; ignore not used imports
F401,
; ignore import with wildcard
F403,
; Found wrong metadata variable
WPS410,
per-file-ignores =
; all tests
test_*.py,tests.py,tests_*.py,*/tests/*:
; Use of assert detected
S101,
exclude =
./.git,
./venv,
./cached_venv,
./var,

1
.gen.env Normal file
View File

@ -0,0 +1 @@
LENOCHKA_INSTAGRAM_ID=1893900744:AAEapWUZtlhxjqa8v-y-aMDTdXb6ACFKfv4

139
.gitignore vendored Normal file
View File

@ -0,0 +1,139 @@
# Byte-compiled / optimized / DLL files
.vscode
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/

70
.gitlab-ci.yml Normal file
View File

@ -0,0 +1,70 @@
stages:
- tools
- test
- cleanup_tests
- build
- deploy
build_test:
stage: tools
tags:
- bots-deployer
script:
- make build-test
flake8:
stage: test
tags:
- bots-deployer
script:
- make flake8
pytest:
stage: test
tags:
- bots-deployer
script:
- make pytest
black:
stage: test
tags:
- bots-deployer
script:
- make black
mypy:
stage: test
tags:
- bots-deployer
script:
- make mypy
cleanup_tests:
stage: cleanup_tests
tags:
- bots-deployer
when: always
script:
- make clear-test
build:
stage: build
only:
- master
- tags
tags:
- bots-deployer
script:
- make build push
deploy:
stage: deploy
only:
- master
- tags
when: manual
tags:
- bots-deployer
script:
- make deploy-prod

4
.isort.cfg Normal file
View File

@ -0,0 +1,4 @@
[isort]
multi_line_output = 3
include_trailing_comma = true
use_parentheses = true

9
.mypy.ini Normal file
View File

@ -0,0 +1,9 @@
[mypy]
strict = True
ignore_missing_imports=True
allow_subclassing_any=True
allow_untyped_calls=True
pretty=True
show_error_codes=True
implicit_reexport=True
allow_untyped_decorators=True

63
.pre-commit-config.yaml Normal file
View File

@ -0,0 +1,63 @@
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v2.4.0
hooks:
- id: check-ast
- id: trailing-whitespace
- id: check-toml
- id: end-of-file-fixer
- repo: https://github.com/asottile/add-trailing-comma
rev: v2.1.0
hooks:
- id: add-trailing-comma
- repo: local
hooks:
- id: black
name: Format with Black
entry: black
language: system
types: [python]
- id: autoflake
name: autoflake
entry: autoflake
language: system
types: [ python ]
args: [ --in-place, --remove-all-unused-imports, --remove-duplicate-keys ]
- id: isort
name: isort
entry: isort
language: system
types: [ python ]
- id: flake8
name: Check with Flake8
entry: flake8
language: system
pass_filenames: false
types: [ python ]
args: [--count, .]
- id: mypy
name: Validate types with MyPy
entry: mypy
language: system
types: [ python ]
- id: yesqa
name: Remove usless noqa
entry: yesqa
language: system
types: [ python ]
- id: pytest
name: pytest
entry: pytest
language: system
pass_filenames: false
types: [ python ]

7
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,7 @@
{
"python.pythonPath": "/home/s3rius/.cache/pypoetry/virtualenvs/lenochka-6E1S1fmY-py3.9/bin/python",
"python.linting.pylintEnabled": false,
"python.linting.flake8Enabled": true,
"python.linting.enabled": true,
"python.formatting.provider": "black"
}

86
Makefile Normal file
View File

@ -0,0 +1,86 @@
.DEFAULT_GOAL := help
.PHONY: help
help: ## Show help
@grep -E '^[\.a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) \
| awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
.PHONY: env_file
env_file: ## Generate .gen.env file from env with "LENOCHKA_" prefix.
touch .gen.env && env | grep -E '^LENOCHKA_' > ".gen.env" || echo "No env vars"
test -f .env && cat .env > .gen.env || echo ".env file not found"
.PHONY: build
build: env_file ## Build docker container image.
docker-compose \
-f ./deploy/docker-compose.yml \
--project-directory . \
build --pull lenochka
.PHONY: build_test
build-test: ## Build image for tests.
docker-compose \
-f ./deploy/docker-compose.test.yml \
--project-directory . \
build --no-cache test_lenochka
.PHONY: flake8
flake8: ## Run flake8 check
docker-compose \
-f ./deploy/docker-compose.test.yml \
--project-directory . \
run test_lenochka flake8 --count .
.PHONY: black
black: ## Run black check
docker-compose \
-f ./deploy/docker-compose.test.yml \
--project-directory . \
run test_lenochka black --check .
.PHONY: mypy
mypy: ## Run pytest tests
docker-compose \
-f ./deploy/docker-compose.test.yml \
--project-directory . \
run test_lenochka mypy .
.PHONY: pytest
pytest: ## Run pytest tests
docker-compose \
-f ./deploy/docker-compose.test.yml \
--project-directory . \
run test_lenochka pytest -v --cov=lenochka
.PHONY: push
push: env_file ## Push container to the registry.
docker-compose \
-f ./deploy/docker-compose.yml \
--project-directory . \
push lenochka
.PHONY: pull
pull: env_file ## Download images from docker hub.
docker-compose \
-f deploy/docker-compose.yml \
--project-directory . \
pull --ignore-pull-failures lenochka \
|| \
docker-compose \
-f deploy/docker-compose.yml \
--project-directory . \
pull lenochka
.PHONY: deploy-prod
deploy-prod: pull ## deploy container in production.
docker-compose \
-f ./deploy/docker-compose.yml \
--project-directory . \
up -d
.PHONY: clear-test
clear-test: ## Remove containers and images for test
docker-compose \
-f ./deploy/docker-compose.test.yml \
--project-directory . \
down --rmi=all

1
README.md Normal file
View File

@ -0,0 +1 @@
# The most hilarious creature of the russian internet

View File

@ -0,0 +1,8 @@
version: '3.7'
services:
test_lenochka:
container_name: the_test_lenochka
build:
dockerfile: ./deploy/dockerfiles/test.Dockerfile
context: .

11
deploy/docker-compose.yml Normal file
View File

@ -0,0 +1,11 @@
version: '3.7'
services:
lenochka:
container_name: the_lenochka
build:
dockerfile: ./deploy/dockerfiles/Dockerfile
context: .
image: docker.le-memese.com/bots/lenochka-bot:${CI_COMMIT_REF_SLUG:-latest}
env_file:
- .gen.env

View File

@ -0,0 +1,37 @@
FROM python:3.9-alpine3.13
RUN adduser --disabled-password lenochka
RUN apk add --no-cache curl gcc musl-dev
ENV POETRY_VERSION 1.1.6
USER lenochka
RUN curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python -
ENV PATH="${PATH}:/home/lenochka/.poetry/bin:/home/lenochka/.local/bin"
RUN source "/home/lenochka/.poetry/env"
# Installing requirements
RUN poetry config virtualenvs.create false
COPY pyproject.toml poetry.lock /home/lenochka/app/
WORKDIR /home/lenochka/app/
RUN poetry install --no-dev
# Copying actuall application
COPY . /home/lenochka/app/src/
WORKDIR /home/lenochka/app/src/
RUN pip install --use-feature=in-tree-build .
WORKDIR /home/lenochka/app
USER root
RUN rm -rf /home/lenochka/app/src
RUN chown -R lenochka /home/lenochka
RUN chmod -R 700 /home/lenochka
RUN apk del curl gcc musl-dev
USER lenochka
CMD "lenochka_wake_up"

View File

@ -0,0 +1,17 @@
FROM python:3.9-alpine3.13
RUN apk add --no-cache curl gcc musl-dev
ENV POETRY_VERSION 1.1.6
RUN curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python -
ENV PATH="${PATH}:/root/.local/bin:/root/.poetry/bin"
RUN source "/root/.poetry/env"
# Installing requirements
RUN poetry config virtualenvs.create false
COPY . /app/
WORKDIR /app/
RUN poetry install

1
lenochka/__init__.py Normal file
View File

@ -0,0 +1 @@
"""The Lenochka."""

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,5 @@
"""Lenochka's brain."""
from lenochka.brain.brain import Brain
from lenochka.brain.memory import memory
__all__ = ["memory", "Brain"]

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

30
lenochka/brain/brain.py Normal file
View File

@ -0,0 +1,30 @@
from aiogram.types import Message
from lenochka.brain.brain_cell import BrainCell
class Brain:
"""
Lenochka's brain.
We don't realy need this part,
but since we need to have a container
for our the only one brain cell, we
have to implement it.
"""
def __init__(self) -> None:
self.cell = BrainCell()
async def think(self, message: Message, state_of_mind: None = None) -> None:
"""
Actual thinking.
:param message: incoming message.
:param state_of_mind: lenochka's current state of mind.
It's always clear in order to make the most thoughtful answer.
"""
if not self.cell.do_i_need_to_say_something():
return
thoughtful_answer = self.cell.create_reply(message.text)
await message.reply(thoughtful_answer)

View File

@ -0,0 +1,45 @@
import re
import secrets
WORD_PATTERN = re.compile(r"(\w+)")
class BrainCell:
"""
Main thinking mechanism.
It decides when and what to say.
"""
def do_i_need_to_say_something(self) -> bool:
"""
One of the most important decisions.
This brain cell's function looks
deep inside the palaces of the mind,
searching through the secrets of the universe
and decides if she want to say something.
:return: The decision.
"""
return secrets.randbelow(11) < 1 # noqa: WPS432
def create_reply(self, sentence: str) -> str:
"""
The hardest action to accomplish.
This brain cell function produces
thoughtful reply to evrey sentence that
needs it.
:param sentence: Sentence that needs a reply.
:return: actual reply.
"""
tokens = WORD_PATTERN.findall(sentence)
for token in reversed(tokens):
token_parts = token.lower().split("да")
if len(token_parts) > 1:
postfix = token_parts[-1]
break
return f"Пизда{postfix}."

14
lenochka/brain/memory.py Normal file
View File

@ -0,0 +1,14 @@
from pydantic import BaseSettings, Field
class Memory(BaseSettings):
"""
The memory.
It holds the most important things for lenochka's life.
"""
token: str = Field(env="LENOCHKA_INSTAGRAM_ID", required=True)
memory = Memory()

View File

@ -0,0 +1,8 @@
from lenochka.brain.brain_cell import BrainCell
def test_replies() -> None:
"""Test reply generator."""
cell = BrainCell()
assert cell.create_reply("Удав") == "Пиздав."
assert cell.create_reply("Дакимакура") == "Пиздакимакура."

23
lenochka/mouth.py Normal file
View File

@ -0,0 +1,23 @@
from aiogram import Bot as ConnectionToBrain
from aiogram import Dispatcher as ConnectionToEars
from aiogram import executor as world
from lenochka.brain import Brain, memory
def talk_to_the_wind() -> None:
"""
Start thinking and speaking to the wind.
This part of lenochka's existance
is the only one thing she can do
without making mistakes.
"""
brain = Brain()
brain_connection = ConnectionToBrain(token=memory.token)
ears = ConnectionToEars(brain_connection)
ears.register_message_handler(
brain.think,
regexp="да",
)
world.start_polling(ears)

1396
poetry.lock generated Normal file

File diff suppressed because it is too large Load Diff

29
pyproject.toml Normal file
View File

@ -0,0 +1,29 @@
[tool.poetry]
name = "lenochka"
version = "0.1.0"
description = ""
authors = ["Pavel Kirilin <win10@list.ru>"]
[tool.poetry.dependencies]
python = "^3.9"
aiogram = "^2.13"
pydantic = "^1.8.2"
[tool.poetry.dev-dependencies]
pytest = "^5.2"
black = "^21.5b2"
isort = "^5.8.0"
wemake-python-styleguide = "^0.15.2"
flake8 = "^3.9.2"
mypy = "^0.812"
yesqa = "^1.2.3"
autoflake = "^1.4"
pytest-env = "^0.6.2"
pytest-cov = "^2.12.1"
[tool.poetry.scripts]
lenochka_wake_up = "lenochka.mouth:talk_to_the_wind"
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"

3
pytest.ini Normal file
View File

@ -0,0 +1,3 @@
[pytest]
env =
LENOCHKA_INSTAGRAM_ID=123123