Working on channel commands
This commit is contained in:
parent
76d27c4782
commit
da3dcbc369
@ -74,3 +74,8 @@ class FeedChannelModel(Base):
|
|||||||
discord_server_id = Column(BigInteger, nullable=False)
|
discord_server_id = Column(BigInteger, nullable=False)
|
||||||
search_name = Column(String, nullable=False)
|
search_name = Column(String, nullable=False)
|
||||||
rss_source_id = Column(Integer, ForeignKey('rss_source.id'), nullable=False)
|
rss_source_id = Column(Integer, ForeignKey('rss_source.id'), nullable=False)
|
||||||
|
|
||||||
|
# the rss source must be unique, but only within the same discord channel
|
||||||
|
__table_args__ = (
|
||||||
|
UniqueConstraint('rss_source_id', 'discord_channel_id', name='uq_rss_discord_channel'),
|
||||||
|
)
|
||||||
|
@ -5,12 +5,12 @@ Loading this file via `commands.Bot.load_extension` will add `ChannelCog` to the
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from sqlalchemy import select, and_
|
from sqlalchemy import select, insert, delete, and_
|
||||||
from discord import Interaction, TextChannel
|
from discord import Interaction, TextChannel
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
from discord.app_commands import Group, Choice, autocomplete
|
from discord.app_commands import Group, Choice, autocomplete
|
||||||
|
|
||||||
from db import DatabaseManager, FeedChannelModel
|
from db import DatabaseManager, FeedChannelModel, RssSourceModel
|
||||||
from utils import followup
|
from utils import followup
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
@ -29,6 +29,25 @@ class ChannelCog(commands.Cog):
|
|||||||
async def on_ready(self):
|
async def on_ready(self):
|
||||||
log.info(f"{self.__class__.__name__} cog is ready")
|
log.info(f"{self.__class__.__name__} cog is ready")
|
||||||
|
|
||||||
|
async def autocomplete_rss_sources(self, inter: Interaction, nickname: str):
|
||||||
|
""""""
|
||||||
|
|
||||||
|
async with DatabaseManager() as database:
|
||||||
|
whereclause = and_(
|
||||||
|
RssSourceModel.discord_server_id == inter.guild_id,
|
||||||
|
RssSourceModel.nick.ilike(f"%{nickname}%")
|
||||||
|
)
|
||||||
|
query = select(RssSourceModel).where(whereclause)
|
||||||
|
result = await database.session.execute(query)
|
||||||
|
sources = [
|
||||||
|
Choice(name=rss.nick, value=rss.id)
|
||||||
|
for rss in result.scalars().all()
|
||||||
|
]
|
||||||
|
|
||||||
|
log.debug(f"Autocomplete rss_sources returned {len(sources)} results")
|
||||||
|
|
||||||
|
return sources
|
||||||
|
|
||||||
async def autocomplete_existing_feeds(self, inter: Interaction, current: str):
|
async def autocomplete_existing_feeds(self, inter: Interaction, current: str):
|
||||||
"""Returns a list of existing RSS + Channel feeds.
|
"""Returns a list of existing RSS + Channel feeds.
|
||||||
|
|
||||||
@ -52,17 +71,20 @@ class ChannelCog(commands.Cog):
|
|||||||
for feed in result.scalars().all()
|
for feed in result.scalars().all()
|
||||||
]
|
]
|
||||||
|
|
||||||
|
log.debug(f"Autocomplete existing_feeds returned {len(feeds)} results")
|
||||||
|
|
||||||
return feeds
|
return feeds
|
||||||
|
|
||||||
# All RSS commands belong to this group.
|
# All RSS commands belong to this group.
|
||||||
channel_group = Group(
|
channel_group = Group(
|
||||||
name="channel",
|
name="channel",
|
||||||
description="Commands for channel assignment.",
|
description="Commands for channel assignment.",
|
||||||
guild_only=True # We store guild IDs in the database, so guild only = True
|
guild_only=True # These commands belong to channels of
|
||||||
)
|
)
|
||||||
|
|
||||||
channel_group.command(name="include-feed")
|
@channel_group.command(name="include-feed")
|
||||||
async def include_feed(self, inter: Interaction, rss: str, channel: TextChannel):
|
@autocomplete(rss=autocomplete_rss_sources)
|
||||||
|
async def include_feed(self, inter: Interaction, rss: int, channel: TextChannel = None):
|
||||||
"""Include a feed within the specified channel.
|
"""Include a feed within the specified channel.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
@ -76,9 +98,32 @@ class ChannelCog(commands.Cog):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
await inter.response.defer()
|
await inter.response.defer()
|
||||||
await followup(inter, "Ping")
|
|
||||||
|
|
||||||
channel_group.command(name="exclude-feed")
|
channel = channel or inter.channel
|
||||||
|
|
||||||
|
async with DatabaseManager() as database:
|
||||||
|
select_query = select(RssSourceModel).where(and_(
|
||||||
|
RssSourceModel.id == rss,
|
||||||
|
RssSourceModel.discord_server_id == inter.guild_id
|
||||||
|
))
|
||||||
|
|
||||||
|
select_result = await database.session.execute(select_query)
|
||||||
|
rss_source = select_result.scalars().one()
|
||||||
|
nick, rss_url = rss_source.nick, rss_source.rss_url
|
||||||
|
|
||||||
|
insert_query = insert(FeedChannelModel).values(
|
||||||
|
discord_server_id = inter.guild_id,
|
||||||
|
discord_channel_id = channel.id,
|
||||||
|
rss_source_id=rss,
|
||||||
|
search_name=f"{nick} #{channel.name}"
|
||||||
|
)
|
||||||
|
|
||||||
|
insert_result = await database.session.execute(insert_query)
|
||||||
|
|
||||||
|
|
||||||
|
await followup(inter, f"I've included [{nick}]({rss_url}) to {channel.mention}")
|
||||||
|
|
||||||
|
@channel_group.command(name="exclude-feed")
|
||||||
@autocomplete(option=autocomplete_existing_feeds)
|
@autocomplete(option=autocomplete_existing_feeds)
|
||||||
async def exclude_feed(self, inter: Interaction, option: int):
|
async def exclude_feed(self, inter: Interaction, option: int):
|
||||||
"""Undo command for the `/channel include-feed` command.
|
"""Undo command for the `/channel include-feed` command.
|
||||||
@ -92,7 +137,20 @@ class ChannelCog(commands.Cog):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
await inter.response.defer()
|
await inter.response.defer()
|
||||||
await followup(inter, "Pong")
|
|
||||||
|
async with DatabaseManager() as database:
|
||||||
|
query = delete(FeedChannelModel).where(and_(
|
||||||
|
FeedChannelModel.id == option,
|
||||||
|
FeedChannelModel.discord_server_id == inter.guild_id
|
||||||
|
))
|
||||||
|
|
||||||
|
result = await database.session.execute(query)
|
||||||
|
|
||||||
|
if not result.rowcount:
|
||||||
|
await followup(inter, "I couldn't find any items under that ID (placeholder response)")
|
||||||
|
return
|
||||||
|
|
||||||
|
await followup(inter, "I've removed this item (placeholder response)")
|
||||||
|
|
||||||
|
|
||||||
async def setup(bot):
|
async def setup(bot):
|
||||||
|
@ -299,26 +299,44 @@ class RssCog(commands.Cog):
|
|||||||
await followup(inter, "Sorry, I couldn't find any articles from this feed.")
|
await followup(inter, "Sorry, I couldn't find any articles from this feed.")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# TODO: embed rules
|
||||||
|
#
|
||||||
|
# https://discord.com/safety/using-webhooks-and-embeds
|
||||||
|
#
|
||||||
|
# 1. Embed titles are limited to 256 characters
|
||||||
|
# 2. Embed descriptions are limited to 2048 characters
|
||||||
|
# 3. There can be up to 25 fields
|
||||||
|
# 4. The name of a field is limited to 256 characters and its value to 1024 characters
|
||||||
|
# 5. The footer text is limited to 2048 characters
|
||||||
|
# 6. The author name is limited to 256 characters
|
||||||
|
# 7. In addition, the sum of all characters in an embed structure must not exceed 6000 characters
|
||||||
|
# 8. The title cannot hyperlink URLs
|
||||||
|
# 10. The embed and author URL must be valid, these will be checked.
|
||||||
|
|
||||||
embeds = []
|
embeds = []
|
||||||
for article in articles:
|
for article in articles:
|
||||||
md_description = markdownify(article.description, strip=("img",))
|
md_description = markdownify(article.description, strip=("img",))
|
||||||
article_description = textwrap.shorten(md_description, 4096)
|
article_description = textwrap.shorten(md_description, 4096)
|
||||||
|
|
||||||
|
md_title = markdownify(article.title, strip=("img", "a"))
|
||||||
|
article_title = textwrap.shorten(md_title, 256)
|
||||||
|
|
||||||
embed = Embed(
|
embed = Embed(
|
||||||
title=article.title,
|
title=article_title,
|
||||||
description=article_description,
|
description=article_description,
|
||||||
url=article.url,
|
url=article.url if validators.url(article.url) else None,
|
||||||
timestamp=article.published,
|
timestamp=article.published,
|
||||||
colour=Colour.brand_red()
|
colour=Colour.brand_red()
|
||||||
)
|
)
|
||||||
|
|
||||||
thumbail_url = await article.get_thumbnail_url()
|
thumbail_url = await article.get_thumbnail_url()
|
||||||
thumbail_url = thumbail_url if validators.url(thumbail_url) else None
|
thumbail_url = thumbail_url if validators.url(thumbail_url) else None
|
||||||
embed.set_thumbnail(url=source.icon_url)
|
embed.set_thumbnail(url=source.icon_url if validators.url(source.icon_url) else None)
|
||||||
embed.set_image(url=thumbail_url)
|
embed.set_image(url=thumbail_url)
|
||||||
embed.set_footer(text=article.author)
|
embed.set_footer(text=article.author)
|
||||||
embed.set_author(
|
embed.set_author(
|
||||||
name=source.name,
|
name=source.name,
|
||||||
url=source.url,
|
url=source.url if validators.url(source.url) else None,
|
||||||
)
|
)
|
||||||
embeds.append(embed)
|
embeds.append(embed)
|
||||||
|
|
||||||
|
10
src/feed.py
10
src/feed.py
@ -64,9 +64,13 @@ class Article:
|
|||||||
|
|
||||||
log.debug("Fetching thumbnail for article: %s", self)
|
log.debug("Fetching thumbnail for article: %s", self)
|
||||||
|
|
||||||
async with aiohttp.ClientSession() as session:
|
try:
|
||||||
async with session.get(self.url) as response:
|
async with aiohttp.ClientSession() as session:
|
||||||
html = await response.text()
|
async with session.get(self.url) as response:
|
||||||
|
html = await response.text()
|
||||||
|
except aiohttp.InvalidURL as error:
|
||||||
|
log.error(error)
|
||||||
|
return None
|
||||||
|
|
||||||
soup = bs4(html, "html.parser")
|
soup = bs4(html, "html.parser")
|
||||||
image_element = soup.select_one("meta[property='og:image']")
|
image_element = soup.select_one("meta[property='og:image']")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user