temp trim commands
Some checks are pending
Build and Push Docker Image / build (push) Waiting to run
Some checks are pending
Build and Push Docker Image / build (push) Waiting to run
This commit is contained in:
parent
eb97dca5c6
commit
cf8fb34a29
@ -7,82 +7,82 @@ import logging
|
|||||||
from typing import Tuple
|
from typing import Tuple
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
import aiohttp
|
# import aiohttp
|
||||||
import validators
|
# import validators
|
||||||
from feedparser import FeedParserDict, parse
|
from feedparser import FeedParserDict, parse
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
from discord import Interaction, TextChannel, Embed, Colour
|
from discord import Interaction, TextChannel, Embed, Colour
|
||||||
from discord.app_commands import Choice, choices, Group, autocomplete, rename, command
|
from discord.app_commands import Choice, choices, Group, autocomplete, rename, command
|
||||||
from discord.errors import Forbidden
|
from discord.errors import Forbidden
|
||||||
|
|
||||||
from api import API
|
# from api import API
|
||||||
from feed import Subscription, TrackedContent, ContentFilter
|
# from feed import Subscription, TrackedContent, ContentFilter
|
||||||
from utils import (
|
# from utils import (
|
||||||
Followup,
|
# Followup,
|
||||||
PaginationView,
|
# PaginationView,
|
||||||
get_rss_data,
|
# get_rss_data,
|
||||||
)
|
# )
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
rss_list_sort_choices = [
|
# rss_list_sort_choices = [
|
||||||
Choice(name="Nickname", value=0),
|
# Choice(name="Nickname", value=0),
|
||||||
Choice(name="Date Added", value=1)
|
# Choice(name="Date Added", value=1)
|
||||||
]
|
# ]
|
||||||
channels_list_sort_choices=[
|
# channels_list_sort_choices=[
|
||||||
Choice(name="Feed Nickname", value=0),
|
# Choice(name="Feed Nickname", value=0),
|
||||||
Choice(name="Channel ID", value=1),
|
# Choice(name="Channel ID", value=1),
|
||||||
Choice(name="Date Added", value=2)
|
# Choice(name="Date Added", value=2)
|
||||||
]
|
# ]
|
||||||
|
|
||||||
# TODO SECURITY: a potential attack is that the user submits an rss feed then changes the
|
# # TODO SECURITY: a potential attack is that the user submits an rss feed then changes the
|
||||||
# target resource. Run a period task to check this.
|
# # target resource. Run a period task to check this.
|
||||||
async def validate_rss_source(nickname: str, url: str) -> Tuple[str | None, FeedParserDict | None]:
|
# async def validate_rss_source(nickname: str, url: str) -> Tuple[str | None, FeedParserDict | None]:
|
||||||
"""Validate a provided RSS source.
|
# """Validate a provided RSS source.
|
||||||
|
|
||||||
Parameters
|
# Parameters
|
||||||
----------
|
# ----------
|
||||||
nickname : str
|
# nickname : str
|
||||||
Nickname of the source. Must not contain URL.
|
# Nickname of the source. Must not contain URL.
|
||||||
url : str
|
# url : str
|
||||||
URL of the source. Must be URL with valid status code and be an RSS feed.
|
# URL of the source. Must be URL with valid status code and be an RSS feed.
|
||||||
|
|
||||||
Returns
|
# Returns
|
||||||
-------
|
# -------
|
||||||
str or None
|
# str or None
|
||||||
String invalid message if invalid, NoneType if valid.
|
# String invalid message if invalid, NoneType if valid.
|
||||||
FeedParserDict or None
|
# FeedParserDict or None
|
||||||
The feed parsed from the given URL or None if invalid.
|
# The feed parsed from the given URL or None if invalid.
|
||||||
"""
|
# """
|
||||||
|
|
||||||
# Ensure the URL is valid
|
# # Ensure the URL is valid
|
||||||
if not validators.url(url):
|
# if not validators.url(url):
|
||||||
return f"The URL you have entered is malformed or invalid:\n`{url=}`", None
|
# return f"The URL you have entered is malformed or invalid:\n`{url=}`", None
|
||||||
|
|
||||||
# Check the nickname is not a URL
|
# # Check the nickname is not a URL
|
||||||
if validators.url(nickname):
|
# if validators.url(nickname):
|
||||||
return "It looks like the nickname you have entered is a URL.\n" \
|
# return "It looks like the nickname you have entered is a URL.\n" \
|
||||||
f"For security reasons, this is not allowed.\n`{nickname=}`", None
|
# f"For security reasons, this is not allowed.\n`{nickname=}`", None
|
||||||
|
|
||||||
|
|
||||||
feed_data, status_code = await get_rss_data(url)
|
# feed_data, status_code = await get_rss_data(url)
|
||||||
|
|
||||||
# Check the URL status code is valid
|
# # Check the URL status code is valid
|
||||||
if status_code != 200:
|
# if status_code != 200:
|
||||||
return f"The URL provided returned an invalid status code:\n{url=}, {status_code=}", None
|
# return f"The URL provided returned an invalid status code:\n{url=}, {status_code=}", None
|
||||||
|
|
||||||
# Check the contents is actually an RSS feed.
|
# # Check the contents is actually an RSS feed.
|
||||||
feed = parse(feed_data)
|
# feed = parse(feed_data)
|
||||||
if not feed.version:
|
# if not feed.version:
|
||||||
return f"The provided URL '{url}' does not seem to be a valid RSS feed.", None
|
# return f"The provided URL '{url}' does not seem to be a valid RSS feed.", None
|
||||||
|
|
||||||
return None, feed
|
# return None, feed
|
||||||
|
|
||||||
tri_choices = [
|
# tri_choices = [
|
||||||
Choice(name="Yes", value=2),
|
# Choice(name="Yes", value=2),
|
||||||
Choice(name="No (default)", value=1),
|
# Choice(name="No (default)", value=1),
|
||||||
Choice(name="All", value=0),
|
# Choice(name="All", value=0),
|
||||||
]
|
# ]
|
||||||
|
|
||||||
|
|
||||||
class CommandsCog(commands.Cog):
|
class CommandsCog(commands.Cog):
|
||||||
@ -100,206 +100,206 @@ class CommandsCog(commands.Cog):
|
|||||||
|
|
||||||
log.info("%s cog is ready", self.__class__.__name__)
|
log.info("%s cog is ready", self.__class__.__name__)
|
||||||
|
|
||||||
# Group for commands about viewing data
|
# # Group for commands about viewing data
|
||||||
view_group = Group(
|
# view_group = Group(
|
||||||
name="view",
|
# name="view",
|
||||||
description="View data.",
|
# description="View data.",
|
||||||
guild_only=True
|
# guild_only=True
|
||||||
)
|
# )
|
||||||
|
|
||||||
@view_group.command(name="subscriptions")
|
# @view_group.command(name="subscriptions")
|
||||||
async def cmd_list_subs(self, inter: Interaction, search: str = ""):
|
# async def cmd_list_subs(self, inter: Interaction, search: str = ""):
|
||||||
"""List Subscriptions from this server."""
|
# """List Subscriptions from this server."""
|
||||||
|
|
||||||
await inter.response.defer()
|
# await inter.response.defer()
|
||||||
|
|
||||||
def formatdata(index, item):
|
# def formatdata(index, item):
|
||||||
item = Subscription.from_dict(item)
|
# item = Subscription.from_dict(item)
|
||||||
|
|
||||||
notes = item.extra_notes[:25] + "..." if len(item.extra_notes) > 28 else item.extra_notes
|
# notes = item.extra_notes[:25] + "..." if len(item.extra_notes) > 28 else item.extra_notes
|
||||||
links = f"[RSS Link]({item.url}) • [API Link]({API.API_EXTERNAL_ENDPOINT}subscription/{item.id}/)"
|
# links = f"[RSS Link]({item.url}) • [API Link]({API.API_EXTERNAL_ENDPOINT}subscription/{item.id}/)"
|
||||||
activeness = "✅ `enabled`" if item.active else "🚫 `disabled`"
|
# activeness = "✅ `enabled`" if item.active else "🚫 `disabled`"
|
||||||
|
|
||||||
description = f"🆔 `{item.id}`\n{activeness}\n#️⃣ `{item.channels_count}` 🔽 `{len(item.filters)}`\n"
|
# description = f"🆔 `{item.id}`\n{activeness}\n#️⃣ `{item.channels_count}` 🔽 `{len(item.filters)}`\n"
|
||||||
description = f"{notes}\n" + description if notes else description
|
# description = f"{notes}\n" + description if notes else description
|
||||||
description += links
|
# description += links
|
||||||
|
|
||||||
key = f"{index}. {item.name}"
|
# key = f"{index}. {item.name}"
|
||||||
return key, description # key, value pair
|
# return key, description # key, value pair
|
||||||
|
|
||||||
async def getdata(page: int, pagesize: int):
|
# async def getdata(page: int, pagesize: int):
|
||||||
async with aiohttp.ClientSession() as session:
|
# async with aiohttp.ClientSession() as session:
|
||||||
api = API(self.bot.api_token, session)
|
# api = API(self.bot.api_token, session)
|
||||||
return await api.get_subscriptions(
|
# return await api.get_subscriptions(
|
||||||
guild_id=inter.guild.id,
|
# guild_id=inter.guild.id,
|
||||||
page=page,
|
# page=page,
|
||||||
page_size=pagesize,
|
# page_size=pagesize,
|
||||||
search=search
|
# search=search
|
||||||
)
|
# )
|
||||||
|
|
||||||
embed = Followup(f"Subscriptions in {inter.guild.name}").info()._embed
|
# embed = Followup(f"Subscriptions in {inter.guild.name}").info()._embed
|
||||||
pagination = PaginationView(
|
# pagination = PaginationView(
|
||||||
self.bot,
|
# self.bot,
|
||||||
inter=inter,
|
# inter=inter,
|
||||||
embed=embed,
|
# embed=embed,
|
||||||
getdata=getdata,
|
# getdata=getdata,
|
||||||
formatdata=formatdata,
|
# formatdata=formatdata,
|
||||||
pagesize=10,
|
# pagesize=10,
|
||||||
initpage=1
|
# initpage=1
|
||||||
)
|
# )
|
||||||
await pagination.send()
|
# await pagination.send()
|
||||||
|
|
||||||
@view_group.command(name="tracked-content")
|
# @view_group.command(name="tracked-content")
|
||||||
@choices(blocked=tri_choices)
|
# @choices(blocked=tri_choices)
|
||||||
async def cmd_list_tracked(self, inter: Interaction, search: str = "", blocked: Choice[int] = 1):
|
# async def cmd_list_tracked(self, inter: Interaction, search: str = "", blocked: Choice[int] = 1):
|
||||||
"""List Tracked Content from this server""" # TODO: , or a given sub
|
# """List Tracked Content from this server""" # TODO: , or a given sub
|
||||||
|
|
||||||
await inter.response.defer()
|
# await inter.response.defer()
|
||||||
|
|
||||||
# If the user picks an option it's an instance of `Choice` otherwise `str`
|
# # If the user picks an option it's an instance of `Choice` otherwise `str`
|
||||||
# Can't figure a way to select a default choices, so blame discordpy for this mess.
|
# # Can't figure a way to select a default choices, so blame discordpy for this mess.
|
||||||
if isinstance(blocked, Choice):
|
# if isinstance(blocked, Choice):
|
||||||
blocked = blocked.value
|
# blocked = blocked.value
|
||||||
|
|
||||||
def formatdata(index, item):
|
# def formatdata(index, item):
|
||||||
item = TrackedContent.from_dict(item)
|
# item = TrackedContent.from_dict(item)
|
||||||
sub = Subscription.from_dict(item.subscription)
|
# sub = Subscription.from_dict(item.subscription)
|
||||||
|
|
||||||
links = f"[Content Link]({item.url}) · [Message Link](https://discord.com/channels/{sub.guild_id}/{item.channel_id}/{item.message_id}/) · [API Link]({API.API_EXTERNAL_ENDPOINT}tracked-content/{item.id}/)"
|
# links = f"[Content Link]({item.url}) · [Message Link](https://discord.com/channels/{sub.guild_id}/{item.channel_id}/{item.message_id}/) · [API Link]({API.API_EXTERNAL_ENDPOINT}tracked-content/{item.id}/)"
|
||||||
delivery_state = "✅ Delivered" if not item.blocked else "🚫 Blocked"
|
# delivery_state = "✅ Delivered" if not item.blocked else "🚫 Blocked"
|
||||||
|
|
||||||
description = f"🆔 `{item.id}`\n"
|
# description = f"🆔 `{item.id}`\n"
|
||||||
description += f"{delivery_state}\n" if blocked == 0 else ""
|
# description += f"{delivery_state}\n" if blocked == 0 else ""
|
||||||
description += f"➡️ *{sub.name}*\n{links}"
|
# description += f"➡️ *{sub.name}*\n{links}"
|
||||||
|
|
||||||
key = f"{index}. {item.title}"
|
# key = f"{index}. {item.title}"
|
||||||
return key, description
|
# return key, description
|
||||||
|
|
||||||
def determine_blocked():
|
# def determine_blocked():
|
||||||
match blocked:
|
# match blocked:
|
||||||
case 0: return ""
|
# case 0: return ""
|
||||||
case 1: return "false"
|
# case 1: return "false"
|
||||||
case 2: return "true"
|
# case 2: return "true"
|
||||||
case _: return ""
|
# case _: return ""
|
||||||
|
|
||||||
async def getdata(page: int, pagesize: int):
|
# async def getdata(page: int, pagesize: int):
|
||||||
async with aiohttp.ClientSession() as session:
|
# async with aiohttp.ClientSession() as session:
|
||||||
api = API(self.bot.api_token, session)
|
# api = API(self.bot.api_token, session)
|
||||||
is_blocked = determine_blocked()
|
# is_blocked = determine_blocked()
|
||||||
return await api.get_tracked_content(
|
# return await api.get_tracked_content(
|
||||||
subscription__guild_id=inter.guild_id,
|
# subscription__guild_id=inter.guild_id,
|
||||||
blocked=is_blocked,
|
# blocked=is_blocked,
|
||||||
page=page,
|
# page=page,
|
||||||
page_size=pagesize,
|
# page_size=pagesize,
|
||||||
search=search,
|
# search=search,
|
||||||
)
|
# )
|
||||||
|
|
||||||
embed = Followup(f"Tracked Content in {inter.guild.name}").info()._embed
|
# embed = Followup(f"Tracked Content in {inter.guild.name}").info()._embed
|
||||||
pagination = PaginationView(
|
# pagination = PaginationView(
|
||||||
self.bot,
|
# self.bot,
|
||||||
inter=inter,
|
# inter=inter,
|
||||||
embed=embed,
|
# embed=embed,
|
||||||
getdata=getdata,
|
# getdata=getdata,
|
||||||
formatdata=formatdata,
|
# formatdata=formatdata,
|
||||||
pagesize=10,
|
# pagesize=10,
|
||||||
initpage=1
|
# initpage=1
|
||||||
)
|
# )
|
||||||
await pagination.send()
|
# await pagination.send()
|
||||||
|
|
||||||
@view_group.command(name="filters")
|
# @view_group.command(name="filters")
|
||||||
async def cmd_list_filters(self, inter: Interaction, search: str = ""):
|
# async def cmd_list_filters(self, inter: Interaction, search: str = ""):
|
||||||
"""List Filters from this server."""
|
# """List Filters from this server."""
|
||||||
|
|
||||||
await inter.response.defer()
|
# await inter.response.defer()
|
||||||
|
|
||||||
def formatdata(index, item):
|
# def formatdata(index, item):
|
||||||
item = ContentFilter.from_dict(item)
|
# item = ContentFilter.from_dict(item)
|
||||||
|
|
||||||
matching_algorithm = get_algorithm_name(item.matching_algorithm)
|
# matching_algorithm = get_algorithm_name(item.matching_algorithm)
|
||||||
whitelist = "Whitelist" if item.is_whitelist else "Blacklist"
|
# whitelist = "Whitelist" if item.is_whitelist else "Blacklist"
|
||||||
sensitivity = "Case insensitive" if item.is_insensitive else "Case sensitive"
|
# sensitivity = "Case insensitive" if item.is_insensitive else "Case sensitive"
|
||||||
|
|
||||||
description = f"🆔 `{item.id}`\n"
|
# description = f"🆔 `{item.id}`\n"
|
||||||
description += f"🔄 `{matching_algorithm}`\n🟰 `{item.match}`\n"
|
# description += f"🔄 `{matching_algorithm}`\n🟰 `{item.match}`\n"
|
||||||
description += f"✅ `{whitelist}` 🔠 `{sensitivity}`\n"
|
# description += f"✅ `{whitelist}` 🔠 `{sensitivity}`\n"
|
||||||
description += f"[API Link]({API.API_EXTERNAL_ENDPOINT}filter/{item.id}/)"
|
# description += f"[API Link]({API.API_EXTERNAL_ENDPOINT}filter/{item.id}/)"
|
||||||
|
|
||||||
key = f"{index}. {item.name}"
|
# key = f"{index}. {item.name}"
|
||||||
return key, description
|
# return key, description
|
||||||
|
|
||||||
def get_algorithm_name(matching_algorithm: int):
|
# def get_algorithm_name(matching_algorithm: int):
|
||||||
match matching_algorithm:
|
# match matching_algorithm:
|
||||||
case 0: return "None"
|
# case 0: return "None"
|
||||||
case 1: return "Any word"
|
# case 1: return "Any word"
|
||||||
case 2: return "All words"
|
# case 2: return "All words"
|
||||||
case 3: return "Exact match"
|
# case 3: return "Exact match"
|
||||||
case 4: return "Regex match"
|
# case 4: return "Regex match"
|
||||||
case 5: return "Fuzzy match"
|
# case 5: return "Fuzzy match"
|
||||||
case _: return "unknown"
|
# case _: return "unknown"
|
||||||
|
|
||||||
async def getdata(page, pagesize):
|
# async def getdata(page, pagesize):
|
||||||
async with aiohttp.ClientSession() as session:
|
# async with aiohttp.ClientSession() as session:
|
||||||
api = API(self.bot.api_token, session)
|
# api = API(self.bot.api_token, session)
|
||||||
return await api.get_filters(
|
# return await api.get_filters(
|
||||||
guild_id=inter.guild_id,
|
# guild_id=inter.guild_id,
|
||||||
page=page,
|
# page=page,
|
||||||
page_size=pagesize,
|
# page_size=pagesize,
|
||||||
search=search
|
# search=search
|
||||||
)
|
# )
|
||||||
|
|
||||||
embed = Followup(f"Filters in {inter.guild.name}").info()._embed
|
# embed = Followup(f"Filters in {inter.guild.name}").info()._embed
|
||||||
pagination = PaginationView(
|
# pagination = PaginationView(
|
||||||
self.bot,
|
# self.bot,
|
||||||
inter=inter,
|
# inter=inter,
|
||||||
embed=embed,
|
# embed=embed,
|
||||||
getdata=getdata,
|
# getdata=getdata,
|
||||||
formatdata=formatdata,
|
# formatdata=formatdata,
|
||||||
pagesize=10,
|
# pagesize=10,
|
||||||
initpage=1
|
# initpage=1
|
||||||
)
|
# )
|
||||||
await pagination.send()
|
# await pagination.send()
|
||||||
|
|
||||||
# Group for test related commands
|
# # Group for test related commands
|
||||||
test_group = Group(
|
# test_group = Group(
|
||||||
name="test",
|
# name="test",
|
||||||
description="Commands to test Bot functionality.",
|
# description="Commands to test Bot functionality.",
|
||||||
guild_only=True
|
# guild_only=True
|
||||||
)
|
# )
|
||||||
|
|
||||||
@test_group.command(name="channel-permissions")
|
# @test_group.command(name="channel-permissions")
|
||||||
async def cmd_test_channel_perms(self, inter: Interaction):
|
# async def cmd_test_channel_perms(self, inter: Interaction):
|
||||||
"""Test that the current channel's permissions allow for PYRSS to operate in it."""
|
# """Test that the current channel's permissions allow for PYRSS to operate in it."""
|
||||||
|
|
||||||
try:
|
# try:
|
||||||
test_message = await inter.channel.send(content="... testing permissions ...")
|
# test_message = await inter.channel.send(content="... testing permissions ...")
|
||||||
await self.test_channel_perms(inter.channel)
|
# await self.test_channel_perms(inter.channel)
|
||||||
except Exception as error:
|
# except Exception as error:
|
||||||
await inter.response.send_message(content=f"Failed: {error}")
|
# await inter.response.send_message(content=f"Failed: {error}")
|
||||||
return
|
# return
|
||||||
|
|
||||||
await test_message.delete()
|
# await test_message.delete()
|
||||||
await inter.response.send_message(content="Success")
|
# await inter.response.send_message(content="Success")
|
||||||
|
|
||||||
async def test_channel_perms(self, channel: TextChannel):
|
# async def test_channel_perms(self, channel: TextChannel):
|
||||||
|
|
||||||
# Test generic message and delete
|
# # Test generic message and delete
|
||||||
msg = await channel.send(content="test message")
|
# msg = await channel.send(content="test message")
|
||||||
await msg.delete()
|
# await msg.delete()
|
||||||
|
|
||||||
# Test detailed embed
|
# # Test detailed embed
|
||||||
embed = Embed(
|
# embed = Embed(
|
||||||
title="test title",
|
# title="test title",
|
||||||
description="test description",
|
# description="test description",
|
||||||
colour=Colour.random(),
|
# colour=Colour.random(),
|
||||||
timestamp=datetime.now(),
|
# timestamp=datetime.now(),
|
||||||
url="https://google.com"
|
# url="https://google.com"
|
||||||
)
|
# )
|
||||||
embed.set_author(name="test author")
|
# embed.set_author(name="test author")
|
||||||
embed.set_footer(text="test footer")
|
# embed.set_footer(text="test footer")
|
||||||
embed.set_thumbnail(url="https://www.google.com/images/branding/googlelogo/2x/googlelogo_light_color_272x92dp.png")
|
# embed.set_thumbnail(url="https://www.google.com/images/branding/googlelogo/2x/googlelogo_light_color_272x92dp.png")
|
||||||
embed.set_image(url="https://www.google.com/images/branding/googlelogo/2x/googlelogo_light_color_272x92dp.png")
|
# embed.set_image(url="https://www.google.com/images/branding/googlelogo/2x/googlelogo_light_color_272x92dp.png")
|
||||||
embed_msg = await channel.send(embed=embed)
|
# embed_msg = await channel.send(embed=embed)
|
||||||
await embed_msg.delete()
|
# await embed_msg.delete()
|
||||||
|
|
||||||
|
|
||||||
async def setup(bot):
|
async def setup(bot):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user