adding rss module from aikaterna to fix double post issue
This commit is contained in:
11
rss/__init__.py
Normal file
11
rss/__init__.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
from redbot.core import commands
|
||||||
|
|
||||||
|
from .rss import RSS
|
||||||
|
|
||||||
|
__red_end_user_data_statement__ = "This cog does not persistently store data or metadata about users."
|
||||||
|
|
||||||
|
|
||||||
|
async def setup(bot: commands.Bot):
|
||||||
|
n = RSS(bot)
|
||||||
|
bot.add_cog(n)
|
||||||
|
n.initialize()
|
||||||
131
rss/color.py
Normal file
131
rss/color.py
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
from math import sqrt
|
||||||
|
import discord
|
||||||
|
import re
|
||||||
|
import webcolors
|
||||||
|
|
||||||
|
|
||||||
|
_DISCORD_COLOURS = {
|
||||||
|
discord.Color.teal().to_rgb(): 'teal',
|
||||||
|
discord.Color.dark_teal().to_rgb(): 'dark_teal',
|
||||||
|
discord.Color.green().to_rgb(): 'green',
|
||||||
|
discord.Color.dark_green().to_rgb(): 'dark_green',
|
||||||
|
discord.Color.blue().to_rgb(): 'blue',
|
||||||
|
discord.Color.dark_blue().to_rgb(): 'dark_blue',
|
||||||
|
discord.Color.purple().to_rgb(): 'purple',
|
||||||
|
discord.Color.dark_purple().to_rgb(): 'dark_purple',
|
||||||
|
discord.Color.magenta().to_rgb(): 'magenta',
|
||||||
|
discord.Color.dark_magenta().to_rgb(): 'dark_magenta',
|
||||||
|
discord.Color.gold().to_rgb(): 'gold',
|
||||||
|
discord.Color.dark_gold().to_rgb(): 'dark_gold',
|
||||||
|
discord.Color.orange().to_rgb(): 'orange',
|
||||||
|
discord.Color.dark_orange().to_rgb(): 'dark_orange',
|
||||||
|
discord.Color.red().to_rgb(): 'red',
|
||||||
|
discord.Color.dark_red().to_rgb(): 'dark_red',
|
||||||
|
discord.Color.lighter_grey().to_rgb(): 'lighter_grey',
|
||||||
|
discord.Color.light_grey().to_rgb(): 'light_grey',
|
||||||
|
discord.Color.dark_grey().to_rgb(): 'dark_grey',
|
||||||
|
discord.Color.darker_grey().to_rgb(): 'darker_grey',
|
||||||
|
discord.Color.blurple().to_rgb(): 'old_blurple',
|
||||||
|
discord.Color(0x4a90e2).to_rgb(): 'new_blurple',
|
||||||
|
discord.Color.greyple().to_rgb(): 'greyple',
|
||||||
|
discord.Color.dark_theme().to_rgb(): 'discord_dark_theme'
|
||||||
|
}
|
||||||
|
|
||||||
|
_RGB_NAME_MAP = {webcolors.hex_to_rgb(hexcode): name for hexcode, name in webcolors.css3_hex_to_names.items()}
|
||||||
|
_RGB_NAME_MAP.update(_DISCORD_COLOURS)
|
||||||
|
|
||||||
|
|
||||||
|
def _distance(point_a: tuple, point_b: tuple):
|
||||||
|
"""
|
||||||
|
Euclidean distance between two points using rgb values as the metric space.
|
||||||
|
"""
|
||||||
|
# rgb values
|
||||||
|
x1, y1, z1 = point_a
|
||||||
|
x2, y2, z2 = point_b
|
||||||
|
|
||||||
|
# distances
|
||||||
|
dx = x1 - x2
|
||||||
|
dy = y1 - y2
|
||||||
|
dz = z1 - z2
|
||||||
|
|
||||||
|
# final distance
|
||||||
|
return sqrt(dx**2 + dy**2 + dz**2)
|
||||||
|
|
||||||
|
def _linear_nearest_neighbour(all_points: list, pivot: tuple):
|
||||||
|
"""
|
||||||
|
Check distance against all points from the pivot and return the distance and nearest point.
|
||||||
|
"""
|
||||||
|
best_dist = None
|
||||||
|
nearest = None
|
||||||
|
for point in all_points:
|
||||||
|
dist = _distance(point, pivot)
|
||||||
|
if best_dist is None or dist < best_dist:
|
||||||
|
best_dist = dist
|
||||||
|
nearest = point
|
||||||
|
return best_dist, nearest
|
||||||
|
|
||||||
|
|
||||||
|
class Color:
|
||||||
|
"""Helper for color handling."""
|
||||||
|
|
||||||
|
async def _color_converter(self, hex_code_or_color_word: str):
|
||||||
|
"""
|
||||||
|
Used for user input on rss embed color
|
||||||
|
Input: discord.Color name, CSS3 color name, 0xFFFFFF, #FFFFFF, FFFFFF
|
||||||
|
Output: 0xFFFFFF
|
||||||
|
"""
|
||||||
|
# #FFFFFF and FFFFFF to 0xFFFFFF
|
||||||
|
hex_match = re.match(r"#?[a-f0-9]{6}", hex_code_or_color_word.lower())
|
||||||
|
if hex_match:
|
||||||
|
hex_code = f"0x{hex_code_or_color_word.lstrip('#')}"
|
||||||
|
return hex_code
|
||||||
|
|
||||||
|
# discord.Color checking
|
||||||
|
if hasattr(discord.Color, hex_code_or_color_word):
|
||||||
|
hex_code = str(getattr(discord.Color, hex_code_or_color_word)())
|
||||||
|
hex_code = hex_code.replace("#", "0x")
|
||||||
|
return hex_code
|
||||||
|
|
||||||
|
# CSS3 color name checking
|
||||||
|
try:
|
||||||
|
hex_code = webcolors.name_to_hex(hex_code_or_color_word, spec="css3")
|
||||||
|
hex_code = hex_code.replace("#", "0x")
|
||||||
|
return hex_code
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def _hex_to_css3_name(self, hex_code: str):
|
||||||
|
"""
|
||||||
|
Input: 0xFFFFFF
|
||||||
|
Output: CSS3 color name string closest match
|
||||||
|
"""
|
||||||
|
hex_code = await self._hex_validator(hex_code)
|
||||||
|
rgb_tuple = await self._hex_to_rgb(hex_code)
|
||||||
|
|
||||||
|
positions = list(_RGB_NAME_MAP.keys())
|
||||||
|
dist, nearest = _linear_nearest_neighbour(positions, rgb_tuple)
|
||||||
|
|
||||||
|
return _RGB_NAME_MAP[nearest]
|
||||||
|
|
||||||
|
async def _hex_to_rgb(self, hex_code: str):
|
||||||
|
"""
|
||||||
|
Input: 0xFFFFFF
|
||||||
|
Output: (255, 255, 255)
|
||||||
|
"""
|
||||||
|
return webcolors.hex_to_rgb(hex_code)
|
||||||
|
|
||||||
|
async def _hex_validator(self, hex_code: str):
|
||||||
|
"""
|
||||||
|
Input: 0xFFFFFF
|
||||||
|
Output: #FFFFFF or None
|
||||||
|
"""
|
||||||
|
if hex_code[:2] == "0x":
|
||||||
|
hex_code = hex_code.replace("0x", "#")
|
||||||
|
try:
|
||||||
|
# just a check to make sure it's a real color hex code
|
||||||
|
hex_code = webcolors.normalize_hex(hex_code)
|
||||||
|
except ValueError:
|
||||||
|
hex_code = None
|
||||||
|
return hex_code
|
||||||
10
rss/info.json
Normal file
10
rss/info.json
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"author": ["aikaterna"],
|
||||||
|
"install_msg": "Thanks for installing.",
|
||||||
|
"short": "Read RSS feeds.",
|
||||||
|
"description": "Read RSS feeds.",
|
||||||
|
"tags": ["rss"],
|
||||||
|
"permissions": ["embed_links"],
|
||||||
|
"requirements": ["bs4", "feedparser>=6.0.0", "webcolors==1.3"],
|
||||||
|
"end_user_data_statement": "This cog does not persistently store data or metadata about users."
|
||||||
|
}
|
||||||
31
rss/quiet_template.py
Normal file
31
rss/quiet_template.py
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
from collections import ChainMap
|
||||||
|
from string import Template
|
||||||
|
|
||||||
|
|
||||||
|
class QuietTemplate(Template):
|
||||||
|
"""
|
||||||
|
A subclass of string.Template that is less verbose on a missing key
|
||||||
|
https://github.com/python/cpython/blob/919f0bc8c904d3aa13eedb2dd1fe9c6b0555a591/Lib/string.py#L123
|
||||||
|
"""
|
||||||
|
|
||||||
|
def quiet_safe_substitute(self, mapping={}, /, **kws):
|
||||||
|
if mapping is {}:
|
||||||
|
mapping = kws
|
||||||
|
elif kws:
|
||||||
|
mapping = ChainMap(kws, mapping)
|
||||||
|
# Helper function for .sub()
|
||||||
|
def convert(mo):
|
||||||
|
named = mo.group('named') or mo.group('braced')
|
||||||
|
if named is not None:
|
||||||
|
try:
|
||||||
|
return str(mapping[named])
|
||||||
|
except KeyError:
|
||||||
|
# return None instead of the tag name so that
|
||||||
|
# invalid tags are not present in the feed output
|
||||||
|
return None
|
||||||
|
if mo.group('escaped') is not None:
|
||||||
|
return self.delimiter
|
||||||
|
if mo.group('invalid') is not None:
|
||||||
|
return mo.group()
|
||||||
|
raise ValueError('Unrecognized named group in pattern', self.pattern)
|
||||||
|
return self.pattern.sub(convert, self.template)
|
||||||
1692
rss/rss.py
Normal file
1692
rss/rss.py
Normal file
File diff suppressed because it is too large
Load Diff
50
rss/rss_feed.py
Normal file
50
rss/rss_feed.py
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
class RssFeed():
|
||||||
|
"""RSS feed object"""
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
super().__init__()
|
||||||
|
self.name: str = kwargs.get("name", None)
|
||||||
|
self.last_title: str = kwargs.get("last_title", None)
|
||||||
|
self.last_link: str = kwargs.get("last_link", None)
|
||||||
|
self.last_time: str = kwargs.get("last_time", None)
|
||||||
|
self.template: str = kwargs.get("template", None)
|
||||||
|
self.url: str = kwargs.get("url", None)
|
||||||
|
self.template_tags: List[str] = kwargs.get("template_tags", [])
|
||||||
|
self.is_special: List[str] = kwargs.get("is_special", [])
|
||||||
|
self.embed: bool = kwargs.get("embed", True)
|
||||||
|
self.embed_color: str = kwargs.get("embed_color", None)
|
||||||
|
self.embed_image: str = kwargs.get("embed_image", None)
|
||||||
|
self.embed_thumbnail: str = kwargs.get("embed_thumbnail", None)
|
||||||
|
|
||||||
|
def to_json(self) -> dict:
|
||||||
|
return {
|
||||||
|
"name": self.name,
|
||||||
|
"last_title": self.last_title,
|
||||||
|
"last_link": self.last_link,
|
||||||
|
"last_time": self.last_time,
|
||||||
|
"template": self.template,
|
||||||
|
"url": self.url,
|
||||||
|
"template_tags": self.template_tags,
|
||||||
|
"is_special": self.is_special,
|
||||||
|
"embed": self.embed,
|
||||||
|
"embed_color": self.embed_color,
|
||||||
|
"embed_image": self.embed_image,
|
||||||
|
"embed_thumbnail": self.embed_thumbnail,
|
||||||
|
}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_json(cls, data: dict):
|
||||||
|
return cls(
|
||||||
|
name=data["name"] if data["name"] else None,
|
||||||
|
last_title=data["last_title"] if data["last_title"] else None,
|
||||||
|
last_link=data["last_link"] if data["last_link"] else None,
|
||||||
|
last_time=data["last_time"] if data["last_time"] else None,
|
||||||
|
template=data["template"] if data["template"] else None,
|
||||||
|
url=data["url"] if data["url"] else None,
|
||||||
|
template_tags=data["template_tags"] if data["template_tags"] else [],
|
||||||
|
is_special=data["is_special"] if data["is_special"] else [],
|
||||||
|
embed=data["embed"] if data["embed"] else True,
|
||||||
|
embed_color=data["embed_color"] if data["embed_color"] else None,
|
||||||
|
embed_image=data["embed_image"] if data["embed_image"] else None,
|
||||||
|
embed_thumbnail=data["embed_thumbnail"] if data["embed_thumbnail"] else None,
|
||||||
|
)
|
||||||
13
rss/tag_type.py
Normal file
13
rss/tag_type.py
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
|
||||||
|
INTERNAL_TAGS = ["is_special", "template_tags", "embed", "embed_color", "embed_image", "embed_thumbnail"]
|
||||||
|
|
||||||
|
VALID_IMAGES = ["png", "webp", "gif", "jpeg", "jpg"]
|
||||||
|
|
||||||
|
|
||||||
|
class TagType(Enum):
|
||||||
|
PLAINTEXT = 1
|
||||||
|
HTML = 2
|
||||||
|
DICT = 3
|
||||||
|
LIST = 4
|
||||||
Reference in New Issue
Block a user