Add filters, backups, use AwareDateTimeField

This commit is contained in:
Zeva Rose 2022-10-02 01:18:28 -06:00
parent fe24fce330
commit 470e999c68
16 changed files with 610 additions and 466 deletions

View file

@ -1,3 +1,3 @@
{
"python.formatting.provider": "black"
{
"python.formatting.provider": "black"
}

26
LICENSE
View file

@ -1,13 +1,13 @@
“Commons Clause” License Condition v1.0
The Software is provided to you by the Licensor under the License, as defined below, subject to the following condition.
Without limiting other conditions in the License, the grant of rights under the License will not include, and the License does not grant to you, the right to Sell the Software.
For purposes of the foregoing, “Sell” means practicing any or all of the rights granted to you under the License to provide to third parties, for a fee or other consideration (including without limitation fees for hosting or consulting/ support services related to the Software), a product or service whose value derives, entirely or substantially, from the functionality of the Software. Any license notice or attribution required by the License must also include this Commons Clause License Condition notice.
Software: JARVIS
License: Expat License
Licensor: zevaryx
“Commons Clause” License Condition v1.0
The Software is provided to you by the Licensor under the License, as defined below, subject to the following condition.
Without limiting other conditions in the License, the grant of rights under the License will not include, and the License does not grant to you, the right to Sell the Software.
For purposes of the foregoing, “Sell” means practicing any or all of the rights granted to you under the License to provide to third parties, for a fee or other consideration (including without limitation fees for hosting or consulting/ support services related to the Software), a product or service whose value derives, entirely or substantially, from the functionality of the Software. Any license notice or attribution required by the License must also include this Commons Clause License Condition notice.
Software: JARVIS
License: Expat License
Licensor: zevaryx

View file

@ -1,55 +1,55 @@
# Privacy Policy
Your privacy is important to us. It is JARVIS' policy to respect your privacy and comply with any applicable law and regulation regarding any personal information we may collect about you through our JARVIS bot.
This policy is effective as of 20 March 2022 and was last updated on 10 March 2022.
## Information We Collect
Information we collect includes both information you knowingly and actively provide us when using or participating in any of our services and promotions, and any information automatically sent by your devices in the course of accessing our products and services.
## Log Data
When you use our JARVIS services, if opted in to usage data collection services, we may collect data in certain cicumstances:
- Administrative activity (i.e. ban, warn, mute)
- Your User ID (either as admin or as the recipient of the activity)
- Guild ID of activity origin
- Your discriminator at time of activity (bans only)
- Your username at time of activity (bans only)
- Admin commands
- User ID of admin who executes admin command
- Reminders
- Your User ID
- The guild in which the command originated
- The channel in which the command originated
- Private text entered via the command
- Starboard
- Message ID of starred message
- Channel ID of starred message
- Guild ID of origin guild
- Automated activity logging
- We store no information about users who edit nicknames, join/leave servers, or other related activities. However, this information, if configured by server admins, is relayed into a Discord channel and is not automatically deleted, nor do we have control over this information.
- This information is also stored by Discord via their Audit Log, which we also have no control over. Please contact Discord for their own privacy policy and asking about your rights on their platform.
## Use of Information
We use the information we collect to provide, maintain, and improve our services. Common uses where this data may be used includes sending reminders, helping administrate Discord servers, and providing extra utilities into Discord based on user content.
## Security of Your Personal Information
Although we will do our best to protect the personal information you provide to us, we advise that no method of electronic transmission or storage is 100% secure, and no one can guarantee absolute data security. We will comply with laws applicable to us in respect of any data breach.
## How Long We Keep Your Personal Information
We keep your personal information only for as long as we need to. This time period may depend on what we are using your information for, in accordance with this privacy policy. If your personal information is no longer required, we will delete it or make it anonymous by removing all details that identify you.
## Your Rights and Controlling Your Personal Information
You may request access to your personal information, and change what you are okay with us collecting from you. You may also request that we delete your personal identifying information. Please message **zevaryx#5779** on Discord, or join the Discord server at https://discord.gg/4TuFvW5n and ask in there.
## Limits of Our Policy
Our website may link to external sites that are not operated by us (ie. discord.com). Please be aware that we have no control over the content and policies of those sites, and cannot accept responsibility or liability for their respective privacy practices.
## Changes to This Policy
At our discretion, we may change our privacy policy to reflect updates to our business processes, current acceptable practices, or legislative or regulatory changes. If we decide to change this privacy policy, we will post the changes here at the same link by which you are accessing this privacy policy.
## Contact Us
For any questions or concerns regarding your privacy, you may contact us using the following details:
### Discord
#### zevaryx#5779
#### https://discord.gg/4TuFvW5n
# Privacy Policy
Your privacy is important to us. It is JARVIS' policy to respect your privacy and comply with any applicable law and regulation regarding any personal information we may collect about you through our JARVIS bot.
This policy is effective as of 20 March 2022 and was last updated on 10 March 2022.
## Information We Collect
Information we collect includes both information you knowingly and actively provide us when using or participating in any of our services and promotions, and any information automatically sent by your devices in the course of accessing our products and services.
## Log Data
When you use our JARVIS services, if opted in to usage data collection services, we may collect data in certain cicumstances:
- Administrative activity (i.e. ban, warn, mute)
- Your User ID (either as admin or as the recipient of the activity)
- Guild ID of activity origin
- Your discriminator at time of activity (bans only)
- Your username at time of activity (bans only)
- Admin commands
- User ID of admin who executes admin command
- Reminders
- Your User ID
- The guild in which the command originated
- The channel in which the command originated
- Private text entered via the command
- Starboard
- Message ID of starred message
- Channel ID of starred message
- Guild ID of origin guild
- Automated activity logging
- We store no information about users who edit nicknames, join/leave servers, or other related activities. However, this information, if configured by server admins, is relayed into a Discord channel and is not automatically deleted, nor do we have control over this information.
- This information is also stored by Discord via their Audit Log, which we also have no control over. Please contact Discord for their own privacy policy and asking about your rights on their platform.
## Use of Information
We use the information we collect to provide, maintain, and improve our services. Common uses where this data may be used includes sending reminders, helping administrate Discord servers, and providing extra utilities into Discord based on user content.
## Security of Your Personal Information
Although we will do our best to protect the personal information you provide to us, we advise that no method of electronic transmission or storage is 100% secure, and no one can guarantee absolute data security. We will comply with laws applicable to us in respect of any data breach.
## How Long We Keep Your Personal Information
We keep your personal information only for as long as we need to. This time period may depend on what we are using your information for, in accordance with this privacy policy. If your personal information is no longer required, we will delete it or make it anonymous by removing all details that identify you.
## Your Rights and Controlling Your Personal Information
You may request access to your personal information, and change what you are okay with us collecting from you. You may also request that we delete your personal identifying information. Please message **zevaryx#5779** on Discord, or join the Discord server at https://discord.gg/4TuFvW5n and ask in there.
## Limits of Our Policy
Our website may link to external sites that are not operated by us (ie. discord.com). Please be aware that we have no control over the content and policies of those sites, and cannot accept responsibility or liability for their respective privacy practices.
## Changes to This Policy
At our discretion, we may change our privacy policy to reflect updates to our business processes, current acceptable practices, or legislative or regulatory changes. If we decide to change this privacy policy, we will post the changes here at the same link by which you are accessing this privacy policy.
## Contact Us
For any questions or concerns regarding your privacy, you may contact us using the following details:
### Discord
#### zevaryx#5779
#### https://discord.gg/4TuFvW5n

View file

@ -1,28 +1,28 @@
===========
jarvis-core
===========
Core functionality of `JARVIS <https://git.zevaryx.com/stark-industries/j.a.r.v.i.s.>`_
Modules
=======
``jarvis_core.config``
----------------------
Configuration management
``jarvis_core.db``
------------------
Database connectors and model implementations
``jarvis_core.filters``
-----------------------
Regex filters for various functions
``jarvis_core.util``
--------------------
Quality of life utilities
===========
jarvis-core
===========
Core functionality of `JARVIS <https://git.zevaryx.com/stark-industries/j.a.r.v.i.s.>`_
Modules
=======
``jarvis_core.config``
----------------------
Configuration management
``jarvis_core.db``
------------------
Database connectors and model implementations
``jarvis_core.filters``
-----------------------
Regex filters for various functions
``jarvis_core.util``
--------------------
Quality of life utilities

View file

@ -1,15 +1,15 @@
# Terms Of Use
Please just be reasonable do not try to mess with the bot in a malicious way. This includes but is not limited to:
- Spamming
- Flooding
- Hacking
- DOS Attacks
## Contact Us
For any questions or concerns regarding the terms, feel free to contact us:
### Discord
#### zevaryx#5779
#### https://discord.gg/4TuFvW5n
# Terms Of Use
Please just be reasonable do not try to mess with the bot in a malicious way. This includes but is not limited to:
- Spamming
- Flooding
- Hacking
- DOS Attacks
## Contact Us
For any questions or concerns regarding the terms, feel free to contact us:
### Discord
#### zevaryx#5779
#### https://discord.gg/4TuFvW5n

26
jarvis_core/db/fields.py Normal file
View file

@ -0,0 +1,26 @@
import bson
import marshmallow as ma
from marshmallow import fields as ma_fields
from umongo import fields
class BinaryField(fields.BaseField, ma_fields.Field):
default_error_messages = {"invalid": "Not a valid byte sequence."}
def _serialize(self, value, attr, data, **kwargs):
return bytes(value)
def _deserialize(self, value, attr, data, **kwargs):
if not isinstance(value, bytes):
self.fail("invalid")
return value
def _serialize_to_mongo(self, obj):
return bson.binary.Binary(obj)
def _deserialize_from_mongo(self, value):
return bytes(value)
class RawField(fields.BaseField, ma_fields.Raw):
pass

View file

@ -1,29 +1,19 @@
"""JARVIS database models."""
from datetime import datetime, timezone
import re
from datetime import datetime
from typing import Any, List
import marshmallow as ma
from umongo import Document, EmbeddedDocument, fields
from jarvis_core.db import CTC2_INST, JARVIS_INST
from jarvis_core.db.models.actions import Ban, Kick, Mute, Unban, Warning
from jarvis_core.db.models.modlog import Action, Modlog, Note
from jarvis_core.db.models.reddit import (
Redditor,
RedditorFollow,
Subreddit,
SubredditFollow,
)
from jarvis_core.db.models.twitter import TwitterAccount, TwitterFollow
def get_now() -> datetime:
"""Get proper timestamp."""
return datetime.now(tz=timezone.utc)
class RawField(fields.BaseField, ma.fields.Raw):
pass
from jarvis_core.db.fields import RawField
from jarvis_core.db.models.actions import *
from jarvis_core.db.models.backups import *
from jarvis_core.db.models.modlog import *
from jarvis_core.db.models.reddit import *
from jarvis_core.db.models.twitter import *
from jarvis_core.db.utils import get_now
@JARVIS_INST.register
@ -32,7 +22,7 @@ class Autopurge(Document):
channel: int = fields.IntegerField(required=True)
delay: int = fields.IntegerField(default=30)
admin: int = fields.IntegerField(required=True)
created_at: datetime = fields.DateTimeField(default=datetime.now)
created_at: datetime = fields.AwareDateTimeField(default=get_now)
@JARVIS_INST.register
@ -42,7 +32,7 @@ class Autoreact(Document):
reactions: List[str] = fields.ListField(fields.StringField())
admin: int = fields.IntegerField(required=True)
thread: bool = fields.BooleanField(default=True)
created_at: datetime = fields.DateTimeField(default=datetime.now)
created_at: datetime = fields.AwareDateTimeField(default=get_now)
@JARVIS_INST.register
@ -53,6 +43,22 @@ class Config(Document):
value: Any = RawField(required=True)
@JARVIS_INST.register
class Filter(Document):
"""Regex Filter database object."""
def _validate_filters(value):
for v in value:
try:
re.compile(v)
except re.error:
raise ValueError(f"Invalid regex: {v}")
guild: int = fields.IntegerField(required=True)
name: str = fields.StringField(required=True)
filters: List[str] = fields.ListField(fields.StringField(), validate=[_validate_filters])
@CTC2_INST.register
class Guess(Document):
"""Guess database object."""
@ -82,7 +88,7 @@ class Lock(Document):
guild: int = fields.IntegerField(required=True)
reason: str = fields.StringField(required=True)
original_perms: Permission = fields.EmbeddedField(Permission, required=False)
created_at: datetime = fields.DateTimeField(default=get_now)
created_at: datetime = fields.AwareDateTimeField(default=get_now)
@JARVIS_INST.register
@ -95,7 +101,7 @@ class Lockdown(Document):
guild: int = fields.IntegerField(required=True)
reason: str = fields.StringField(required=True)
original_perms: int = fields.IntegerField(required=True)
created_at: datetime = fields.DateTimeField(default=get_now)
created_at: datetime = fields.AwareDateTimeField(default=get_now)
@JARVIS_INST.register
@ -106,7 +112,7 @@ class Purge(Document):
channel: int = fields.IntegerField(required=True)
guild: int = fields.IntegerField(required=True)
count: int = fields.IntegerField(default=10)
created_at: datetime = fields.DateTimeField(default=get_now)
created_at: datetime = fields.AwareDateTimeField(default=get_now)
@JARVIS_INST.register
@ -118,8 +124,8 @@ class Reminder(Document):
guild: int = fields.IntegerField(required=True)
channel: int = fields.IntegerField(required=True)
message: str = fields.StringField(required=True)
remind_at: datetime = fields.DateTimeField(required=True)
created_at: datetime = fields.DateTimeField(default=get_now)
remind_at: datetime = fields.AwareDateTimeField(required=True)
created_at: datetime = fields.AwareDateTimeField(default=get_now)
private: bool = fields.BooleanField(default=False)
@ -148,7 +154,7 @@ class Roleping(Document):
guild: int = fields.IntegerField(required=True)
admin: int = fields.IntegerField(required=True)
bypass: Bypass = fields.EmbeddedField(Bypass)
created_at: datetime = fields.DateTimeField(default=get_now)
created_at: datetime = fields.AwareDateTimeField(default=get_now)
@JARVIS_INST.register
@ -172,7 +178,7 @@ class Star(Document):
guild: int = fields.IntegerField(required=True)
admin: int = fields.IntegerField(required=True)
star: int = fields.IntegerField(required=True)
created_at: datetime = fields.DateTimeField(default=get_now)
created_at: datetime = fields.AwareDateTimeField(default=get_now)
@JARVIS_INST.register
@ -182,7 +188,7 @@ class Starboard(Document):
channel: int = fields.IntegerField(required=True)
guild: int = fields.IntegerField(required=True)
admin: int = fields.IntegerField(required=True)
created_at: datetime = fields.DateTimeField(default=get_now)
created_at: datetime = fields.AwareDateTimeField(default=get_now)
@JARVIS_INST.register
@ -190,9 +196,9 @@ class Tag(Document):
"""Tag database object."""
creator: int = fields.IntegerField(required=True)
created_at: datetime = fields.DateTimeField(default=get_now)
created_at: datetime = fields.AwareDateTimeField(default=get_now)
editor: int = fields.IntegerField()
edited_at: datetime = fields.DateTimeField()
edited_at: datetime = fields.AwareDateTimeField()
name: str = fields.StringField(required=True)
content: str = fields.StringField(required=True)
guild: int = fields.IntegerField(required=True)
@ -206,8 +212,8 @@ class Temprole(Document):
user: int = fields.IntegerField(required=True)
role: int = fields.IntegerField(required=True)
admin: int = fields.IntegerField(required=True)
expires_at: datetime = fields.DateTimeField(required=True)
created_at: datetime = fields.DateTimeField(default=get_now)
expires_at: datetime = fields.AwareDateTimeField(required=True)
created_at: datetime = fields.AwareDateTimeField(default=get_now)
@JARVIS_INST.register

View file

@ -4,11 +4,7 @@ from datetime import datetime, timezone
from umongo import Document, fields
from jarvis_core.db import JARVIS_INST
def get_now() -> datetime:
"""Get proper timestamp."""
return datetime.now(tz=timezone.utc)
from jarvis_core.db.utils import get_now
@JARVIS_INST.register
@ -22,7 +18,7 @@ class Ban(Document):
guild: int = fields.IntegerField(required=True)
type: str = fields.StringField(default="perm")
reason: str = fields.StringField(required=True)
created_at: datetime = fields.DateTimeField(default=get_now)
created_at: datetime = fields.AwareDateTimeField(default=get_now)
@JARVIS_INST.register
@ -33,7 +29,7 @@ class Kick(Document):
guild: int = fields.IntegerField(required=True)
reason: str = fields.StringField(required=True)
user: int = fields.IntegerField(required=True)
created_at: datetime = fields.DateTimeField(default=get_now)
created_at: datetime = fields.AwareDateTimeField(default=get_now)
@JARVIS_INST.register
@ -46,7 +42,7 @@ class Mute(Document):
duration: int = fields.IntegerField(default=10)
guild: int = fields.IntegerField(required=True)
reason: str = fields.StringField(required=True)
created_at: datetime = fields.DateTimeField(default=get_now)
created_at: datetime = fields.AwareDateTimeField(default=get_now)
@JARVIS_INST.register
@ -59,7 +55,7 @@ class Unban(Document):
guild: int = fields.IntegerField(required=True)
admin: int = fields.IntegerField(required=True)
reason: str = fields.StringField(required=True)
created_at: datetime = fields.DateTimeField(default=get_now)
created_at: datetime = fields.AwareDateTimeField(default=get_now)
@JARVIS_INST.register
@ -72,5 +68,5 @@ class Warning(Document):
guild: int = fields.IntegerField(required=True)
duration: int = fields.IntegerField(default=24)
reason: str = fields.StringField(required=True)
expires_at: datetime = fields.DateTimeField(required=True)
created_at: datetime = fields.DateTimeField(default=get_now)
expires_at: datetime = fields.AwareDateTimeField(required=True)
created_at: datetime = fields.AwareDateTimeField(default=get_now)

View file

@ -0,0 +1,122 @@
from datetime import datetime
from typing import List, Optional
from umongo import Document, EmbeddedDocument, fields
from jarvis_core import __version__
from jarvis_core.db import JARVIS_INST
from jarvis_core.db.fields import BinaryField
from jarvis_core.db.utils import get_id, get_now
@JARVIS_INST.register
class Image(Document):
discord_id: int = fields.IntegerField(unique=True)
image_data: List[bytes] = BinaryField()
image_ext: str = fields.StringField()
created_at: datetime = fields.AwareDateTimeField(default=get_now)
@JARVIS_INST.register
class PermissionOverwriteBackup(EmbeddedDocument):
id: int = fields.IntegerField()
type: int = fields.IntegerField()
allow: int = fields.IntegerField()
deny: int = fields.IntegerField()
@JARVIS_INST.register
class WebhookBackup(EmbeddedDocument):
id: int = fields.IntegerField()
channel_id: int = fields.IntegerField()
type: int = fields.IntegerField()
avatar: Image = fields.ReferenceField(Image)
name: str = fields.StringField()
@JARVIS_INST.register
class ChannelBackup(EmbeddedDocument):
id: int = fields.IntegerField()
name: str = fields.StringField()
type: int = fields.IntegerField()
position: int = fields.IntegerField()
topic: Optional[str] = fields.StringField(default=None)
nsfw: bool = fields.BooleanField(default=False)
rate_limit_per_user: int = fields.IntegerField(default=None)
bitrate: Optional[int] = fields.IntegerField(default=None)
user_limit: Optional[int] = fields.IntegerField(default=None)
permission_overwrites: List[PermissionOverwriteBackup] = fields.ListField(
fields.EmbeddedField(PermissionOverwriteBackup), factory=list
)
parent_id: Optional[int] = fields.IntegerField(default=None)
rtc_region: Optional[str] = fields.StringField(default=None)
video_quality_mode: Optional[int] = fields.IntegerField(default=None)
default_auto_archive_duration: Optional[int] = fields.IntegerField(default=None)
webhooks: List[WebhookBackup] = fields.ListField(
fields.EmbeddedField(WebhookBackup), factory=list
)
@JARVIS_INST.register
class RoleBackup(EmbeddedDocument):
id: int = fields.IntegerField()
name: str = fields.StringField()
permissions: int = fields.IntegerField()
color: str = fields.StringField()
hoist: bool = fields.BooleanField()
mentionable: bool = fields.BooleanField()
@JARVIS_INST.register
class EmojiBackup(EmbeddedDocument):
id: int = fields.IntegerField()
name: str = fields.StringField()
image: Image = fields.ReferenceField(Image)
@JARVIS_INST.register
class StickerBackup(EmbeddedDocument):
id: int = fields.IntegerField()
name: str = fields.StringField()
format_type: int = fields.IntegerField()
tags: str = fields.StringField()
type: int = fields.IntegerField()
image: Image = fields.ReferenceField(Image)
@JARVIS_INST.register
class GuildBackup(EmbeddedDocument):
name: str = fields.StringField(required=True)
description: str = fields.StringField(default=None)
default_message_notifications: Optional[int] = fields.IntegerField(default=None)
explicit_content_filter: Optional[int] = fields.IntegerField(default=None)
afk_channel: Optional[int] = fields.IntegerField(default=None)
afk_timeout: Optional[int] = fields.IntegerField(default=None)
icon: Optional[Image] = fields.ReferenceField(Image, default=None)
owner: int = fields.IntegerField(required=True)
splash: Optional[Image] = fields.ReferenceField(Image, default=None)
discovery_splash: Optional[Image] = fields.ReferenceField(Image, default=None)
banner: Optional[Image] = fields.ReferenceField(Image, default=None)
system_channel: Optional[int] = fields.IntegerField(default=None)
system_channel_flags: Optional[int] = fields.IntegerField(default=None)
rules_channel: Optional[int] = fields.IntegerField(default=None)
public_updates_channel: Optional[int] = fields.IntegerField(default=None)
preferred_locale: Optional[str] = fields.StringField(default=None)
features: List[str] = fields.ListField(fields.StringField, factory=list)
channels: List[ChannelBackup] = fields.ListField(
fields.EmbeddedField(ChannelBackup), factory=list
)
roles: List[RoleBackup] = fields.ListField(fields.EmbeddedField(RoleBackup), factory=list)
emojis: List[EmojiBackup] = fields.ListField(fields.EmbeddedField(EmojiBackup), factory=list)
stickers: List[StickerBackup] = fields.ListField(
fields.EmbeddedField(StickerBackup), factory=list
)
@JARVIS_INST.register
class Backup(Document):
created_at: datetime = fields.AwareDateTimeField(default=get_now)
guild_id: int = fields.IntegerField()
bkid: str = fields.StringField(default=get_id)
guild: GuildBackup = fields.EmbeddedField(GuildBackup, required=True)
version: str = fields.StringField(default=__version__)

View file

@ -2,24 +2,11 @@
from datetime import datetime, timezone
from typing import List
import nanoid
from bson import ObjectId
from umongo import Document, EmbeddedDocument, fields
from jarvis_core.db import JARVIS_INST
NANOID_ALPHA = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
def get_now() -> datetime:
"""Get proper timestamp."""
return datetime.now(tz=timezone.utc)
def get_id() -> str:
"""Get nanoid."""
return nanoid.generate(NANOID_ALPHA, 12)
from jarvis_core.db.utils import get_id, get_now
@JARVIS_INST.register
class Action(EmbeddedDocument):
@ -36,7 +23,7 @@ class Note(EmbeddedDocument):
admin: int = fields.IntegerField(required=True)
content: str = fields.StrField(required=True)
created_at: datetime = fields.DateTimeField(default=get_now)
created_at: datetime = fields.AwareDateTimeField(default=get_now)
@JARVIS_INST.register
@ -49,5 +36,5 @@ class Modlog(Document):
admin: int = fields.IntegerField(required=True)
actions: List[Action] = fields.ListField(fields.EmbeddedField(Action), factory=list)
open: bool = fields.BoolField(default=True)
created_at: datetime = fields.DateTimeField(default=get_now)
created_at: datetime = fields.AwareDateTimeField(default=get_now)
notes: List[Note] = fields.ListField(fields.EmbeddedField(Note), factory=list)

View file

@ -1,56 +1,52 @@
"""Reddit databaes models."""
from datetime import datetime, timezone
from umongo import Document, fields
from jarvis_core.db import JARVIS_INST
def get_now() -> datetime:
"""Get proper timestamp."""
return datetime.now(tz=timezone.utc)
@JARVIS_INST.register
class Subreddit(Document):
"""Subreddit object."""
display_name: str = fields.StringField(required=True)
over18: bool = fields.BooleanField(default=False)
@JARVIS_INST.register
class SubredditFollow(Document):
"""Subreddit Follow object."""
active: bool = fields.BooleanField(default=True)
display_name: str = fields.StringField(required=True)
channel: int = fields.IntegerField(required=True)
guild: int = fields.IntegerField(required=True)
admin: int = fields.IntegerField(required=True)
created_at: datetime = fields.DateTimeField(default=get_now)
class Meta:
collection_name = "subredditfollow"
@JARVIS_INST.register
class Redditor(Document):
"""Reddit User object."""
name: str = fields.StringField(required=True)
@JARVIS_INST.register
class RedditorFollow(Document):
"""Reditor Follow object."""
active: bool = fields.BooleanField(default=True)
name: str = fields.StringField(required=True)
channel: int = fields.IntegerField(required=True)
guild: int = fields.IntegerField(required=True)
admin: int = fields.IntegerField(required=True)
created_at: datetime = fields.DateTimeField(default=get_now)
class Meta:
collection_name = "redditorfollow"
"""Reddit databaes models."""
from datetime import datetime, timezone
from umongo import Document, fields
from jarvis_core.db import JARVIS_INST
from jarvis_core.db.utils import get_now
@JARVIS_INST.register
class Subreddit(Document):
"""Subreddit object."""
display_name: str = fields.StringField(required=True)
over18: bool = fields.BooleanField(default=False)
@JARVIS_INST.register
class SubredditFollow(Document):
"""Subreddit Follow object."""
active: bool = fields.BooleanField(default=True)
display_name: str = fields.StringField(required=True)
channel: int = fields.IntegerField(required=True)
guild: int = fields.IntegerField(required=True)
admin: int = fields.IntegerField(required=True)
created_at: datetime = fields.AwareDateTimeField(default=get_now)
class Meta:
collection_name = "subredditfollow"
@JARVIS_INST.register
class Redditor(Document):
"""Reddit User object."""
name: str = fields.StringField(required=True)
@JARVIS_INST.register
class RedditorFollow(Document):
"""Reditor Follow object."""
active: bool = fields.BooleanField(default=True)
name: str = fields.StringField(required=True)
channel: int = fields.IntegerField(required=True)
guild: int = fields.IntegerField(required=True)
admin: int = fields.IntegerField(required=True)
created_at: datetime = fields.AwareDateTimeField(default=get_now)
class Meta:
collection_name = "redditorfollow"

View file

@ -4,11 +4,7 @@ from datetime import datetime, timezone
from umongo import Document, fields
from jarvis_core.db import JARVIS_INST
def get_now() -> datetime:
"""Get proper timestamp."""
return datetime.now(tz=timezone.utc)
from jarvis_core.db.utils import get_now
@JARVIS_INST.register
@ -18,7 +14,7 @@ class TwitterAccount(Document):
handle: str = fields.StringField(required=True)
twitter_id: int = fields.IntegerField(required=True)
last_tweet: int = fields.IntegerField(required=True)
last_sync: datetime = fields.DateTimeField(default=get_now)
last_sync: datetime = fields.AwareDateTimeField(default=get_now)
class Meta:
collection_name = "twitteraccount"
@ -34,7 +30,7 @@ class TwitterFollow(Document):
guild: int = fields.IntegerField(required=True)
retweets: bool = fields.BooleanField(default=True)
admin: int = fields.IntegerField(required=True)
created_at: datetime = fields.DateTimeField(default=get_now)
created_at: datetime = fields.AwareDateTimeField(default=get_now)
class Meta:
collection_name = "twitterfollow"

15
jarvis_core/db/utils.py Normal file
View file

@ -0,0 +1,15 @@
from datetime import datetime, timezone
import nanoid
NANOID_ALPHA = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
def get_now() -> datetime:
"""Get proper timestamp."""
return datetime.now(tz=timezone.utc)
def get_id() -> str:
"""Get nanoid."""
return nanoid.generate(NANOID_ALPHA, 12)

View file

@ -1,206 +1,206 @@
"""JARVIS quality of life utilities."""
import hashlib
from typing import Any, Callable, Iterable, List, Optional, Tuple, Union
from aiohttp import ClientSession
from jarvis_core.filters import url
DEFAULT_BLOCKSIZE = 8 * 1024 * 1024
class Singleton(object):
REQUIRED = []
OPTIONAL = {}
def __new__(cls, *args: list, **kwargs: dict):
"""Create a new singleton."""
inst = cls.__dict__.get("inst")
if inst is not None:
return inst
inst = object.__new__(cls)
inst.init(*args, **kwargs)
inst._validate()
cls.__inst__ = inst
return inst
def _validate(self) -> None:
for key in self.REQUIRED:
if not getattr(self, key, None):
raise ValueError(f"Missing required key: {key}")
def init(self, **kwargs: dict) -> None:
"""Initialize the object."""
for key, value in kwargs.items():
setattr(self, key, value)
for key, value in self.OPTIONAL.items():
if not getattr(self, key, None):
setattr(self, key, value)
async def hash(
data: str, method: Union[Callable, str] = hashlib.sha256, size: int = DEFAULT_BLOCKSIZE
) -> Tuple[str, int, str]:
"""
Hash an object.
Args:
data: URL or text to hash
method: Hash function/function name, default `hashlib.sha256`
size: Blocksize, default `8 MB`
Returns:
Tuple of hash hex, data size, and content type
Raises:
Exception: Raise on status
"""
# Get hashlib method
if isinstance(method, str):
method = getattr(hashlib, method, hashlib.sha256)
method = method()
# Hash non-URLs
if not url.match(data):
method.update(data.encode("UTF8"))
return method.hexdigest(), len(data), "string"
# Hash URL contents
async with ClientSession() as session:
resp = await session.get(data)
resp.raise_for_status()
content_type = resp.content_type
# While we could use resp.content_length, that sometimes returns None,
# so we manually count the size while looping.
data_len = 0
block_count = 0
async for block in resp.content.iter_chunked(n=size):
data_len += len(block)
block_count += 1
method.update(block)
return method.hexdigest(), data_len, content_type
def convert_bytesize(b: int) -> str:
"""Convert bytes amount to human readable."""
if not b:
return "??? B"
b = float(b)
sizes = ["B", "KB", "MB", "GB", "TB", "PB"]
size = 0
while b >= 1024 and size < len(sizes) - 1:
b = b / 1024
size += 1
return "{:0.3f} {}".format(b, sizes[size])
def unconvert_bytesize(size: int, ending: str) -> int:
"""Convert human readable to bytes."""
ending = ending.upper()
sizes = ["B", "KB", "MB", "GB", "TB", "PB"]
if ending == "B":
return size
# Rounding is only because bytes cannot be partial
return round(size * (1024 ** sizes.index(ending)))
def find(predicate: Callable, sequence: Iterable) -> Optional[Any]:
"""
Find the first element in a sequence that matches the predicate.
??? Hint "Example Usage:"
```python
member = find(lambda m: m.name == "UserName", guild.members)
```
Args:
predicate: A callable that returns a boolean value
sequence: A sequence to be searched
Returns:
A match if found, otherwise None
"""
for el in sequence:
if predicate(el):
return el
return None
def find_all(predicate: Callable, sequence: Iterable) -> List[Any]:
"""
Find all elements in a sequence that match the predicate.
??? Hint "Example Usage:"
```python
members = find_all(lambda m: m.name == "UserName", guild.members)
```
Args:
predicate: A callable that returns a boolean value
sequence: A sequence to be searched
Returns:
A list of matches
"""
return [el for el in sequence if predicate(el)]
def get(sequence: Iterable, **kwargs: Any) -> Optional[Any]:
"""
Find the first element in a sequence that matches all attrs.
??? Hint "Example Usage:"
```python
channel = get(guild.channels, nsfw=False, category="General")
```
Args:
sequence: A sequence to be searched
kwargs: Keyword arguments to search the sequence for
Returns:
A match if found, otherwise None
"""
if not kwargs:
return sequence[0]
for el in sequence:
if any(not hasattr(el, attr) for attr in kwargs.keys()):
continue
if all(getattr(el, attr) == value for attr, value in kwargs.items()):
return el
return None
def get_all(sequence: Iterable, **kwargs: Any) -> List[Any]:
"""
Find all elements in a sequence that match all attrs.
??? Hint "Example Usage:"
```python
channels = get_all(guild.channels, nsfw=False, category="General")
```
Args:
sequence: A sequence to be searched
kwargs: Keyword arguments to search the sequence for
Returns:
A list of matches
"""
if not kwargs:
return sequence
matches = []
for el in sequence:
if any(not hasattr(el, attr) for attr in kwargs.keys()):
continue
if all(getattr(el, attr) == value for attr, value in kwargs.items()):
matches.append(el)
return matches
"""JARVIS quality of life utilities."""
import hashlib
from typing import Any, Callable, Iterable, List, Optional, Tuple, Union
from aiohttp import ClientSession
from jarvis_core.filters import url
DEFAULT_BLOCKSIZE = 8 * 1024 * 1024
class Singleton(object):
REQUIRED = []
OPTIONAL = {}
def __new__(cls, *args: list, **kwargs: dict):
"""Create a new singleton."""
inst = cls.__dict__.get("inst")
if inst is not None:
return inst
inst = object.__new__(cls)
inst.init(*args, **kwargs)
inst._validate()
cls.__inst__ = inst
return inst
def _validate(self) -> None:
for key in self.REQUIRED:
if not getattr(self, key, None):
raise ValueError(f"Missing required key: {key}")
def init(self, **kwargs: dict) -> None:
"""Initialize the object."""
for key, value in kwargs.items():
setattr(self, key, value)
for key, value in self.OPTIONAL.items():
if not getattr(self, key, None):
setattr(self, key, value)
async def hash(
data: str, method: Union[Callable, str] = hashlib.sha256, size: int = DEFAULT_BLOCKSIZE
) -> Tuple[str, int, str]:
"""
Hash an object.
Args:
data: URL or text to hash
method: Hash function/function name, default `hashlib.sha256`
size: Blocksize, default `8 MB`
Returns:
Tuple of hash hex, data size, and content type
Raises:
Exception: Raise on status
"""
# Get hashlib method
if isinstance(method, str):
method = getattr(hashlib, method, hashlib.sha256)
method = method()
# Hash non-URLs
if not url.match(data):
method.update(data.encode("UTF8"))
return method.hexdigest(), len(data), "string"
# Hash URL contents
async with ClientSession() as session:
resp = await session.get(data)
resp.raise_for_status()
content_type = resp.content_type
# While we could use resp.content_length, that sometimes returns None,
# so we manually count the size while looping.
data_len = 0
block_count = 0
async for block in resp.content.iter_chunked(n=size):
data_len += len(block)
block_count += 1
method.update(block)
return method.hexdigest(), data_len, content_type
def convert_bytesize(b: int) -> str:
"""Convert bytes amount to human readable."""
if not b:
return "??? B"
b = float(b)
sizes = ["B", "KB", "MB", "GB", "TB", "PB"]
size = 0
while b >= 1024 and size < len(sizes) - 1:
b = b / 1024
size += 1
return "{:0.3f} {}".format(b, sizes[size])
def unconvert_bytesize(size: int, ending: str) -> int:
"""Convert human readable to bytes."""
ending = ending.upper()
sizes = ["B", "KB", "MB", "GB", "TB", "PB"]
if ending == "B":
return size
# Rounding is only because bytes cannot be partial
return round(size * (1024 ** sizes.index(ending)))
def find(predicate: Callable, sequence: Iterable) -> Optional[Any]:
"""
Find the first element in a sequence that matches the predicate.
??? Hint "Example Usage:"
```python
member = find(lambda m: m.name == "UserName", guild.members)
```
Args:
predicate: A callable that returns a boolean value
sequence: A sequence to be searched
Returns:
A match if found, otherwise None
"""
for el in sequence:
if predicate(el):
return el
return None
def find_all(predicate: Callable, sequence: Iterable) -> List[Any]:
"""
Find all elements in a sequence that match the predicate.
??? Hint "Example Usage:"
```python
members = find_all(lambda m: m.name == "UserName", guild.members)
```
Args:
predicate: A callable that returns a boolean value
sequence: A sequence to be searched
Returns:
A list of matches
"""
return [el for el in sequence if predicate(el)]
def get(sequence: Iterable, **kwargs: Any) -> Optional[Any]:
"""
Find the first element in a sequence that matches all attrs.
??? Hint "Example Usage:"
```python
channel = get(guild.channels, nsfw=False, category="General")
```
Args:
sequence: A sequence to be searched
kwargs: Keyword arguments to search the sequence for
Returns:
A match if found, otherwise None
"""
if not kwargs:
return sequence[0]
for el in sequence:
if any(not hasattr(el, attr) for attr in kwargs.keys()):
continue
if all(getattr(el, attr) == value for attr, value in kwargs.items()):
return el
return None
def get_all(sequence: Iterable, **kwargs: Any) -> List[Any]:
"""
Find all elements in a sequence that match all attrs.
??? Hint "Example Usage:"
```python
channels = get_all(guild.channels, nsfw=False, category="General")
```
Args:
sequence: A sequence to be searched
kwargs: Keyword arguments to search the sequence for
Returns:
A list of matches
"""
if not kwargs:
return sequence
matches = []
for el in sequence:
if any(not hasattr(el, attr) for attr in kwargs.keys()):
continue
if all(getattr(el, attr) == value for attr, value in kwargs.items()):
matches.append(el)
return matches

2
poetry.lock generated
View file

@ -326,7 +326,7 @@ multidict = ">=4.0"
[metadata]
lock-version = "1.1"
python-versions = "^3.10"
python-versions = "^3.10"
content-hash = "4581dae71400051e27a4fedf8d3f91303102ab0e25e4b4392fa047f865419f3a"
[metadata.files]

View file

@ -1,23 +1,23 @@
[tool.poetry]
name = "jarvis-core"
version = "0.12.0"
description = "JARVIS core"
authors = ["Zevaryx <zevaryx@gmail.com>"]
[tool.poetry.dependencies]
python = "^3.10"
orjson = "^3.6.6"
motor = "^2.5.1"
umongo = "^3.1.0"
PyYAML = "^6.0"
pytz = "^2022.1"
aiohttp = "^3.8.1"
rich = "^12.3.0"
nanoid = "^2.0.0"
[tool.poetry.dev-dependencies]
pytest = "^7.1"
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
[tool.poetry]
name = "jarvis-core"
version = "0.13.0"
description = "JARVIS core"
authors = ["Zevaryx <zevaryx@gmail.com>"]
[tool.poetry.dependencies]
python = "^3.10"
orjson = "^3.6.6"
motor = "^2.5.1"
umongo = "^3.1.0"
PyYAML = "^6.0"
pytz = "^2022.1"
aiohttp = "^3.8.1"
rich = "^12.3.0"
nanoid = "^2.0.0"
[tool.poetry.dev-dependencies]
pytest = "^7.1"
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"