Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
annndruha committed Jul 14, 2023
1 parent 81c070f commit dc7f99e
Show file tree
Hide file tree
Showing 8 changed files with 1,038 additions and 259 deletions.
703 changes: 674 additions & 29 deletions LICENSE

Large diffs are not rendered by default.

8 changes: 3 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ Only for GitHub organizations repos issues (not for personal repos issues)

1. You need to create telegram bot via [BotFather](https://t.me/BotFather) and get bot token
2. Use your personal GitHub account or create another account, get [GitHub token](https://github.com/settings/tokens)
Token scopes must include: `repo (full)`, `admin:org -> read:org`, `user -> read:user`, `project -> read:project`

3. Next set the docker environment secrets:
* `BOT_TOKEN` - From step 1
* `BOT_NICKNAME` - From step 1
Expand All @@ -19,11 +21,7 @@ Only for GitHub organizations repos issues (not for personal repos issues)

5. Run Docker Example:
```commandline
docker run -e BOT_TOKEN=value1
-e BOT_NICKNAME=value2
-e GH_ACCOUNT_TOKEN=value3
-e GH_ORGANIZATION_NICKNAME=value4
-e ...
docker run -e BOT_TOKEN=value1 -e BOT_NICKNAME=value2 -e GH_ACCOUNT_TOKEN=value3 -e GH_ORGANIZATION_NICKNAME=value4
```
6. Add to bot to group chat

Expand Down
1 change: 1 addition & 0 deletions src/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,6 @@
application.add_handler(CallbackQueryHandler(handler_button))
application.add_handler(MessageHandler(filters.Entity("mention"), handler_message))
application.add_handler(MessageHandler(filters.ATTACHMENT, handler_message))
application.add_handler(MessageHandler(filters.ALL, handler_message))
application.add_error_handler(native_error_handler)
application.run_polling()
51 changes: 27 additions & 24 deletions src/answers.py
Original file line number Diff line number Diff line change
@@ -1,33 +1,38 @@
# Marakulin Andrey https://github.com/Annndruha
# 2023

ans = {
'issue_open': '\n> Issue open by {} via {}',
'assign_change': '\n> Assign changed from {} to @{} by {}.',
'issue_close': '\n> Issue closed by {}.',
'issue_reopen': '\n> Issue reopened by {}.',
'help': 'Чтобы создать issue, упомяните меня и после упоминания введите название issue.'
'\nЕсли вы хотите указать описание, после названия issue сделайте перенос строки'
' и напишите описание. Ниже пример сообщения для создания issue: \n\n'
'{} Разобраться в синтаксисе Markdown\nВ этой issue мне необходимо прочитать про синтаксис Markdown.'
'\nЯ могу это сделать вызвав команду бота: /md_guide',
'start': '🤖 Я <a href="https://github.com/Annndruha/issue-github-telegram-bot">бот</a>'
' для создания issue в репозиториях '
'<a href="https://github.com/{}">организации</a> в GitHub.'
'\nЧтобы создать issue из личных сообщений, упомяните меня в вашем сообщении. Больше информации в /help',
'markdown_guide_tg': '''
Нативный стиль Telegram поддерживает следующее форматирование, которое преобразуется в Markdown в GitHub:
from dataclasses import dataclass


@dataclass
class Answers:
issue_open = '\n> Issue open by {} via {}'
assign_change = '\n> Assign changed from {} to @{} by {}.'
issue_close = '\n> Issue closed by {}.'
issue_reopen = '\n> Issue reopened by {}.'
no_title = 'After the mention, you need to enter the title of the issue. More in /help'
help = 'To create an issue, mention me and after the mention enter the title of the issue.' \
'\nIf you want to provide a description, after the title of the issue, break the line and write a ' \
'description. Below is an example of a message to create an issue:\n\n' \
'{} This is issue title\n' \
'And start with this line is description' \
'\nAnother line of description.\nSee more by calling the bot command: /md_guide'
start = '🤖 I\'m <a href="https://github.com/Annndruha/issue-github-telegram-bot">bot</a>' \
' for create issue in GitHub <a href="https://github.com/{}">organization</a>' \
'\nMore info in /help'
markdown_guide_tg = '''
Native Telegram styling are converted to Markdown in GitHub:
<i>italic</i>
<b>bold</b>
<u>underline</u>
<s>strike</s>
<code>monospace_code</code>
<span class="tg-spoiler">spoiler</span> (GitHub не поддерживает)
<span class="tg-spoiler">spoiler</span> (GitHub not supported)
<a href="https://github.com">link</a>
''',
'markdown_guide_md': '''
## А это синтаксис Markdown
### Это раздел 3 уровня
'''
markdown_guide_md = '''
## This is Markdown syntax
### Third level header
* list_item
* list_item
* sub_list_item
Expand All @@ -41,13 +46,11 @@
| markdown_table | version |
|--------------------:|---------|
| Python | 3.11 |
| python-telegram-bot | 20.0 |
| python-telegram-bot | 20.1 |
```python
# code block
print('Hello, issue bot!')
```
[md_link](github.com/annndruha/issue-github-telegram-bot)
![md_image_link](https://picsum.photos/200)
'''
}
82 changes: 32 additions & 50 deletions src/github_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
# 2023
import logging

import requests
from gql import gql, Client
from gql.transport.requests import RequestsHTTPTransport
from gql.transport.requests import log as requests_logger
Expand All @@ -14,47 +13,38 @@ class Github:
def __init__(self, settings):
self.to_scrum = True
self.settings = settings
self.organization_nickname = settings.GH_ORGANIZATION_NICKNAME
self.issue_url = 'https://api.github.com/repos/' + settings.GH_ORGANIZATION_NICKNAME + '/{}/issues'
self.org_members_url = f'https://api.github.com/orgs/{settings.GH_ORGANIZATION_NICKNAME}/members'

self.session = requests.Session()

self.headers = {
'Accept': 'application/vnd.github+json',
'Authorization': f'Bearer {settings.GH_ACCOUNT_TOKEN}',
'X-GitHub-Api-Version': '2022-11-28',
'Content-Type': 'application/x-www-form-urlencoded'
}

self.transport = RequestsHTTPTransport(
url='https://api.github.com/graphql',
verify=True,
retries=3,
headers=self.headers
retries=1,
headers={'Authorization': f'Bearer {settings.GH_ACCOUNT_TOKEN}'}
)

with open('src/graphql/schema.github.graphql') as f:
schema_str = f.read()

self.client = Client(transport=self.transport, schema=schema_str)
self.client = Client(transport=self.transport)
self.__read_queries()

def __read_queries(self):
with open('src/graphql/get_repos.graphql') as f:
self.q_get_repos = gql(f.read())
with open('src/graphql/get_members.graphql') as f:
self.q_get_members = gql(f.read())
with open('src/graphql/add_to_scrum.graphql') as f:
self.q_add_to_scrum = gql(f.read())
with open('src/graphql/issue_actions.graphql') as f:
self.q_issue_actions = gql(f.read())

def open_issue(self, repo_id, title, body):
params = {'repositoryId': repo_id, 'title': title, 'body': body}
r = self.client.execute(self.q_issue_actions, operation_name='CreateIssue', variable_values=params)
return r
return self.client.execute(self.q_issue_actions, operation_name='CreateIssue', variable_values=params)

def transfer_issue(self, repo_id, issue_id):
params = {'repositoryId': repo_id, 'issueId': issue_id}
return self.client.execute(self.q_issue_actions, operation_name='TransferIssue', variable_values=params)

def get_repos(self, page_info):
params = {'gh_query': f'org:{self.organization_nickname} archived:false fork:true is:public sort:updated'}
params = {'gh_query': f'org:{self.settings.GH_ORGANIZATION_NICKNAME} archived:false fork:true is:public '
f'sort:updated'}
if page_info == 'repos_start': # start page
r = self.client.execute(self.q_get_repos, operation_name='getReposInit', variable_values=params)
elif page_info.startswith('repos_after'): # next page
Expand All @@ -65,33 +55,29 @@ def get_repos(self, page_info):
r = self.client.execute(self.q_get_repos, operation_name='getReposBefore', variable_values=params)
return r['repos']

def close_issue(self, issue_url, comment=''):
url = issue_url.replace('https://github.com', 'https://api.github.com/repos')
payload = {'state': 'closed', 'body': comment}
r = self.session.patch(url, headers=self.headers, json=payload)
return r

def reopen_issue(self, issue_url, comment=''):
url = issue_url.replace('https://github.com', 'https://api.github.com/repos')
payload = {'state': 'open', 'body': comment}
r = self.session.patch(url, headers=self.headers, json=payload)
return r.json(), r.status_code
def close_issue(self, issue_id, comment=''):
params = {'issueId': issue_id}
return self.client.execute(self.q_issue_actions, operation_name='CloseIssue', variable_values=params)

def get_issue(self, issue_url):
url = issue_url.replace('https://github.com', 'https://api.github.com/repos')
r = self.session.get(url, headers=self.headers)
return r.json(), r.status_code
def reopen_issue(self, issue_id, comment=''):
params = {'issueId': issue_id}
return self.client.execute(self.q_issue_actions, operation_name='ReopenIssue', variable_values=params)

def get_members(self, page):
data = {'sort': 'full_name', 'per_page': 9, 'page': page}
r = self.session.get(self.org_members_url, headers=self.headers, params=data)
return r.json()
def get_members(self, page_info):
params = {'org': self.settings.GH_ORGANIZATION_NICKNAME}
if page_info == 'members_start': # start page
r = self.client.execute(self.q_get_members, operation_name='GetMembersInit', variable_values=params)
elif page_info.startswith('members_after'): # next page
params['cursor'] = page_info.split('_')[2]
r = self.client.execute(self.q_get_members, operation_name='GetMembersAfter', variable_values=params)
else: # previous page
params['cursor'] = page_info.split('_')[2]
r = self.client.execute(self.q_get_members, operation_name='GetMembersBefore', variable_values=params)
return r['organization']['membersWithRole']

def set_assignee(self, issue_url, member_login, comment):
url = issue_url.replace('https://github.com', 'https://api.github.com/repos')
payload = {'assignees': [member_login], 'body': comment}
r = self.session.patch(url, headers=self.headers, json=payload)
return r
def set_assignee(self, issue_id, assign_to_id):
params = {'issueId': issue_id, 'assigneeIds': [assign_to_id]}
return self.client.execute(self.q_issue_actions, operation_name='SetIssueAssign', variable_values=params)

def add_to_scrum(self, node_id):
try:
Expand All @@ -111,7 +97,3 @@ def add_to_scrum(self, node_id):
logging.warning(f'''itemId={item_id} not set status. Reason: {r['errors']}''')
except Exception as err:
logging.error(f'Scrum adding FAILED: {err.args}')


class GithubIssueDisabledError(Exception):
pass
59 changes: 59 additions & 0 deletions src/graphql/get_members.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
query GetMembersInit($org: String!) {
organization(login: $org) {
membersWithRole(first: 9) {
pageInfo {
hasNextPage
endCursor
hasPreviousPage
startCursor
}
totalCount
edges {
node {
login
id
}
}
}
}
}

query GetMembersAfter($org: String!, $cursor: String!) {
organization(login: $org) {
membersWithRole(first: 9, after: $cursor) {
pageInfo {
hasNextPage
endCursor
hasPreviousPage
startCursor
}
totalCount
edges {
node {
login
id
}
}
}
}
}

query GetMembersBefore($org: String!, $cursor: String!) {
organization(login: $org) {
membersWithRole(last: 9, before: $cursor) {
pageInfo {
hasNextPage
endCursor
hasPreviousPage
startCursor
}
totalCount
edges {
node {
login
id
}
}
}
}
}
Loading

0 comments on commit dc7f99e

Please sign in to comment.