-
Notifications
You must be signed in to change notification settings - Fork 0
Verification rewrite #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
Herbstblatt
wants to merge
5
commits into
master
Choose a base branch
from
feature/verification
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
905cb39
Initial verification setup
Herbstblatt 5330834
Create a table schema for storing bindings
Herbstblatt 9b9acf0
Create verification strings
Herbstblatt f1c6a46
Use strings from yaml file
Herbstblatt 4c8f30b
Implement querying users from db
Herbstblatt File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| from .cog import Verification | ||
| from bot import Bot | ||
|
|
||
| __all__ = ("setup",) | ||
|
|
||
| async def setup(bot: Bot) -> None: | ||
| await bot.add_cog(Verification(bot)) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,174 @@ | ||
| from typing import TYPE_CHECKING, Optional | ||
| import discord | ||
| from discord.ext import commands | ||
|
|
||
| from utils.wiki import Wiki | ||
|
|
||
| from .errors import AlreadyVerified, OwnershipNotProved, RequirementsNotSatsified | ||
| from .models import Account, Binding, RequirementsCheckResult, RequirementsCheckResultStatus, User | ||
| from config import config, strings as _strings | ||
| from utils.converters import AccountConverter | ||
|
|
||
| if TYPE_CHECKING: | ||
| from bot import Bot | ||
| from utils.context import WhiteContext | ||
|
|
||
| strings = _strings.verification | ||
|
|
||
| class Verification(commands.Cog): | ||
| def __init__(self, bot: "Bot"): | ||
| self.bot = bot | ||
|
|
||
| async def fetch_user_from_db( | ||
| self, | ||
| fandom_name: Optional[str] = None, | ||
| discord_id: Optional[int] = None, | ||
| trusted_only: bool = True | ||
| ) -> Optional["User"]: | ||
| """Fetches fandom-discord bindings from the database and returns Account object with that data. | ||
|
|
||
| Arguments: | ||
| fandom_name (Optional[str]): Account name on Fandom | ||
| discord_id (Optional[int]): Account id on Discord | ||
| trusted_only (bool): Whether to fetch only trusted bindings | ||
|
|
||
| Returns: | ||
| An User object or None when no bindings satsify the given parameters. | ||
| """ | ||
| conditions = [] | ||
| args = [] | ||
| if fandom_name is not None: | ||
| conditions.append(f"fandom_name = ${len(conditions) + 1}") | ||
| args.append(fandom_name) | ||
| if discord_id is not None: | ||
| conditions.append(f"discord_id = ${len(conditions) + 1}") | ||
| args.append(discord_id) | ||
| if trusted_only: | ||
| conditions.append(f"trusted = true") | ||
|
|
||
| async with self.bot.pool.acquire() as conn: | ||
| results = await conn.fetch(f""" | ||
| WITH RECURSIVE tmp AS ( | ||
| SELECT * | ||
| FROM users | ||
| WHERE {" AND ".join(conditions)} | ||
| UNION | ||
| SELECT users.fandom_name, users.discord_id, users.guild_id, users.trusted, users.active | ||
| FROM users | ||
| JOIN tmp ON users.fandom_name = tmp.fandom_name or users.discord_id = tmp.discord_id | ||
| {"WHERE users.trusted = true" if trusted_only else ""} | ||
| ) SELECT * FROM tmp; | ||
| """, *args) | ||
|
|
||
| if len(results) == 0: | ||
| return None | ||
|
|
||
| bindings = [ | ||
| Binding( | ||
| fandom_account=Account(name=result["fandom_name"], wiki=Wiki.from_dot_notation("ru.c")), | ||
| discord_account=discord.Object(id=result["discord_id"]), | ||
| trusted=result["trusted"], | ||
| active=result["active"] | ||
| ) for result in results | ||
| ] | ||
| return User(_bot=self.bot, bindings=bindings) | ||
|
|
||
| async def is_verified(self, ctx: "WhiteContext", member: discord.Member) -> bool: | ||
| """Checks whether the given account is verified in the given context. | ||
|
|
||
| Arguments: | ||
| ctx (WhiteContext): The context to check in | ||
| member (discord.Member): The member to check | ||
|
|
||
| Returns: | ||
| True if the account is verified, False otherwise. | ||
| """ | ||
| pass | ||
|
|
||
| async def check_requirements(self, ctx: "WhiteContext", user: User) -> RequirementsCheckResult: | ||
| """Checks whether the user satsifies verification requirements in the given context. | ||
|
|
||
| Arguments: | ||
| ctx (WhiteContext): The context to check requirements in | ||
| user (User): The user to check requirements for | ||
|
|
||
| Returns: | ||
| ReqiurementsCheckResult object. | ||
| """ | ||
|
|
||
| async def verify(self, ctx: "WhiteContext", member: discord.Member, account: Account): | ||
| """Verifies the user in the given context under the given account. | ||
|
|
||
| This method performs real actions, like giving roles and changing nickname. | ||
| It does not perform any checks or create bindings. It's up to the caller to do so. | ||
|
|
||
| Arguments: | ||
| ctx (WhiteContext): The context | ||
| member (discord.Member): The member to verify | ||
| account (Account): The account that will be used for verification | ||
|
|
||
| Returns: | ||
| None | ||
|
|
||
| Raises: | ||
| MissingPermissions: The bot doesn't have permissions to verify that user. | ||
| """ | ||
|
|
||
| @commands.hybrid_command(name="verify") | ||
| @discord.app_commands.guild_only() | ||
| @commands.guild_only() | ||
| async def verify_command(self, ctx: "WhiteContext", *, account: Account = commands.param(converter=AccountConverter)): | ||
| """Верифицирует Вас на сервере. | ||
|
|
||
| Аргументы: | ||
| account: Имя Вашего аккаунта на Фэндоме | ||
| """ | ||
| await ctx.defer() | ||
|
|
||
| # a few asserts to make type checker happy | ||
| assert isinstance(ctx.author, discord.Member) | ||
| assert ctx.guild is not None | ||
|
|
||
| if await self.is_verified(ctx, ctx.author): | ||
| raise AlreadyVerified() | ||
|
|
||
| user = await self.fetch_user_from_db(discord_id=ctx.author.id, trusted_only=True) | ||
| if user is None or account not in user.fandom_accounts: | ||
| if account.discord_tag != str(ctx.author): | ||
| raise OwnershipNotProved() | ||
|
|
||
| if user is None: | ||
| user = User(_bot=self.bot) | ||
| await user.add_account( | ||
| fandom_account=account.name, | ||
| discord_id=ctx.author.id, | ||
| guild_id=ctx.guild.id, | ||
| trusted=True | ||
| ) | ||
|
|
||
| req_check_result = await self.check_requirements(ctx, user) | ||
| if req_check_result.status is RequirementsCheckResultStatus.failed: | ||
| raise RequirementsNotSatsified(req_check_result) | ||
|
|
||
| await self.verify(ctx, ctx.author, account) | ||
|
|
||
| em = discord.Embed( | ||
| description=strings.verification_successful.format(guild=ctx.guild.name), | ||
| color=discord.Color.green(), | ||
| timestamp=discord.utils.utcnow() | ||
| ) | ||
| em.set_author( | ||
| name=account.name, | ||
| url=account.page_url, | ||
| icon_url=account.avatar_url | ||
| ) | ||
| em.add_field( | ||
| name=strings.account_header, | ||
| value=f"{config.emojis.success} {strings.ownership_confirmed}", | ||
| ) | ||
| em.add_field( | ||
| name=strings.requirements_header, | ||
| value=f"{config.emojis.success} {strings.requirements_satsified}" | ||
| ) | ||
| await ctx.send(embed=em) | ||
|
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| from dataclasses import dataclass | ||
| from utils.errors import WhiteException | ||
|
|
||
| class VerificationError(WhiteException): | ||
| pass | ||
|
|
||
| class AlreadyVerified(VerificationError): | ||
| pass | ||
|
|
||
| class OwnershipNotProved(VerificationError): | ||
| pass | ||
|
|
||
| class RequirementsNotSatsified(VerificationError): | ||
| pass | ||
|
|
||
| @dataclass | ||
| class MissingPermissions(VerificationError): | ||
| add_role: bool | ||
| remove_role: bool | ||
| change_nickname: bool | ||
| create_role: bool |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| from dataclasses import dataclass, field | ||
| import enum | ||
| from typing import TYPE_CHECKING, List, Optional | ||
|
|
||
| import discord | ||
|
|
||
| if TYPE_CHECKING: | ||
| from bot import Bot | ||
| from utils.wiki import Wiki | ||
|
|
||
| @dataclass | ||
| class Account: | ||
| name: str | ||
| wiki: "Wiki" | ||
| id: Optional[int] = None | ||
| discord_tag: Optional[str] = None | ||
|
|
||
| @property | ||
| def page_url(self): | ||
| return self.wiki.url_to("User:" + self.name) | ||
|
|
||
| @property | ||
| def avatar_url(self): | ||
| return f"https://services.fandom.com/user-avatar/user/{self.id}/avatar" | ||
|
|
||
| def __hash__(self) -> int: | ||
| return hash(self.name) | ||
|
|
||
| def __eq__(self, other: "Account") -> bool: | ||
| return self.name == other.name | ||
|
|
||
| @dataclass | ||
| class Binding: | ||
| fandom_account: Account | ||
| discord_account: discord.abc.Snowflake | ||
| trusted: bool | ||
| active: bool | ||
|
|
||
| @dataclass | ||
| class User: | ||
| """Represents an user. | ||
|
|
||
| The user is a person that can have multiple accounts both on Fandom an Discord. | ||
| """ | ||
| _bot: "Bot" | ||
| bindings: List[Binding] = field(default_factory=list) | ||
|
|
||
| async def add_account( | ||
| self, | ||
| *, | ||
| guild_id: int, | ||
| fandom_account: Optional[str] = None, | ||
| discord_id: Optional[int] = None, | ||
| trusted: bool = True | ||
| ): | ||
| pass | ||
|
|
||
| @property | ||
| def fandom_accounts(self) -> List[Account]: | ||
| return list(set(b.fandom_account for b in self.bindings)) | ||
|
|
||
| @property | ||
| def discord_accounts(self) -> List[discord.abc.Snowflake]: | ||
| return list(set(b.discord_account for b in self.bindings)) | ||
|
|
||
| class RequirementsCheckResultStatus(enum.Enum): | ||
| ok = True | ||
| failed = False | ||
|
|
||
| @dataclass | ||
| class RequirementsCheckResult: | ||
| status: RequirementsCheckResultStatus | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,6 +17,7 @@ cogs: | |
| # - cogs.help | ||
| - cogs.info | ||
| - cogs.fandom | ||
| - cogs.verification | ||
| - cogs.wikilinks | ||
|
|
||
| emojis: | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| -- migrate:up | ||
| CREATE TABLE users ( | ||
| fandom_name text, | ||
| discord_id bigint, | ||
| guild_id bigint, | ||
| trusted boolean, | ||
| active boolean | ||
| ); | ||
|
|
||
| CREATE INDEX ON users (fandom_name, discord_id, guild_id); | ||
|
|
||
| -- migrate:down | ||
| DROP TABLE users; | ||
|
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo:
and, notan