Initial Commit
This commit is contained in:
commit
03fd706642
91
.gitignore
vendored
Normal file
91
.gitignore
vendored
Normal file
@ -0,0 +1,91 @@
|
||||
config.json
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
.idea/vcs.xml
|
||||
|
||||
# User-specific stuff
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/**/dictionaries
|
||||
.idea/**/shelf
|
||||
|
||||
# Sensitive or high-churn files
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
.idea/**/dbnavigator.xml
|
||||
|
||||
# Gradle
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# CMake
|
||||
cmake-build-debug/
|
||||
cmake-build-release/
|
||||
|
||||
# Mongo Explorer plugin
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
# File-based project format
|
||||
*.iws
|
||||
|
||||
# IntelliJ
|
||||
out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Cursive Clojure plugin
|
||||
.idea/replstate.xml
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
# Editor-based Rest Client
|
||||
.idea/httpRequests
|
||||
2
.idea/.gitignore
generated
vendored
Normal file
2
.idea/.gitignore
generated
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
# Default ignored files
|
||||
/workspace.xml
|
||||
11
.idea/geeksbot-matrix.iml
generated
Normal file
11
.idea/geeksbot-matrix.iml
generated
Normal file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="jdk" jdkName="Python 3.7 (geeksbot-matrix)" jdkType="Python SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
<component name="TestRunnerService">
|
||||
<option name="PROJECT_TEST_RUNNER" value="Unittests" />
|
||||
</component>
|
||||
</module>
|
||||
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
||||
7
.idea/misc.xml
generated
Normal file
7
.idea/misc.xml
generated
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="JavaScriptSettings">
|
||||
<option name="languageLevel" value="ES6" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.7 (geeksbot-matrix)" project-jdk-type="Python SDK" />
|
||||
</project>
|
||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/geeksbot-matrix.iml" filepath="$PROJECT_DIR$/.idea/geeksbot-matrix.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
25
.idea/watcherTasks.xml
generated
Normal file
25
.idea/watcherTasks.xml
generated
Normal file
@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectTasksOptions">
|
||||
<TaskOptions isEnabled="true">
|
||||
<option name="arguments" value="$FilePath$" />
|
||||
<option name="checkSyntaxErrors" value="true" />
|
||||
<option name="description" />
|
||||
<option name="exitCodeBehavior" value="ERROR" />
|
||||
<option name="fileExtension" value="py" />
|
||||
<option name="immediateSync" value="false" />
|
||||
<option name="name" value="black formatter" />
|
||||
<option name="output" value="$FilePath$" />
|
||||
<option name="outputFilters">
|
||||
<array />
|
||||
</option>
|
||||
<option name="outputFromStdout" value="false" />
|
||||
<option name="program" value="/usr/local/bin/black" />
|
||||
<option name="runOnExternalChanges" value="true" />
|
||||
<option name="scopeName" value="Project Files" />
|
||||
<option name="trackOnlyRoot" value="false" />
|
||||
<option name="workingDir" value="$ProjectFileDir$" />
|
||||
<envs />
|
||||
</TaskOptions>
|
||||
</component>
|
||||
</project>
|
||||
43
geeksbot.py
Normal file
43
geeksbot.py
Normal file
@ -0,0 +1,43 @@
|
||||
import json
|
||||
import asyncio
|
||||
from nio import (AsyncClient, RoomMessageText)
|
||||
import logging
|
||||
|
||||
log_format = '%(asctime)s ||| %(name)s | %(levelname)s | %(message)s'
|
||||
logging.basicConfig(format=log_format, level=logging.INFO)
|
||||
logger = logging.getLogger('geeksbot')
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
|
||||
class Geeksbot(AsyncClient):
|
||||
def __init__(self, base_url, username, password):
|
||||
self.base_url = base_url
|
||||
self.username = username
|
||||
self.password = password
|
||||
super(Geeksbot, self).__init__(self.base_url, self.username)
|
||||
self.add_event_callback(self.on_message, RoomMessageText)
|
||||
|
||||
async def login(self):
|
||||
await super(Geeksbot, self).login(self.password)
|
||||
|
||||
async def on_message(self, room, event):
|
||||
logger.info(f'Message recieved for room {room.display_name} | {room.user_name(event.sender)}: {event.body}')
|
||||
if event.body.startswith('$say '):
|
||||
msg = {
|
||||
"body": event.body.split(' ', 1)[1],
|
||||
"msgtype": 'm.text'
|
||||
}
|
||||
print(msg)
|
||||
resp = await self.room_send(room.room_id, 'm.room.message', msg)
|
||||
print(resp)
|
||||
|
||||
|
||||
async def main():
|
||||
with open('config.json') as f:
|
||||
config = json.load(f)
|
||||
client = Geeksbot(**config)
|
||||
|
||||
await client.login()
|
||||
await client.sync_forever(timeout=1000)
|
||||
|
||||
asyncio.get_event_loop().run_until_complete(main())
|
||||
0
lib/__init__.py
Normal file
0
lib/__init__.py
Normal file
167
lib/http.py
Normal file
167
lib/http.py
Normal file
@ -0,0 +1,167 @@
|
||||
import json
|
||||
from typing import Union
|
||||
import uuid
|
||||
import aiohttp
|
||||
from aiohttp.client_exceptions import ClientConnectionError
|
||||
import asyncio
|
||||
from urllib.parse import quote, urlencode, urlparse
|
||||
from dataclasses import dataclass
|
||||
|
||||
MATRIX_API = "/_matrix/client/r0"
|
||||
MATRIX_MEDIA = "/_matrix/media/r0"
|
||||
|
||||
|
||||
@dataclass
|
||||
class HTTPConfig:
|
||||
max_retry: int = 10
|
||||
max_wait_time: int = 3600
|
||||
backoff_factor: float = 0.1
|
||||
ssl: bool = None
|
||||
proxy: str = None
|
||||
|
||||
|
||||
class HTTP:
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
base_url: str,
|
||||
username: str,
|
||||
password: str = None,
|
||||
token: str = None,
|
||||
device_id: str = None,
|
||||
device_name: str = None,
|
||||
config: HTTPConfig = HTTPConfig(),
|
||||
):
|
||||
self.base_url = base_url
|
||||
self.username = username
|
||||
self.password = password
|
||||
self.token = token
|
||||
self.device_id = device_id
|
||||
self.device_name = device_name
|
||||
self.access_token = None
|
||||
self.config = config
|
||||
self.client_session = aiohttp.ClientSession()
|
||||
|
||||
def build_url(
|
||||
self, endpoint: str, request_type: str = None, query: dict = None
|
||||
) -> str:
|
||||
path = f'{MATRIX_MEDIA if request_type == "MEDIA" else MATRIX_API}/{endpoint}'
|
||||
path = self.base_url + quote(path)
|
||||
if query:
|
||||
path += f"?{urlencode(query)}"
|
||||
return path
|
||||
|
||||
def get_wait_time(self, num_timeouts: int) -> float:
|
||||
if num_timeouts <= 2:
|
||||
return 0.0
|
||||
|
||||
return min(
|
||||
self.config.backoff_factor * (2 ** (num_timeouts - 1)),
|
||||
self.config.max_wait_time,
|
||||
)
|
||||
|
||||
async def close(self):
|
||||
if self.client_session:
|
||||
await self.client_session.close()
|
||||
self.client_session = None
|
||||
|
||||
async def _send(
|
||||
self, method: str, path: str, data: dict = None, headers: dict = {}
|
||||
) -> Union[dict, bytes]:
|
||||
if not self.client_session:
|
||||
self.client_session = aiohttp.ClientSession()
|
||||
|
||||
raw_resp = await self.client_session.request(
|
||||
method,
|
||||
path,
|
||||
json=data,
|
||||
ssl=self.config.ssl,
|
||||
proxy=self.config.proxy,
|
||||
headers=headers,
|
||||
)
|
||||
if raw_resp.content_type == "application/json":
|
||||
return await raw_resp.json()
|
||||
else:
|
||||
return await raw_resp.read()
|
||||
|
||||
async def send(
|
||||
self, method: str, path: str, data: dict = None, content_type: str = None
|
||||
) -> dict:
|
||||
if not self.access_token:
|
||||
raise RuntimeError("Client is not logged in")
|
||||
|
||||
headers = {
|
||||
"Authorization": f"Bearer {self.access_token}",
|
||||
"content_type": content_type or "application/json",
|
||||
}
|
||||
|
||||
timeouts = 0
|
||||
|
||||
for _ in range(self.config.max_retry or 1):
|
||||
try:
|
||||
resp = await self._send(method, path, data, headers)
|
||||
|
||||
if isinstance(resp, bytes):
|
||||
break
|
||||
|
||||
if resp.get("retry_after_ms"):
|
||||
await asyncio.sleep(resp["retry_after_ms"] / 1000)
|
||||
else:
|
||||
break
|
||||
except (asyncio.TimeoutError, ClientConnectionError, TimeoutError):
|
||||
timeouts += 1
|
||||
await asyncio.sleep(self.get_wait_time(timeouts))
|
||||
else:
|
||||
raise RuntimeWarning(f"Max retries reached for {method} - {path} | {data}")
|
||||
|
||||
return resp
|
||||
|
||||
async def login(self):
|
||||
path = self.build_url("login")
|
||||
|
||||
data = {}
|
||||
if self.password:
|
||||
data = {
|
||||
"type": "m.login.password",
|
||||
"identifier": {"user": self.username, "type": "m.id.user"},
|
||||
"password": self.password,
|
||||
}
|
||||
elif self.token:
|
||||
data = {"type": "m.login.token", "token": self.token}
|
||||
if self.device_id:
|
||||
data["device_id"] = self.device_id
|
||||
if self.device_name:
|
||||
data["device_name"] = self.device_name
|
||||
|
||||
headers = {"content_type": "application/json"}
|
||||
resp = await self._send("post", path, data=data, headers=headers)
|
||||
self.access_token = resp.get("access_token")
|
||||
self.device_id = resp.get("device_id")
|
||||
return resp
|
||||
|
||||
async def logout(self):
|
||||
path = self.build_url("logout")
|
||||
await self.send("POST", path)
|
||||
self.access_token = None
|
||||
|
||||
async def logout_all(self):
|
||||
path = self.build_url("logout/all")
|
||||
await self.send("POST", path)
|
||||
self.access_token = None
|
||||
|
||||
async def room_send(self, room_id: str, event_type: str, content: dict):
|
||||
if room_id.startswith("!") and ":" in room_id:
|
||||
path = self.build_url(f"rooms/{room_id}/send/{event_type}/{uuid.uuid4()}")
|
||||
elif room_id.startswith("#") and ":" in room_id:
|
||||
path = self.build_url(f"directory/room/{room_id}")
|
||||
resp = await self.send("GET", path)
|
||||
if resp.get("room_id"):
|
||||
path = self.build_url(
|
||||
f'rooms/{resp["room_id"]}/send/{event_type}/{uuid.uuid4()}'
|
||||
)
|
||||
else:
|
||||
raise RuntimeWarning(resp)
|
||||
else:
|
||||
raise RuntimeWarning(f"{room_id} is not a valid room id or alias")
|
||||
|
||||
return await self.send("PUT", path, data=content)
|
||||
Loading…
x
Reference in New Issue
Block a user