Add initial project structure with Knuddels API integration
Introduced the foundational codebase for a new Python wrapper to interact with the Knuddels.de API. This includes a .gitignore file to exclude virtual environments and compiled Python files, basic project metadata in pyproject.toml, and license information. Core functionality is added in the `api.py` file within a `Knuddels` class, enabling login, session handling, and GraphQL queries for message management. Accompanying `users.py` and `messages.py` modules define relevant data classes. Also set up a test script to verify login and data retrieval flows.
This commit is contained in:
commit
3473e6ed6d
10 changed files with 321 additions and 0 deletions
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
venv/
|
||||||
|
*.pyc
|
||||||
|
__pycache__/
|
0
LICENSE
Normal file
0
LICENSE
Normal file
0
README.md
Normal file
0
README.md
Normal file
23
pyproject.toml
Normal file
23
pyproject.toml
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
[build-system]
|
||||||
|
requires = ["hatchling"]
|
||||||
|
build-backend = "hatchling.build"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "pyknuddels"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = [
|
||||||
|
{ name="Kumi", email="pyknuddels@kumi.email" },
|
||||||
|
]
|
||||||
|
description = "Simple Python wrapper to fetch data from Knuddels.de"
|
||||||
|
readme = "README.md"
|
||||||
|
license = { file="LICENSE" }
|
||||||
|
requires-python = ">=3.10"
|
||||||
|
classifiers = [
|
||||||
|
"Programming Language :: Python :: 3",
|
||||||
|
"License :: OSI Approved :: MIT License",
|
||||||
|
"Operating System :: OS Independent",
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.urls]
|
||||||
|
"Homepage" = "https://kumig.it/kumitterer/pyknuddels"
|
||||||
|
"Bug Tracker" = "https://kumig.it/kumitterer/pyknuddels/issues"
|
0
src/pyknuddels/__init__.py
Normal file
0
src/pyknuddels/__init__.py
Normal file
0
src/pyknuddels/classes/__init__.py
Normal file
0
src/pyknuddels/classes/__init__.py
Normal file
147
src/pyknuddels/classes/api.py
Normal file
147
src/pyknuddels/classes/api.py
Normal file
File diff suppressed because one or more lines are too long
86
src/pyknuddels/classes/messages.py
Normal file
86
src/pyknuddels/classes/messages.py
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
from .users import User
|
||||||
|
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
|
||||||
|
class MessageContent:
|
||||||
|
text: str
|
||||||
|
|
||||||
|
def __init__(self, formatted_text: dict) -> None:
|
||||||
|
self.formatted_text = formatted_text
|
||||||
|
|
||||||
|
def get_text(self) -> str:
|
||||||
|
return self.formatted_text["text"]["text"]
|
||||||
|
|
||||||
|
def get_formatted_text(self) -> str:
|
||||||
|
return self.formatted_text
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, data: dict):
|
||||||
|
obj = cls(data["formattedText"])
|
||||||
|
return obj
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"<MessageContent formatted_text={self.formatted_text}>"
|
||||||
|
|
||||||
|
|
||||||
|
class Message:
|
||||||
|
sender: User
|
||||||
|
content: dict
|
||||||
|
|
||||||
|
def __init__(self, sender: User, content: MessageContent) -> None:
|
||||||
|
self.sender = sender
|
||||||
|
self.content = content
|
||||||
|
|
||||||
|
def get_sender(self) -> User:
|
||||||
|
return self.sender
|
||||||
|
|
||||||
|
def get_content(self) -> dict:
|
||||||
|
return self.content
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, data: dict):
|
||||||
|
obj = cls(User.from_dict(data["sender"]), MessageContent.from_dict(data["content"]))
|
||||||
|
|
||||||
|
return obj
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"<Message sender={self.sender} content={self.content}>"
|
||||||
|
|
||||||
|
class Conversation:
|
||||||
|
id: int
|
||||||
|
visibility: str
|
||||||
|
messages: List[Message] = []
|
||||||
|
otherParticipants: List[User] = []
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, id: int, messages: List[Message], other_participants: List[User]
|
||||||
|
) -> None:
|
||||||
|
self.id = id
|
||||||
|
self.messages = messages
|
||||||
|
self.other_participants = other_participants
|
||||||
|
|
||||||
|
def get_messages(self) -> List[Message]:
|
||||||
|
return self.messages
|
||||||
|
|
||||||
|
def get_other_participants(self) -> List[User]:
|
||||||
|
return self.other_participants
|
||||||
|
|
||||||
|
def get_other_participant(self) -> User:
|
||||||
|
return self.other_participants[0]
|
||||||
|
|
||||||
|
def get_message_count(self) -> int:
|
||||||
|
return len(self.messages)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, data: dict):
|
||||||
|
obj = cls(data["id"], [], [])
|
||||||
|
obj.visibility = data["visibility"]
|
||||||
|
|
||||||
|
for participant in data["otherParticipants"]:
|
||||||
|
obj.other_participants.append(User.from_dict(participant))
|
||||||
|
|
||||||
|
return obj
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"<Conversation id={self.id} visibility={self.visibility} other_participants={self.other_participants}>"
|
40
src/pyknuddels/classes/users.py
Normal file
40
src/pyknuddels/classes/users.py
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
class User:
|
||||||
|
id: int
|
||||||
|
nick: str
|
||||||
|
isOnline: bool
|
||||||
|
canSendImages: bool
|
||||||
|
menteeStatus: str
|
||||||
|
age: int
|
||||||
|
albumPhotosUrl: str
|
||||||
|
canReceiveMessages: bool
|
||||||
|
city: str
|
||||||
|
distance: Optional[int]
|
||||||
|
gender: str
|
||||||
|
currentOnlineChannelName: str
|
||||||
|
latestOnlineChannelName: str
|
||||||
|
lastOnlineTime: int
|
||||||
|
profilePicture: str
|
||||||
|
readMe: dict
|
||||||
|
relationshipStatus: str
|
||||||
|
sexualOrientation: str
|
||||||
|
onlineMinutes: int
|
||||||
|
isAppBot: bool
|
||||||
|
isLockedByAutomaticComplaint: bool
|
||||||
|
automaticComplaintCommand: str
|
||||||
|
isReportable: bool
|
||||||
|
interest: Optional[str]
|
||||||
|
latestClient: Optional[str] #?
|
||||||
|
authenticityClassification: str
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, data: dict):
|
||||||
|
obj = cls()
|
||||||
|
for key, value in data.items():
|
||||||
|
setattr(obj, key, value)
|
||||||
|
|
||||||
|
return obj
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<User id={self.id} nick={self.nick}>"
|
22
test.py
Normal file
22
test.py
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
|
logging.basicConfig(
|
||||||
|
format="[%(asctime)s] [%(levelname)s] %(message)s",
|
||||||
|
datefmt="%d.%m.%Y %H:%M:%S",
|
||||||
|
level=logging.INFO
|
||||||
|
)
|
||||||
|
|
||||||
|
from pyknuddels.classes.api import Knuddels
|
||||||
|
from pyknuddels.classes.messages import Conversation, Message
|
||||||
|
|
||||||
|
knuddels = Knuddels(os.environ["KN_USER"], os.environ["KN_PASSWORD"])
|
||||||
|
|
||||||
|
knuddels.login()
|
||||||
|
|
||||||
|
conversations = knuddels.messenger_overview(1)
|
||||||
|
|
||||||
|
for conversation in conversations:
|
||||||
|
obj = knuddels.get_conversation(conversation["id"])
|
||||||
|
obj = Conversation.from_dict(conversation)
|
||||||
|
print(obj)
|
Loading…
Reference in a new issue