hh-applicant-tool 0.2.2__tar.gz → 0.2.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of hh-applicant-tool might be problematic. Click here for more details.

Files changed (23) hide show
  1. {hh_applicant_tool-0.2.2 → hh_applicant_tool-0.2.4}/PKG-INFO +17 -10
  2. {hh_applicant_tool-0.2.2 → hh_applicant_tool-0.2.4}/README.md +16 -9
  3. {hh_applicant_tool-0.2.2 → hh_applicant_tool-0.2.4}/hh_applicant_tool/operations/add_handler.py +11 -0
  4. {hh_applicant_tool-0.2.2 → hh_applicant_tool-0.2.4}/hh_applicant_tool/operations/apply_similar.py +54 -18
  5. {hh_applicant_tool-0.2.2 → hh_applicant_tool-0.2.4}/hh_applicant_tool/operations/authorize.py +19 -0
  6. {hh_applicant_tool-0.2.2 → hh_applicant_tool-0.2.4}/pyproject.toml +1 -1
  7. {hh_applicant_tool-0.2.2 → hh_applicant_tool-0.2.4}/hh_applicant_tool/__init__.py +0 -0
  8. {hh_applicant_tool-0.2.2 → hh_applicant_tool-0.2.4}/hh_applicant_tool/__main__.py +0 -0
  9. {hh_applicant_tool-0.2.2 → hh_applicant_tool-0.2.4}/hh_applicant_tool/api/__init__.py +0 -0
  10. {hh_applicant_tool-0.2.2 → hh_applicant_tool-0.2.4}/hh_applicant_tool/api/client.py +0 -0
  11. {hh_applicant_tool-0.2.2 → hh_applicant_tool-0.2.4}/hh_applicant_tool/api/errors.py +0 -0
  12. {hh_applicant_tool-0.2.2 → hh_applicant_tool-0.2.4}/hh_applicant_tool/color_log.py +0 -0
  13. {hh_applicant_tool-0.2.2 → hh_applicant_tool-0.2.4}/hh_applicant_tool/constants.py +0 -0
  14. {hh_applicant_tool-0.2.2 → hh_applicant_tool-0.2.4}/hh_applicant_tool/main.py +0 -0
  15. {hh_applicant_tool-0.2.2 → hh_applicant_tool-0.2.4}/hh_applicant_tool/operations/__init__.py +0 -0
  16. {hh_applicant_tool-0.2.2 → hh_applicant_tool-0.2.4}/hh_applicant_tool/operations/call_api.py +0 -0
  17. {hh_applicant_tool-0.2.2 → hh_applicant_tool-0.2.4}/hh_applicant_tool/operations/clear_negotiations.py +0 -0
  18. {hh_applicant_tool-0.2.2 → hh_applicant_tool-0.2.4}/hh_applicant_tool/operations/list_resumes.py +0 -0
  19. {hh_applicant_tool-0.2.2 → hh_applicant_tool-0.2.4}/hh_applicant_tool/operations/refresh_token.py +0 -0
  20. {hh_applicant_tool-0.2.2 → hh_applicant_tool-0.2.4}/hh_applicant_tool/operations/update_resumes.py +0 -0
  21. {hh_applicant_tool-0.2.2 → hh_applicant_tool-0.2.4}/hh_applicant_tool/operations/whoami.py +0 -0
  22. {hh_applicant_tool-0.2.2 → hh_applicant_tool-0.2.4}/hh_applicant_tool/types.py +0 -0
  23. {hh_applicant_tool-0.2.2 → hh_applicant_tool-0.2.4}/hh_applicant_tool/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: hh-applicant-tool
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary:
5
5
  Author: Senior YAML Developer
6
6
  Author-email: yamldeveloper@proton.me
@@ -15,8 +15,7 @@ Description-Content-Type: text/markdown
15
15
 
16
16
  # HH Applicant Tool
17
17
 
18
- > ! Наложен мораторий на доработки/переработки. Разработка будет возобновлена после 10 звезд 💫 Вы, блять, сучары 7000 раз эту хуйню скачали и всего 5 звезд поставили. Просто охуеть! Сколько нужно скачек хотя бы ради 50 звезд?
19
- > > Да в пизду это приложение я им не пользуюсь. Ищите работу заграницей. Нахуй вам hh? Эта страна еще 12 лет правления любителя шагов доброй лоли не выдержит
18
+ > ! Наложен мораторий на доработки/переработки. Разработка будет возобновлена после 100 звезд 💫
20
19
 
21
20
  ![Publish to PyPI](https://github.com/s3rgeym/hh-applicant-tool/actions/workflows/publish.yml/badge.svg)
22
21
  [![PyPi Version](https://img.shields.io/pypi/v/hh-applicant-tool)]()
@@ -25,20 +24,30 @@ Description-Content-Type: text/markdown
25
24
  [![PyPI - Downloads](https://img.shields.io/pypi/dm/hh-applicant-tool)]()
26
25
  [![Total Downloads](https://static.pepy.tech/badge/hh-applicant-tool)]()
27
26
 
28
- Утилита для автоматизации действий на HH.RU таких как рассылка откликов на подходящие вакансии и обновление всех резюме.
27
+ Утилита для автоматизации действий на HH.RU таких как рассылка откликов на подходящие вакансии и обновление всех резюме. Поддержка осуществляется строго в группе https://t.me/vaitishniki (в ней разрешены мат, п*рнография, оскорбления всех участников кроме админа, а так же слив любой информации про хуевых работодателей и нерадивых херок).
29
28
 
30
29
  Системные требования:
31
30
 
32
31
  - socat
33
32
  - python >= 3.10
34
33
 
35
- Нужную версию можно поставить через asdf/pyenv, а вот socat придется доставить.
34
+ Нужную версию можно поставить через asdf/pyenv, а вот socat придется доставить. В Arch-based:
36
35
 
37
- Данная утилита не может работать от root. Я не планирую добавлять поддержку Windows, но никто не мешает вам ее реализовать.
36
+ ```bash
37
+ yay -S socat
38
+ ```
39
+
40
+ Пример работы:
41
+
42
+ ![image](https://github.com/user-attachments/assets/4936a8dd-f671-4788-8106-0470d5ce1dd6)
43
+ ![image](https://github.com/user-attachments/assets/55ab24ba-5325-40b4-9bd9-69ebcbc011c4)
44
+
45
+
46
+ Данная утилита работает только в Linux. Для авторизации требуется наличие какого-то окружения для взаимодейсвия с браузерос, после нее вы можете перенести `~/.config/hh-applicant-tool/config.json` на сервер и запускать утилиту через systemd или cron, либо же вообще делать это через **WSL**. Столь странный процесс связан с тем, что на странице авторизации запускается море скриптов, которые шифруют данные на клиенте перед отправкой на сервер, а так же выполняется куча запросов чтобы проверить не бот ли ты (хорошо, что после авторизации никаких проверок по факту нет, даже айпи не проверяется на соответсвие тому с какого была авторизация). В этой лапше мне лень разбираться, так же человек, вызвавшийся осилить этот процесс скрылся в закате, а мне и так нормально. Выходом могло бы стать использование безмозглого Хромиума, тогда можно было бы сделать авторизацию полностью через терминал без всяких socat'ов. Так же при наличии рутованного телефона можно вытащить `access` и `refresh` токены из официального приложения и добавить их в конфиг.
38
47
 
39
48
  Предыстория.
40
49
 
41
- Был один знакомый знакомого, который работал хером. Этот чувак не заморачивался с чтением резюме, а тупо скриптами рассылал предложения о работе... Бывают, конечно, филологини, которые не могут отлчить Java от JavaScript, но я думаю, что в значительном числе случаев, тут имеют место такие вот рассылки... И я перенял эту порочную практику. Мне уже просто лень читать весь этот бред, что пишут в описании вакансий. Там стандартное ООП, алгоритмы и прочая хуета... Вроде все подходят, а вроде хз — все не мое. Поэтому тупло спамлю в надежде на идеальную работу. Долгое время (пару недель в октябре 2022) я делал массовые заявки с помощью консоли браузера:
50
+ Был один знакомый знакомого, который работал хрюшей. Этот чувак не заморачивался с чтением резюме, а тупо скриптами рассылал предложения о работе... Бывают, конечно, филологини, которые не могут отличить Java от JavaScript, но я думаю, что <s>в значительном числе случаев, тут имеют место такие вот рассылки</s> они просто идиотки... И я перенял эту порочную практику. Мне уже просто лень читать весь этот бред, что пишут в описании вакансий. Там стандартное ООП, алгоритмы и прочая хуета... Вроде все подходят, а вроде хз — все не мое. Поэтому тупло спамлю в надежде на идеальную работу. Долгое время (пару недель в октябре 2022) я делал массовые заявки с помощью консоли браузера:
42
51
 
43
52
  ```js
44
53
  $$('[data-qa="vacancy-serp__vacancy_response"]').forEach((el) => el.click());
@@ -80,9 +89,7 @@ $ hh-applicant-tool apply-similar
80
89
  # Поднимаем резюме
81
90
  $ hh-applicant-tool update-resumes
82
91
 
83
- # Чистим заявки и баним за отказы говноконторы, нанявших на работу филолохинь,
84
- # астралохинь и прочих 3.14дарасов по блату, считающих, что погроммистом может
85
- # быть только ДОЦЕНТ МАТЕМАТИЧЕСКИХ НАУК 🤡
92
+ # Чистим заявки и баним за отказы говноконторы
86
93
  $ hh-applicant-tool clear-negotiations --blacklist-discard
87
94
  ```
88
95
 
@@ -1,7 +1,6 @@
1
1
  # HH Applicant Tool
2
2
 
3
- > ! Наложен мораторий на доработки/переработки. Разработка будет возобновлена после 10 звезд 💫 Вы, блять, сучары 7000 раз эту хуйню скачали и всего 5 звезд поставили. Просто охуеть! Сколько нужно скачек хотя бы ради 50 звезд?
4
- > > Да в пизду это приложение я им не пользуюсь. Ищите работу заграницей. Нахуй вам hh? Эта страна еще 12 лет правления любителя шагов доброй лоли не выдержит
3
+ > ! Наложен мораторий на доработки/переработки. Разработка будет возобновлена после 100 звезд 💫
5
4
 
6
5
  ![Publish to PyPI](https://github.com/s3rgeym/hh-applicant-tool/actions/workflows/publish.yml/badge.svg)
7
6
  [![PyPi Version](https://img.shields.io/pypi/v/hh-applicant-tool)]()
@@ -10,20 +9,30 @@
10
9
  [![PyPI - Downloads](https://img.shields.io/pypi/dm/hh-applicant-tool)]()
11
10
  [![Total Downloads](https://static.pepy.tech/badge/hh-applicant-tool)]()
12
11
 
13
- Утилита для автоматизации действий на HH.RU таких как рассылка откликов на подходящие вакансии и обновление всех резюме.
12
+ Утилита для автоматизации действий на HH.RU таких как рассылка откликов на подходящие вакансии и обновление всех резюме. Поддержка осуществляется строго в группе https://t.me/vaitishniki (в ней разрешены мат, п*рнография, оскорбления всех участников кроме админа, а так же слив любой информации про хуевых работодателей и нерадивых херок).
14
13
 
15
14
  Системные требования:
16
15
 
17
16
  - socat
18
17
  - python >= 3.10
19
18
 
20
- Нужную версию можно поставить через asdf/pyenv, а вот socat придется доставить.
19
+ Нужную версию можно поставить через asdf/pyenv, а вот socat придется доставить. В Arch-based:
21
20
 
22
- Данная утилита не может работать от root. Я не планирую добавлять поддержку Windows, но никто не мешает вам ее реализовать.
21
+ ```bash
22
+ yay -S socat
23
+ ```
24
+
25
+ Пример работы:
26
+
27
+ ![image](https://github.com/user-attachments/assets/4936a8dd-f671-4788-8106-0470d5ce1dd6)
28
+ ![image](https://github.com/user-attachments/assets/55ab24ba-5325-40b4-9bd9-69ebcbc011c4)
29
+
30
+
31
+ Данная утилита работает только в Linux. Для авторизации требуется наличие какого-то окружения для взаимодейсвия с браузерос, после нее вы можете перенести `~/.config/hh-applicant-tool/config.json` на сервер и запускать утилиту через systemd или cron, либо же вообще делать это через **WSL**. Столь странный процесс связан с тем, что на странице авторизации запускается море скриптов, которые шифруют данные на клиенте перед отправкой на сервер, а так же выполняется куча запросов чтобы проверить не бот ли ты (хорошо, что после авторизации никаких проверок по факту нет, даже айпи не проверяется на соответсвие тому с какого была авторизация). В этой лапше мне лень разбираться, так же человек, вызвавшийся осилить этот процесс скрылся в закате, а мне и так нормально. Выходом могло бы стать использование безмозглого Хромиума, тогда можно было бы сделать авторизацию полностью через терминал без всяких socat'ов. Так же при наличии рутованного телефона можно вытащить `access` и `refresh` токены из официального приложения и добавить их в конфиг.
23
32
 
24
33
  Предыстория.
25
34
 
26
- Был один знакомый знакомого, который работал хером. Этот чувак не заморачивался с чтением резюме, а тупо скриптами рассылал предложения о работе... Бывают, конечно, филологини, которые не могут отлчить Java от JavaScript, но я думаю, что в значительном числе случаев, тут имеют место такие вот рассылки... И я перенял эту порочную практику. Мне уже просто лень читать весь этот бред, что пишут в описании вакансий. Там стандартное ООП, алгоритмы и прочая хуета... Вроде все подходят, а вроде хз — все не мое. Поэтому тупло спамлю в надежде на идеальную работу. Долгое время (пару недель в октябре 2022) я делал массовые заявки с помощью консоли браузера:
35
+ Был один знакомый знакомого, который работал хрюшей. Этот чувак не заморачивался с чтением резюме, а тупо скриптами рассылал предложения о работе... Бывают, конечно, филологини, которые не могут отличить Java от JavaScript, но я думаю, что <s>в значительном числе случаев, тут имеют место такие вот рассылки</s> они просто идиотки... И я перенял эту порочную практику. Мне уже просто лень читать весь этот бред, что пишут в описании вакансий. Там стандартное ООП, алгоритмы и прочая хуета... Вроде все подходят, а вроде хз — все не мое. Поэтому тупло спамлю в надежде на идеальную работу. Долгое время (пару недель в октябре 2022) я делал массовые заявки с помощью консоли браузера:
27
36
 
28
37
  ```js
29
38
  $$('[data-qa="vacancy-serp__vacancy_response"]').forEach((el) => el.click());
@@ -65,9 +74,7 @@ $ hh-applicant-tool apply-similar
65
74
  # Поднимаем резюме
66
75
  $ hh-applicant-tool update-resumes
67
76
 
68
- # Чистим заявки и баним за отказы говноконторы, нанявших на работу филолохинь,
69
- # астралохинь и прочих 3.14дарасов по блату, считающих, что погроммистом может
70
- # быть только ДОЦЕНТ МАТЕМАТИЧЕСКИХ НАУК 🤡
77
+ # Чистим заявки и баним за отказы говноконторы
71
78
  $ hh-applicant-tool clear-negotiations --blacklist-discard
72
79
  ```
73
80
 
@@ -1,5 +1,6 @@
1
1
  import argparse
2
2
  import logging
3
+ import os
3
4
  from pathlib import Path
4
5
  from subprocess import check_call
5
6
 
@@ -36,6 +37,12 @@ class Operation(BaseOperation):
36
37
  )
37
38
 
38
39
  def run(self, args: Namespace) -> None:
40
+ # Проверка, запущен ли скрипт в WSL
41
+ if self.is_wsl():
42
+ print_err("⚠️ Предупреждение: Скрипт запущен в WSL 💩. Функциональность может быть ограничена или не работать вовсе.")
43
+ print_err("Рекомендуется запуск на нативных Linux-системах.")
44
+ return 1
45
+
39
46
  # TODO: с root не будет работать
40
47
  desktop_path = Path(
41
48
  "~/.local/share/applications/hhandroid.desktop"
@@ -48,3 +55,7 @@ class Operation(BaseOperation):
48
55
  else:
49
56
  print_err("⛔ Обработчик уже существует!")
50
57
  return 1
58
+
59
+ def is_wsl(self) -> bool:
60
+ """Проверяет, запущен ли скрипт в WSL."""
61
+ return "WSL_DISTRO_NAME" in os.environ
@@ -1,8 +1,8 @@
1
- # Этот модуль можно использовать как образец для других
2
1
  import argparse
3
2
  import logging
4
3
  import random
5
- from typing import TextIO
4
+ import time
5
+ from typing import TextIO, Tuple
6
6
 
7
7
  from ..api import ApiClient, ApiError, BadRequest
8
8
  from ..main import BaseOperation
@@ -16,6 +16,9 @@ logger = logging.getLogger(__package__)
16
16
  class Namespace(BaseNamespace):
17
17
  resume_id: str | None
18
18
  message_list: TextIO
19
+ force_message: bool
20
+ apply_interval: Tuple[float, float]
21
+ page_interval: Tuple[float, float]
19
22
 
20
23
 
21
24
  class Operation(BaseOperation):
@@ -34,6 +37,27 @@ class Operation(BaseOperation):
34
37
  default=False,
35
38
  action=argparse.BooleanOptionalAction,
36
39
  )
40
+ parser.add_argument(
41
+ "--apply-interval",
42
+ help="Интервал между отправкой откликов в секундах (X, X-Y)",
43
+ default="1-5",
44
+ type=self._parse_interval,
45
+ )
46
+ parser.add_argument(
47
+ "--page-interval",
48
+ help="Интервал между получением следующей страницы рекомендованных вакансий в секундах (X, X-Y)",
49
+ default="1-3",
50
+ type=self._parse_interval,
51
+ )
52
+
53
+ @staticmethod
54
+ def _parse_interval(interval: str) -> Tuple[float, float]:
55
+ """Парсит строку интервала и возвращает кортеж с минимальным и максимальным значениями."""
56
+ if "-" in interval:
57
+ min_interval, max_interval = map(float, interval.split("-"))
58
+ else:
59
+ min_interval = max_interval = float(interval)
60
+ return min(min_interval, max_interval), max(min_interval, max_interval)
37
61
 
38
62
  def run(self, args: Namespace) -> None:
39
63
  assert args.config["token"]
@@ -45,8 +69,6 @@ class Operation(BaseOperation):
45
69
  resume_id := args.resume_id or args.config["default_resume_id"]
46
70
  ):
47
71
  resumes: ApiListResponse = api.get("/resumes/mine")
48
- # Используем id первого резюме
49
- # TODO: создать 10 резюме и рассылать по 2000 откликов в сутки
50
72
  resume_id = resumes["items"][0]["id"]
51
73
  if args.message_list:
52
74
  application_messages = list(
@@ -54,33 +76,41 @@ class Operation(BaseOperation):
54
76
  )
55
77
  else:
56
78
  application_messages = [
57
- "Меня заинтересовала Ваша вакансия %(name)s",
58
- "Прошу рассмотреть мою кандидатуру на вакансию %(name)s",
79
+ "Меня заинтересовала ваша вакансия %(name)s",
80
+ "Прошу рассмотреть мою жалкую кандидатуру на вакансию %(name)s",
81
+ "Ваша вакансия %(name)s соответствует моим навыкам и опыту",
82
+ "Хочу присоединиться к вашей успешной команде лидеров рынка в качестве %(name)s",
83
+ "Мое резюме содержит все баззворды, указанные в вашей вакансии %(name)s",
59
84
  ]
85
+
86
+ apply_min_interval, apply_max_interval = args.apply_interval
87
+ page_min_interval, page_max_interval = args.page_interval
88
+
60
89
  self._apply_similar(
61
- api, resume_id, args.force_message, application_messages
90
+ api, resume_id, args.force_message, application_messages, apply_min_interval, apply_max_interval, page_min_interval, page_max_interval
62
91
  )
63
92
 
64
93
  def _get_vacancies(
65
- self, api: ApiClient, resume_id: str
94
+ self, api: ApiClient, resume_id: str, page_min_interval: float, page_max_interval: float
66
95
  ) -> list[VacancyItem]:
67
96
  rv = []
68
- # работает ограничение: глубина возвращаемых результатов не может быть больше 2000
69
- # Номер страницы (считается от 0, по умолчанию - 0)
70
97
  per_page = 100
71
98
  for page in range(20):
72
99
  res: ApiListResponse = api.get(
73
100
  f"/resumes/{resume_id}/similar_vacancies",
74
101
  page=page,
75
102
  per_page=per_page,
76
- # Мне кажется, что так поисковая выдача можно забиться неадекватами, которые по полгода кого-то ищут
77
- # Но так откликается на что-то уж совсем нерелевантное
78
- # order_by="publication_time",
79
103
  order_by="relevance",
80
104
  )
81
105
  rv.extend(res["items"])
82
106
  if page >= res["pages"] - 1:
83
107
  break
108
+
109
+ # Задержка перед получением следующей страницы
110
+ if page > 0:
111
+ interval = random.uniform(page_min_interval, page_max_interval)
112
+ time.sleep(interval)
113
+
84
114
  return rv
85
115
 
86
116
  def _apply_similar(
@@ -89,15 +119,20 @@ class Operation(BaseOperation):
89
119
  resume_id: str,
90
120
  force_message: bool,
91
121
  application_messages: list[str],
122
+ apply_min_interval: float,
123
+ apply_max_interval: float,
124
+ page_min_interval: float,
125
+ page_max_interval: float,
92
126
  ) -> None:
93
- # Получаем список рекомендованных вакансий и отправляем заявки
94
- # Проблема тут в том, что вакансии на которые мы отклимкались должны исчезать из поиска, но ОНИ ТАМ ПРИСУТСТВУЮТ. Так же есть вакансии с ебучими тестами, которые всегда вверху. Вроде можно отсортировать по дате, а потом постепенно уменьшать диапазон, но он не точный и округляется до 5 минут, а потому там повторы
95
127
  item: VacancyItem
96
- for item in self._get_vacancies(api, resume_id):
97
- # В рот я ебал вас и ваши тесты, пидоры
128
+ for item in self._get_vacancies(api, resume_id, page_min_interval, page_max_interval):
98
129
  if item["has_test"]:
99
130
  continue
100
- # Откликаемся на ваканчию
131
+
132
+ # Задержка перед отправкой отклика
133
+ interval = random.uniform(apply_min_interval, apply_max_interval)
134
+ time.sleep(interval)
135
+
101
136
  params = {
102
137
  "resume_id": resume_id,
103
138
  "vacancy_id": item["id"],
@@ -121,4 +156,5 @@ class Operation(BaseOperation):
121
156
  print_err("❗ Ошибка:", ex)
122
157
  if isinstance(ex, BadRequest) and ex.limit_exceeded:
123
158
  break
159
+
124
160
  print("📝 Отклики на вакансии разосланы!")
@@ -61,6 +61,17 @@ class Operation(BaseOperation):
61
61
  pass
62
62
 
63
63
  def run(self, args: Namespace) -> None:
64
+ # Проверяем, установлен ли socat
65
+ if not self.is_socat_installed():
66
+ print("⚠️ Предупреждение: socat не установлен. Для работы с unix-сокетами рекомендуется установить socat.")
67
+ print()
68
+ print("Вы можете установить socat с помощью вашего пакетного менеджера, например:")
69
+ print()
70
+ print(" - Debian/Ubuntu: sudo apt-get install socat")
71
+ print(" - Fedora: sudo dnf install socat")
72
+ print(" - Arch/Manjaro: sudo pacman -S socat")
73
+ print()
74
+
64
75
  oauth = OAuthClient(
65
76
  user_agent=(
66
77
  args.config["oauth_user_agent"] or args.config["user_agent"]
@@ -76,3 +87,11 @@ class Operation(BaseOperation):
76
87
  HHANDROID_SOCKET_PATH, oauth_client=oauth, config=args.config
77
88
  )
78
89
  server.serve_forever()
90
+
91
+ def is_socat_installed(self) -> bool:
92
+ """Проверяет, установлен ли socat в системе."""
93
+ try:
94
+ subprocess.run(["socat", "-h"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
95
+ return True
96
+ except FileNotFoundError:
97
+ return False
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "hh-applicant-tool"
3
- version = "0.2.2"
3
+ version = "0.2.4"
4
4
  description = ""
5
5
  authors = ["Senior YAML Developer <yamldeveloper@proton.me>"]
6
6
  readme = "README.md"