Merge branch 'master' into fight-fixes

# Conflicts:
#	fight/fight.py
#	fight/info.json
fight-fixes
bobloy 6 years ago
commit a671e81af8

3
.gitignore vendored

@ -1,2 +1,5 @@
.idea/
*.pyc
venv/
v-data/
database.sqlite3

@ -2,31 +2,38 @@
Cog Function
| Name | Status | Description
| Name | Status | Description (Click to see full status)
| --- | --- | --- |
| ccrole | **Beta** | Create custom commands that also assign roles |
| chatter | **Alpha** | Chat-bot trained to talk like your guild
| fight | **Incomplete** | Organize bracket tournaments within discord |
| flag | **Beta** | Create temporary marks on users that expire after specified time |
| hangman | **Incomplete** | Play a game of hangman |
| immortal | **Private** | Cog designed for a specific server, not recommended to install |
| leaver | **Incomplete** | Send a message in a channel when a user leaves the server |
| reactrestrict | **Alpha** | Removes reactions by role per channel |
| stealemoji | **Alpha** | Steals any custom emoji it sees |
| werewolf | **Incomplete** | Play the classic party game Werewolf within discord |
| announcedaily | **Alpha** | <details><summary>Send daily announcements to all servers at a specified times</summary>Commissioned release, so suggestions will not be accepted</details> |
| audiotrivia | **Alpha** | <details><summary>Guess the audio using the core trivia cog</summary>Replaces the core Trivia cog. Needs help adding audio trivia lists, please submit a PR to contribute</details> |
| ccrole | **Beta** | <details><summary>Create custom commands that also assign roles</summary>May have some bugs, please create an issue if you find any</details> |
| chatter | **Alpha** | <details><summary>Chat-bot trained to talk like your guild</summary>Missing some key features, but currently functional</details> |
| coglint | **Alpha** | <details><summary>Error check code in python syntax posted to discord</summary>Works, but probably needs more turning to work for cogs</details> |
| exclusiverole | **Alpha** | <details><summary>Prevent certain roles from getting any other roles</summary>Fully functional, but pretty simple</details> |
| fight | **Incomplete** | <details><summary>Organize bracket tournaments within discord</summary>Still in-progress, a massive project</details> |
| flag | **Alpha** | <details><summary>Create temporary marks on users that expire after specified time</summary>Ported, will not import old data. Please report bugs</details> |
| forcemention | **Alpha** | <details><summary>Mentions unmentionable roles</summary>Very simple cog, mention doesn't persist</details> |
| hangman | **Alpha** | <details><summary>Play a game of hangman</summary>Some visual glitches and needs more customization</details> |
| howdoi | **Incomplete** | <details><summary>Ask coding questions and get results from StackExchange</summary>Not yet functional</details> |
| leaver | **Alpha** | <details><summary>Send a message in a channel when a user leaves the server</summary>Just released, please report bugs</details> |
| lovecalculator | **Alpha** | <details><summary>Calculate the love between two users</summary>[Snap-Ons] Just updated to V3</details> |
| lseen | **Alpha** | <details><summary>Track when a member was last online</summary>Alpha release, please report bugs</details> |
| nudity | **Incomplete** | <details><summary>Checks for NSFW images posted in non-NSFW channels</summary>Library this is based on has a bug, waiting for author to merge my PR</details> |
| planttycoon | **Alpha** | <details><summary>Grow your own plants!</summary>[Snap-Ons] Updated to V3, likely to contain bugs</details> |
| qrinvite | **Alpha** | <details><summary>Create a QR code invite for the server</summary>Alpha release, please report any bugs</details> |
| reactrestrict | **Alpha** | <details><summary>Removes reactions by role per channel</summary>A bit clunky, but functional</details> |
| recyclingplant | **Alpha** | <details><summary>Work at a recycling plant</summary>[Snap-Ons] Just updated to V3</details> |
| rpsls | **Alpha** | <details><summary>Play Rock-Paper-Scissors-Lizard-Spock</summary>[Snap-Ons] Just updated to V3</details> |
| sayurl | **Alpha** | <details><summary>Convert any URL into text and post to discord</summary>No error checking and pretty spammy</details> |
| scp | **Alpha** | <details><summary>Look-up SCP articles</summary>[Snap-Ons] Just updated to V3</details> |
| secrethitler | **Incomplete** | <details><summary>Play the Secret Hitler game</summary>Concept, no work done yet</details> |
| stealemoji | **Alpha** | <details><summary>Steals any custom emoji it sees in a reaction</summary>Some planned upgrades for server generation</details> |
| timerole | **Alpha** | <details><summary>Add roles to members after specified time on the server</summary>Upgraded from V2, please report any bugs</details> |
| tts | **Beta** | <details><summary>Send a Text-to-Speech message as an uploaded mp3</summary>Alpha release, please report any bugs</details> |
| unicode | **Alpha** | <details><summary>Encode and Decode unicode characters</summary>[Snap-Ons] Just updated to V3</details> |
| werewolf | **Pre-Alpha** | <details><summary>Play the classic party game Werewolf within discord</summary>Another massive project currently being developed, will be fully customizable</details> |
Cog Status Descriptions
- ccrole: May have some bugs, please create an issue if you find any
- chatter: Missing some key features, but currently functional
- fight: Still in-progress, a massive project
- flag: Not yet ported to v3
- hangman: Not yet ported to v3
- immortal: Designed for a specific server, not recommended to install
- leaver: Not yet ported to v3
- reactrestrict: A bit clunky, but functional
- stealemoji: Some planned upgrades for server generation
- werewolf: Another massive project, will be fully customizable
Many of these are functional in my V2 cogs at [Fox-Cogs v2](https://github.com/bobloy/Fox-Cogs)
Check out my V2 cogs at [Fox-Cogs v2](https://github.com/bobloy/Fox-Cogs)
Get support on the [Third Party Cog Server](https://discord.gg/GET4DVk)

@ -0,0 +1,9 @@
from redbot.core.bot import Red
from .announcedaily import AnnounceDaily
def setup(bot: Red):
daily = AnnounceDaily(bot)
bot.add_cog(daily)
bot.loop.create_task(daily.check_day())

@ -0,0 +1,253 @@
import asyncio
import random
from datetime import datetime, timedelta
from typing import Any
import discord
from redbot.core import Config, checks, commands
from redbot.core.bot import Red
from redbot.core.data_manager import cog_data_path
from redbot.core.utils.chat_formatting import pagify, box
DEFAULT_MESSAGES = [
# "Example message. Uncomment and overwrite to use",
# "Example message 2. Each message is in quotes and separated by a comma"
]
Cog: Any = getattr(commands, "Cog", object)
class AnnounceDaily(Cog):
"""
Send daily announcements
"""
def __init__(self, bot: Red):
self.bot = bot
self.path = str(cog_data_path(self)).replace('\\', '/')
self.image_path = self.path + "/"
self.config = Config.get_conf(self, identifier=9811198108111121, force_registration=True)
default_global = {
'messages': [],
'images': [],
'time': {'hour': 0, 'minute': 0, 'second': 0}
}
default_guild = {
"channelid": None
}
self.config.register_global(**default_global)
self.config.register_guild(**default_guild)
async def _get_msgs(self):
return DEFAULT_MESSAGES + await self.config.messages()
@commands.group(name="announcedaily", aliases=['annd'])
@checks.mod_or_permissions(administrator=True)
@commands.guild_only()
async def _ad(self, ctx: commands.Context):
"""
Base command for managing AnnounceDaily settings
Do `[p]help annd <subcommand>` for more details
"""
if ctx.invoked_subcommand is None:
pass
@commands.command()
@checks.guildowner()
@commands.guild_only()
async def runannounce(self, ctx: commands.Context):
"""Manually run the daily announcement"""
await self.send_announcements()
await ctx.send("Success")
@_ad.command()
async def setchannel(self, ctx: commands.Context, channel: discord.TextChannel = None):
"""
Set the announcement channel for this server
Don't pass a channel to clear this server of receiving announcements
"""
if channel is not None:
await self.config.guild(ctx.guild).channelid.set(channel.id)
await ctx.send("Announcement channel has been set to {}".format(channel.mention))
else:
await self.config.guild(ctx.guild).channelid.set(None)
await ctx.send("Announcement channel has been cleared")
@_ad.command()
async def addmsg(self, ctx: commands.Context, *, msg):
"""
Add a message to the pool of announcement messages
"""
async with self.config.messages() as msgs:
msgs.append(msg)
await ctx.send("Message successfully added!")
@_ad.command()
async def addimg(self, ctx: commands.Context, filename=None):
"""
Add an image to the pool of announcement images
You must attach an image while executing this command
"""
if ctx.message.attachments:
att_ = ctx.message.attachments[0]
try:
h = att_.height
except AttributeError:
await ctx.send("You must attach an image, no other file will be accepted")
return
if filename is None:
filename = att_.filename
try:
# with open(self.image_path + filename, 'w') as f:
# await att_.save(f)
await att_.save(self.image_path + filename)
except discord.NotFound:
await ctx.send("Did you delete the message? Cause I couldn't download the attachment")
except discord.HTTPException:
await ctx.send("Failed to download the attachment, please try again")
else:
async with self.config.images() as images:
if filename in images:
await ctx.send("Image {} has been overwritten!".format(filename))
else:
images.append(filename)
await ctx.send("Image {} has been added!".format(filename))
else:
await ctx.send("You must attach an image when sending this command")
@_ad.command()
async def listmsg(self, ctx: commands.Context):
"""
List all registered announcement messages
"""
messages = await self.config.messages()
for page in pagify("\n".join("{} - {}".format(key, image) for key, image in enumerate(messages))):
await ctx.send(box(page))
await ctx.send("Done!")
@_ad.command()
async def listimg(self, ctx: commands.Context):
"""
List all registered announcement immages
"""
images = await self.config.images()
for page in pagify("\n".join(images)):
await ctx.send(box(page))
await ctx.send("Done!")
@_ad.command()
async def delmsg(self, ctx: commands.Context, index: int):
"""
Remove a message from the announcement pool
Must provide the index of the message, which can be found by using `[p]annd listmsg`
"""
async with self.config.messages() as messages:
try:
out = messages.pop(index)
except IndexError:
await ctx.send("Invalid index, check valid indexes with `listmsg` command")
return
await ctx.send("The following message was removed:\n```{}```".format(out))
@_ad.command()
async def delimg(self, ctx: commands.Context, filename: str):
"""
Remove an image from the announcement pool
Does not delete the file from the disk, so you may have to clean it up occasionally
"""
async with self.config.images() as images:
if filename not in images:
await ctx.send("This file doesn't exist")
else:
images.remove(filename)
await ctx.send("Successfully removed {}".format(filename))
@_ad.command()
async def settime(self, ctx: commands.Context, minutes_from_now: int):
"""
Set the daily announcement time
It will first announce at the time you provided, then it will repeat every 24 hours
"""
ann_time = datetime.now() + timedelta(minutes=minutes_from_now)
h = ann_time.hour
m = ann_time.minute
s = ann_time.second
await self.config.time.set({'hour': h, 'minute': m, 'second': s})
await ctx.send("Announcements time has been set to {}::{}::{} every day\n"
"**Changes will apply after next scheduled announcement or reload**".format(h, m, s))
async def send_announcements(self):
messages = await self._get_msgs()
images = await self.config.images()
total = len(messages) + len(images)
if total < 1:
return
x = random.randint(0, total - 1)
if x >= len(messages):
x -= len(messages)
choice = images[x]
choice = open(self.image_path + choice, 'rb')
is_image = True
else:
choice = messages[x]
is_image = False
for guild in self.bot.guilds:
channel = await self.config.guild(guild).channelid()
if channel is None:
continue
channel = guild.get_channel(channel)
if channel is None:
continue
if is_image:
await channel.send(file=discord.File(choice))
else:
await channel.send(choice)
async def check_day(self):
while self is self.bot.get_cog("AnnounceDaily"):
tomorrow = datetime.now() + timedelta(days=1)
time = await self.config.time()
h, m, s = time['hour'], time['minute'], time['second']
midnight = datetime(year=tomorrow.year, month=tomorrow.month,
day=tomorrow.day, hour=h, minute=m, second=s)
print("Sleeping for {} seconds".format((midnight - datetime.now()).seconds))
await asyncio.sleep((midnight - datetime.now()).seconds)
if self is not self.bot.get_cog("AnnounceDaily"):
print("Announce canceled, cog has been lost")
return
await self.send_announcements()
await asyncio.sleep(3)
# [p]setchannel #channelname - Set the announcement channel per server
# [p]addmsg <message goes here> - Adds a msg to the pool
# [p]addimg http://imgurl.com/image.jpg - Adds an image to the pool
# [p]listmsg - Lists all messages in the pool
# [p]listimg - Unsure about this one, but would probably just post all the images
# [p]delmsg - Remove msg from pool
# [p]delimg - Remove image from pool
# [p]settime <x> - S

@ -0,0 +1,18 @@
{
"author": [
"Bobloy"
],
"bot_version": [
3,
0,
0
],
"description": "Send daily announcements to all servers at a specified times",
"hidden": true,
"install_msg": "Thank you for installing AnnounceDaily! Get started with `[p]load announcedaily` and `[p]help AnnounceDaily`",
"requirements": [],
"short": "Send daily announcements",
"tags": [
"bobloy"
]
}

@ -0,0 +1,13 @@
from redbot.core.bot import Red
from .audiotrivia import AudioTrivia
async def setup(bot: Red):
if bot.get_cog("Trivia"):
print("Trivia is already loaded, attempting to unload it first")
bot.remove_cog("Trivia")
await bot.remove_loaded_package("trivia")
bot.unload_extension("trivia")
bot.add_cog(AudioTrivia(bot))

@ -0,0 +1,74 @@
"""Module to manage audio trivia sessions."""
import asyncio
import lavalink
from redbot.cogs.trivia import TriviaSession
class AudioSession(TriviaSession):
"""Class to run a session of audio trivia"""
def __init__(self, ctx, question_list: dict, settings: dict, player: lavalink.Player):
super().__init__(ctx, question_list, settings)
self.player = player
@classmethod
def start(cls, ctx, question_list, settings, player: lavalink.Player = None):
session = cls(ctx, question_list, settings, player)
loop = ctx.bot.loop
session._task = loop.create_task(session.run())
return session
async def run(self):
"""Run the audio trivia session.
In order for the trivia session to be stopped correctly, this should
only be called internally by `TriviaSession.start`.
"""
await self._send_startup_msg()
max_score = self.settings["max_score"]
delay = self.settings["delay"]
timeout = self.settings["timeout"]
for question, answers in self._iter_questions():
async with self.ctx.typing():
await asyncio.sleep(3)
self.count += 1
await self.player.stop()
msg = "**Question number {}!**\n\nName this audio!".format(self.count)
await self.ctx.send(msg)
# print("Audio question: {}".format(question))
# await self.ctx.invoke(self.audio.play(ctx=self.ctx, query=question))
# ctx_copy = copy(self.ctx)
# await self.ctx.invoke(self.player.play, query=question)
query = question.strip("<>")
tracks = await self.player.get_tracks(query)
seconds = tracks[0].length / 1000
if self.settings["repeat"] and seconds < delay:
tot_length = seconds + 0
while tot_length < delay:
self.player.add(self.ctx.author, tracks[0])
tot_length += seconds
else:
self.player.add(self.ctx.author, tracks[0])
if not self.player.current:
await self.player.play()
continue_ = await self.wait_for_answer(answers, delay, timeout)
if continue_ is False:
break
if any(score >= max_score for score in self.scores.values()):
await self.end_game()
break
else:
await self.ctx.send("There are no more questions!")
await self.end_game()
async def end_game(self):
await super().end_game()
await self.player.disconnect()

@ -0,0 +1,212 @@
import datetime
import pathlib
from typing import List
import lavalink
import yaml
from redbot.cogs.audio import Audio
from redbot.cogs.trivia import LOG
from redbot.cogs.trivia.trivia import InvalidListError, Trivia
from redbot.core import commands, Config, checks
from redbot.core.bot import Red
from redbot.core.data_manager import cog_data_path
from redbot.core.utils.chat_formatting import box
from .audiosession import AudioSession
class AudioTrivia(Trivia):
"""
Custom commands
Creates commands used to display text and adjust roles
"""
def __init__(self, bot: Red):
super().__init__()
self.bot = bot
self.audio = None
self.audioconf = Config.get_conf(self, identifier=651171001051118411410511810597, force_registration=True)
self.audioconf.register_guild(
delay=30.0,
repeat=True,
)
@commands.group()
@commands.guild_only()
@checks.mod_or_permissions(administrator=True)
async def atriviaset(self, ctx: commands.Context):
"""Manage Audio Trivia settings."""
audioset = self.audioconf.guild(ctx.guild)
settings_dict = await audioset.all()
msg = box(
"**Audio settings**\n"
"Answer time limit: {delay} seconds\n"
"Repeat Short Audio: {repeat}"
"".format(**settings_dict),
lang="py",
)
await ctx.send(msg)
@atriviaset.command(name="delay")
async def atriviaset_delay(self, ctx: commands.Context, seconds: float):
"""Set the maximum seconds permitted to answer a question."""
if seconds < 4.0:
await ctx.send("Must be at least 4 seconds.")
return
settings = self.audioconf.guild(ctx.guild)
await settings.delay.set(seconds)
await ctx.send("Done. Maximum seconds to answer set to {}.".format(seconds))
@atriviaset.command(name="repeat")
async def atriviaset_repeat(self, ctx: commands.Context, true_or_false: bool):
"""Set whether or not short audio will be repeated"""
settings = self.audioconf.guild(ctx.guild)
await settings.repeat.set(true_or_false)
await ctx.send("Done. Repeating short audio is now set to {}.".format(true_or_false))
@commands.group(invoke_without_command=True)
@commands.guild_only()
async def audiotrivia(self, ctx: commands.Context, *categories: str):
"""Start trivia session on the specified category.
You may list multiple categories, in which case the trivia will involve
questions from all of them.
"""
if not categories and ctx.invoked_subcommand is None:
await ctx.send_help()
return
if self.audio is None:
self.audio: Audio = self.bot.get_cog("Audio")
if self.audio is None:
await ctx.send("Audio is not loaded. Load it and try again")
return
categories = [c.lower() for c in categories]
session = self._get_trivia_session(ctx.channel)
if session is not None:
await ctx.send("There is already an ongoing trivia session in this channel.")
return
status = await self.audio.config.status()
if status:
await ctx.send("I recommend disabling audio status with `{}audioset status`".format(ctx.prefix))
if not self.audio._player_check(ctx):
try:
if not ctx.author.voice.channel.permissions_for(ctx.me).connect or self.audio._userlimit(
ctx.author.voice.channel
):
return await ctx.send("I don't have permission to connect to your channel."
)
await lavalink.connect(ctx.author.voice.channel)
lavaplayer = lavalink.get_player(ctx.guild.id)
lavaplayer.store("connect", datetime.datetime.utcnow())
except AttributeError:
return await ctx.send("Connect to a voice channel first.")
lavaplayer = lavalink.get_player(ctx.guild.id)
lavaplayer.store("channel", ctx.channel.id) # What's this for? I dunno
lavaplayer.store("guild", ctx.guild.id)
await self.audio._data_check(ctx)
if (
not ctx.author.voice or ctx.author.voice.channel != lavaplayer.channel
):
return await ctx.send("You must be in the voice channel to use the audiotrivia command.")
trivia_dict = {}
authors = []
for category in reversed(categories):
# We reverse the categories so that the first list's config takes
# priority over the others.
try:
dict_ = self.get_audio_list(category)
except FileNotFoundError:
await ctx.send(
"Invalid category `{0}`. See `{1}audiotrivia list`"
" for a list of trivia categories."
"".format(category, ctx.prefix)
)
except InvalidListError:
await ctx.send(
"There was an error parsing the trivia list for"
" the `{}` category. It may be formatted"
" incorrectly.".format(category)
)
else:
trivia_dict.update(dict_)
authors.append(trivia_dict.pop("AUTHOR", None))
continue
return
if not trivia_dict:
await ctx.send(
"The trivia list was parsed successfully, however it appears to be empty!"
)
return
settings = await self.conf.guild(ctx.guild).all()
audiosettings = await self.audioconf.guild(ctx.guild).all()
config = trivia_dict.pop("CONFIG", None)
if config and settings["allow_override"]:
settings.update(config)
settings["lists"] = dict(zip(categories, reversed(authors)))
# Delay in audiosettings overwrites delay in settings
combined_settings = {**settings, **audiosettings}
session = AudioSession.start(ctx=ctx, question_list=trivia_dict, settings=combined_settings, player=lavaplayer)
self.trivia_sessions.append(session)
LOG.debug("New audio trivia session; #%s in %d", ctx.channel, ctx.guild.id)
@audiotrivia.command(name="list")
@commands.guild_only()
async def audiotrivia_list(self, ctx: commands.Context):
"""List available trivia categories."""
lists = set(p.stem for p in self._audio_lists())
msg = box("**Available trivia lists**\n\n{}".format(", ".join(sorted(lists))))
if len(msg) > 1000:
await ctx.author.send(msg)
return
await ctx.send(msg)
def get_audio_list(self, category: str) -> dict:
"""Get the audiotrivia list corresponding to the given category.
Parameters
----------
category : str
The desired category. Case sensitive.
Returns
-------
`dict`
A dict mapping questions (`str`) to answers (`list` of `str`).
"""
try:
path = next(p for p in self._audio_lists() if p.stem == category)
except StopIteration:
raise FileNotFoundError("Could not find the `{}` category.".format(category))
with path.open(encoding="utf-8") as file:
try:
dict_ = yaml.load(file)
except yaml.error.YAMLError as exc:
raise InvalidListError("YAML parsing failed.") from exc
else:
return dict_
def _audio_lists(self) -> List[pathlib.Path]:
personal_lists = [p.resolve() for p in cog_data_path(self).glob("*.yaml")]
return personal_lists + get_core_lists()
def get_core_lists() -> List[pathlib.Path]:
"""Return a list of paths for all trivia lists packaged with the bot."""
core_lists_path = pathlib.Path(__file__).parent.resolve() / "data/lists"
return list(core_lists_path.glob("*.yaml"))

@ -0,0 +1,106 @@
AUTHOR: bobloy
https://www.youtube.com/watch?v=nfjiy-NX5b0:
- flashbang
https://www.youtube.com/watch?v=mJCE7s4W4IE:
- starting round
- round start
- start round
https://www.youtube.com/watch?v=XfLGi4cPu0Y:
- select team
- team select
https://www.youtube.com/watch?v=b6ScVgFs-DQ:
- desert eagle
- deagle
https://www.youtube.com/watch?v=JnHm-rn199Y:
- planted bomb
- bomb planted
- bomb plant
- plant bomb
https://www.youtube.com/watch?v=3wztV24tbVU:
- defusing bomb
- defuse bomb
- bomb defuse
- bomb defusing
https://www.youtube.com/watch?v=mpY9poBVje4:
- lobby
https://www.youtube.com/watch?v=zMT4ovCN7gk:
- usp-s
- usp s
- usps
https://www.youtube.com/watch?v=oI5Ww7y2aUQ:
- gut knife
https://www.youtube.com/watch?v=Dqmyxnx-OaQ:
- ak47
- ak 47
https://www.youtube.com/watch?v=Ny4hGdziZP4:
- hitmarker
- hit
- hitmaker
- marker
https://www.youtube.com/watch?v=vYUynDKM1Yw:
- awp
https://www.youtube.com/watch?v=52etXKmbQRM:
- butterfly knife
https://www.youtube.com/watch?v=99o4eyq0SzY:
- won round
- round won
- win round
- round win
https://www.youtube.com/watch?v=V5tv1ZzqI_U:
- lost round
- round lost
- lose round
- round loss
https://www.youtube.com/watch?v=1hI25OPdim0:
- flashbang toss
- toss flashbang
- throwing flashbang
- throw flashbang
- flashbang throwing
- flashbang throw
- tossing flashbang
- flashbang tossing
https://www.youtube.com/watch?v=oML0z2Aj_D4:
- firegrenade toss
- toss firegrenade
- throwing firegrenade
- throw firegrenade
- firegrenade throwing
- firegrenade throw
- tossing firegrenade
- firegrenade tossing
- fire grenade toss
- toss fire grenade
- throwing fire grenade
- throw fire grenade
- fire grenade throwing
- fire grenade throw
- tossing fire grenade
- fire grenade tossing
https://www.youtube.com/watch?v=9otQ9OLfaQc:
- grenade out
https://www.youtube.com/watch?v=tFA-8Vc32Kg:
- famas
https://www.youtube.com/watch?v=MdI1u8oXKZw:
- awp zoom
- zoom awp
- awp scope
- scope awp
https://www.youtube.com/watch?v=6NiZhX4h32Q:
- c4
https://www.youtube.com/watch?v=3N0NxsyWPiY:
- planting c4
- c4 planting
- plant c4
- c4 plant
https://www.youtube.com/watch?v=XLaJIXZ5QUc:
- awp
https://www.youtube.com/watch?v=DmuK9Wml88E:
- P90
https://www.youtube.com/watch?v=t1Ky_TbDXHY:
- smoke
https://www.youtube.com/watch?v=sJvdTbejDRY:
- kill bonus
https://www.youtube.com/watch?v=DYWi8qdvWCk:
- AK47
- AK 47

File diff suppressed because it is too large Load Diff

@ -0,0 +1,304 @@
AUTHOR: bobloy
https://www.youtube.com/watch?v=FrceWR4XnVU:
- shovel knight
https://www.youtube.com/watch?v=Fn0khIn2wfc:
- super mario world
https://www.youtube.com/watch?v=qkYSuWSPkHI:
- the legend of zelda
- legend of zelda
- zelda
https://www.youtube.com/watch?v=0hvlwLwxweI:
- dragon quest ix
- dragon quest 9
https://www.youtube.com/watch?v=GxrKe9z4CCo:
- chrono trigger
https://www.youtube.com/watch?v=pz3BQFXjEOI:
- super smash bros melee
- super smash bros. melee
- super smash brothers melee
https://www.youtube.com/watch?v=l_ioujmtqjg:
- super mario bros
- super mario brothers
- super mario bros.
https://www.youtube.com/watch?v=zTztR_y9iHc:
- banjo-kazooie
- banjo kazooie
https://www.youtube.com/watch?v=6gWyfQFdMJA:
- metroid samus returns
https://www.youtube.com/watch?v=0jXTBAGv9ZQ:
- halo
https://www.youtube.com/watch?v=Rhaq4JP_t6o:
- the elder scrolls iii morrowind
- morrowind
- elder scrolls iii
- elder scrolls 3
https://www.youtube.com/watch?v=ZksNhHyEhE0:
- sonic generations
https://www.youtube.com/watch?v=lndBgOrTWxo:
- donkey kong country 2
- donkey kong country two
https://www.youtube.com/watch?v=uTEMsmLoEA4:
- mario kart 8
- mario kart eight
https://www.youtube.com/watch?v=WA2WjP6sgrc:
- donkey kong country tropical freeze
- tropical freeze
https://www.youtube.com/watch?v=9wMjq58Fjvo:
- castle crashers
https://www.youtube.com/watch?v=sr2nK06zZkg:
- shadow of the colossus
https://www.youtube.com/watch?v=6CMTXyExkeI:
- final fantasy v
- final fantasy 5
https://www.youtube.com/watch?v=nRbROTdOgj0:
- legend of zelda skyward sword
- skyward sword
https://www.youtube.com/watch?v=LFcH84oNU6s:
- skies of arcadia
https://www.youtube.com/watch?v=VEIWhy-urqM:
- super mario galaxy
https://www.youtube.com/watch?v=IT12DW2Fm9M:
- final fantasy iv
- final fantasy 4
https://www.youtube.com/watch?v=UZbqrZJ9VA4:
- mother3
- mother 3
https://www.youtube.com/watch?v=o_ayLF9vdls:
- dragon age origins
https://www.youtube.com/watch?v=eVVXNDv8rY0:
- the elder scrolls v skyrim
- elder scrolls v
- elder scrolls 5
- the elder scrolls 5 skyrim
- skyrim
https://www.youtube.com/watch?v=kzvZE4BY0hY:
- fallout 4
https://www.youtube.com/watch?v=VTsD2FjmLsw:
- mass effect 2
https://www.youtube.com/watch?v=800be1ZmGd0:
- world of warcraft
https://www.youtube.com/watch?v=SXKrsJZWqK0:
- batman arkham city
- arkham city
https://www.youtube.com/watch?v=BLEBtvOhGnM:
- god of war iii
- god of war 3
https://www.youtube.com/watch?v=rxgTlQLm4Xg:
- gears of war 3
https://www.youtube.com/watch?v=QiPon8lr48U:
- metal gear solid 2
https://www.youtube.com/watch?v=qDnaIfiH37w:
- super smash bros wii u
- super smash bros. wii u
- super smash brothers wii u
- super smash bros wiiu
- super smash bros. wiiu
- super smash brothers wiiu
https://www.youtube.com/watch?v=_Uzlm2MaCWw:
- mega man maverick hunter x
- megaman maverick hunter x
- maverick hunter x
https://www.youtube.com/watch?v=-8wo0KBQ3oI:
- doom
https://www.youtube.com/watch?v=TN36CetQw6I:
- super smash bros brawl
- super smash bros. brawl
- super smash brothers brawl
https://www.youtube.com/watch?v=01IEjvD5lss:
- guilty gear
https://www.youtube.com/watch?v=VXX4Ft1I0Dw:
- dynasty warriors 6
https://www.youtube.com/watch?v=liRMh4LzQQU:
- doom 2016
- doom
https://www.youtube.com/watch?v=ouw3jLAUXWE:
- devil may cry 3
https://www.youtube.com/watch?v=B_MW65XxS7s:
- final fantasy vii
- final fantasy 7
https://www.youtube.com/watch?v=viM0-3PXef0:
- the witcher 3
- witcher 3
https://www.youtube.com/watch?v=WQYN2P3E06s:
- civilization vi
- civilization 6
https://www.youtube.com/watch?v=qOMQxVtbkik:
- guild wars 2
- guild wars two
https://www.youtube.com/watch?v=WwHrQdC02FY:
- final fantasy vi
- final fantasy 6
https://www.youtube.com/watch?v=2_wkJ377LzU:
- journey
https://www.youtube.com/watch?v=IJiHDmyhE1A:
- civilization iv
- civilization 4
https://www.youtube.com/watch?v=kN_LvY97Rco:
- ori and the blind forest
https://www.youtube.com/watch?v=TO7UI0WIqVw:
- super smash bros brawl
- super smash bros. brawl
- super smash brothers brawl
https://www.youtube.com/watch?v=s9XljBWGrRQ:
- kingdom hearts
https://www.youtube.com/watch?v=xkolWbZdGbM:
- shenmue
https://www.youtube.com/watch?v=h-0G_FI61a8:
- final fantasy x
- final fantasy 10
https://www.youtube.com/watch?v=do5NTPLMqXQ:
- fire emblem fates
https://www.youtube.com/watch?v=eFVj0Z6ahcI:
- persona 5
- persona five
https://www.youtube.com/watch?v=PhciLj5VzOk:
- super mario odyssey
https://www.youtube.com/watch?v=GBPbJyxqHV0:
- super mario 64
- mario 64
https://www.youtube.com/watch?v=wRWq53IFXVQ:
- the legend of zelda the wind waker
- legend of zelda the wind waker
- the legend of zelda wind waker
- legend of zelda wind waker
- wind waker
https://www.youtube.com/watch?v=nkPF5UiDi4g:
- uncharted 2
https://www.youtube.com/watch?v=CdYen5UII0s:
- battlefield 1
- battlefield one
https://www.youtube.com/watch?v=8yj-25MOgOM:
- star fox zero
- starfox zero
https://www.youtube.com/watch?v=Z9dNrmGD7mU:
- dark souls iii
- dark souls 3
https://www.youtube.com/watch?v=Bio99hoZVYI:
- fire emblem awakening
https://www.youtube.com/watch?v=4EcgruWlXnQ:
- monty on the run
https://www.youtube.com/watch?v=oEf8gPFFZ58:
- mega man 3
- megaman 3
https://www.youtube.com/watch?v=ifbr2NQ3Js0:
- castlevania
https://www.youtube.com/watch?v=W7rhEKTX-sE:
- shovel knight
https://www.youtube.com/watch?v=as_ct9tgkZA:
- mega man 2
- megaman 2
https://www.youtube.com/watch?v=FB9Pym-sdbs:
- actraiser
https://www.youtube.com/watch?v=G3zhZHU6B2M:
- ogre battle
https://www.youtube.com/watch?v=hlrOAEr6dXc:
- metroid zero mission
- zero mission
https://www.youtube.com/watch?v=jl6kjAkVw_s:
- sonic 2
https://www.youtube.com/watch?v=K8GRDNU50b8:
- the legend of zelda ocarina of time
- legend of zelda ocarina of time
- ocarina of time
https://www.youtube.com/watch?v=dTZ8uhJ5hIE:
- kirby's epic yarn
- kirbys epic yarn
https://www.youtube.com/watch?v=QaaD9CnWgig:
- super smash bros brawl
- super smash bros. brawl
- super smash brothers brawl
https://www.youtube.com/watch?v=JDqJa1RC3q8:
- kid icarus uprising
https://www.youtube.com/watch?v=MQurUl4Snio:
- punch-out!!
- punch-out
- punch out
- punchout
https://www.youtube.com/watch?v=vlz6qgahnYQ:
- super street fighter 2 turbo
- super street fighter two turbo
- street fighter 2 turbo
- street fighter two turbo
https://www.youtube.com/watch?v=FBLp-3Rw_u0:
- mario & luigi bowser's inside story
- mario and luigi bowser's inside story
- mario & luigi bowsers inside story
- mario and luigi bowsers inside story
- bowser's inside story
- bowsers inside story
https://www.youtube.com/watch?v=jqE8M2ZnFL8:
- grand theft auto 4
- grand theft auto four
https://www.youtube.com/watch?v=GQZLEegUK74:
- goldeneye 007
- goldeneye
https://www.youtube.com/watch?v=nCe7W1ajzIE:
- tmnt iv turtles in time
- tmnt iv
- tmnt 4 turtles in time
- tmnt 4
- turtles in time
https://www.youtube.com/watch?v=YHEifuLCSIY:
- ducktales
https://www.youtube.com/watch?v=rXefFHRgyE0:
- pokemon diamond
- pokemon pearl
- pokemon platinum
https://www.youtube.com/watch?v=4jaIUlz-wNU:
- warriors orochi 3
- warriors orochi three
https://www.youtube.com/watch?v=EAwWPadFsOA:
- mortal kombat
https://www.youtube.com/watch?v=XI1VpElKWF8:
- metal gear solid
https://www.youtube.com/watch?v=zz8m1oEkW5k:
- tetris blitz
https://www.youtube.com/watch?v=gMdX_Iloow8:
- ultimate marvel vs capcom 3
- marvel vs capcom 3
- ultimate marvel vs. capcom 3
- marvel vs. capcom 3
https://www.youtube.com/watch?v=vRe3h1iQ1Os:
- sonic the hedgehog 2006
- sonic the hegehog
https://www.youtube.com/watch?v=SYTS2sJWcIs:
- pokemon heartgold
- pokemon soulsilver
https://www.youtube.com/watch?v=5-BIqqSe1nU:
- red dead redemption
https://www.youtube.com/watch?v=wp6QpMWaKpE:
- bioshock
https://www.youtube.com/watch?v=R9XdMnsKvUs:
- call of duty 4 modern warfare
- call of duty 4
- modern warfare
https://www.youtube.com/watch?v=f-sQhBDsjgE:
- killzone 2
https://www.youtube.com/watch?v=-_O6F5FwQ0s:
- soul calibur v
- sould calibur 5
https://www.youtube.com/watch?v=MgK_OfW7nl4:
- the legend of zelda breath of the wild
- legend of zelda breath of the wild
- breath of the wild
https://www.youtube.com/watch?v=tz82xbLvK_k:
- undertale
https://www.youtube.com/watch?v=J46RY4PU8a8:
- chrono cross
https://www.youtube.com/watch?v=6LB7LZZGpkw:
- silent hill 2
https://www.youtube.com/watch?v=ya3yxTbkh5s:
- Ōkami
- okami
- wolf
https://www.youtube.com/watch?v=KGidvt4NTPI:
- hikari 光
- hikari
-
- light
https://www.youtube.com/watch?v=JbXVNKtmWnc:
- final fantasy vi
- final fantasy 6
https://www.youtube.com/watch?v=-jMDutXA4-M:
- final fantasy iii
- final fantasy 3

@ -0,0 +1,4 @@
https://www.youtube.com/watch?v=hfyE220BsD0:
- holiday
https://www.youtube.com/watch?v=Hh3U9iPKeXQ:
- sultans of swing

@ -0,0 +1,4 @@
https://www.youtube.com/watch?v=Hi1kUdreiWk:
- Jinx
https://www.youtube.com/watch?v=PNYHFluhOGI:
- Teemo

@ -0,0 +1,20 @@
{
"author": [
"Bobloy"
],
"bot_version": [
3,
0,
0
],
"description": "Start an Audio Trivia game",
"hidden": false,
"install_msg": "Thank you for installing Audio trivia!\n You **MUST** unload trivia to use this (`[p]unload trivia`)\n Then you can get started with `[p]load audiotrivia` and `[p]help AudioTrivia`",
"requirements": [],
"short": "Start an Audio Trivia game",
"tags": [
"fox",
"bobloy",
"games"
]
}

@ -1,16 +1,16 @@
import discord
import asyncio
import re
from typing import Any
from discord.ext import commands
import discord
from redbot.core import Config, checks
from redbot.core import commands
from redbot.core.utils.chat_formatting import pagify, box
import os
import re
Cog: Any = getattr(commands, "Cog", object)
class CCRole:
class CCRole(Cog):
"""
Custom commands
Creates commands used to display text and adjust roles
@ -26,17 +26,22 @@ class CCRole:
self.config.register_guild(**default_guild)
@commands.group(no_pm=True)
@commands.guild_only()
@commands.group()
async def ccrole(self, ctx):
"""Custom commands management"""
"""Custom commands management with roles
Highly customizable custom commands with role management."""
if not ctx.invoked_subcommand:
await ctx.send_help()
pass
@ccrole.command(name="add")
@checks.mod_or_permissions(administrator=True)
async def ccrole_add(self, ctx, command: str):
"""Adds a custom command with roles"""
"""Adds a custom command with roles
When adding text, put arguments in `{}` to eval them
Options: `{author}`, `{target}`, `{server}`, `{channel}`, `{message}`"""
command = command.lower()
if command in self.bot.all_commands:
await ctx.send("That command is already a standard command.")
@ -46,9 +51,9 @@ class CCRole:
author = ctx.author
channel = ctx.channel
cmdlist = self.config.guild(ctx.guild).cmdlist
cmd_list = self.config.guild(guild).cmdlist
if await cmdlist.get_raw(command, default=None):
if await cmd_list.get_raw(command, default=None):
await ctx.send("This command already exists. Delete it with `{}ccrole delete` first.".format(ctx.prefix))
return
@ -62,6 +67,7 @@ class CCRole:
answer = await self.bot.wait_for('message', timeout=120, check=check)
except asyncio.TimeoutError:
await ctx.send("Timed out, canceling")
return
arole_list = []
if answer.content.upper() != "NONE":
@ -76,6 +82,7 @@ class CCRole:
answer = await self.bot.wait_for('message', timeout=120, check=check)
except asyncio.TimeoutError:
await ctx.send("Timed out, canceling")
return
rrole_list = []
if answer.content.upper() != "NONE":
@ -85,12 +92,14 @@ class CCRole:
return
# Roles to use
await ctx.send('What roles are allowed to use this command? (Must be comma separated)\nSay `None` to allow all roles')
await ctx.send(
'What roles are allowed to use this command? (Must be comma separated)\nSay `None` to allow all roles')
try:
answer = await self.bot.wait_for('message', timeout=120, check=check)
except asyncio.TimeoutError:
await ctx.send("Timed out, canceling")
return
prole_list = []
if answer.content.upper() != "NONE":
@ -100,12 +109,13 @@ class CCRole:
return
# Selfrole
await ctx.send('Is this a targeted command?(yes/no)\nNo will make this a selfrole command')
await ctx.send('Is this a targeted command?(yes//no)\nNo will make this a selfrole command')
try:
answer = await self.bot.wait_for('message', timeout=120, check=check)
except asyncio.TimeoutError:
await ctx.send("Timed out, canceling")
return
if answer.content.upper() in ["Y", "YES"]:
targeted = True
@ -115,12 +125,18 @@ class CCRole:
await ctx.send("This command will be **`selfrole`**")
# Message to send
await ctx.send('What message should the bot say when using this command?\nSay `None` to send the default `Success!` message')
await ctx.send(
'What message should the bot say when using this command?\n'
'Say `None` to send the default `Success!` message\n'
'Eval Options: `{author}`, `{target}`, `{server}`, `{channel}`, `{message}`\n'
'For example: `Welcome {target.mention} to {server.name}!`')
try:
answer = await self.bot.wait_for('message', timeout=120, check=check)
except asyncio.TimeoutError:
await ctx.send("Timed out, canceling")
return
text = "Success!"
if answer.content.upper() != "NONE":
text = answer.content
@ -129,41 +145,69 @@ class CCRole:
out = {'text': text, 'aroles': arole_list, 'rroles': rrole_list, "proles": prole_list, "targeted": targeted}
await cmdlist.set_raw(command, value=out)
await cmd_list.set_raw(command, value=out)
ctx.send("Custom Command **`{}`** successfully added".format(command))
await ctx.send("Custom Command **`{}`** successfully added".format(command))
@ccrole.command(name="delete")
@checks.mod_or_permissions(administrator=True)
async def ccrole_delete(self, ctx, command: str):
"""Deletes a custom command
Example:
[p]ccrole delete yourcommand"""
`[p]ccrole delete yourcommand`"""
guild = ctx.guild
command = command.lower()
if not await self.config.guild(ctx.guild).cmdlist.get_raw(command, default=None):
if not await self.config.guild(guild).cmdlist.get_raw(command, default=None):
await ctx.send("That command doesn't exist")
else:
await self.config.guild(ctx.guild).cmdlist.set_raw(command, value=None)
await self.config.guild(guild).cmdlist.set_raw(command, value=None)
await ctx.send("Custom command successfully deleted.")
@ccrole.command(name="details")
async def ccrole_details(self, ctx, command: str):
"""Provide details about passed custom command"""
guild = ctx.guild
command = command.lower()
cmd = await self.config.guild(guild).cmdlist.get_raw(command, default=None)
if cmd is None:
await ctx.send("That command doesn't exist")
return
embed = discord.Embed(title=command,
description="{} custom command".format("Targeted" if cmd['targeted'] else "Non-Targeted"))
def process_roles(role_list):
if not role_list:
return "None"
return ", ".join([discord.utils.get(ctx.guild.roles, id=roleid).name for roleid in role_list])
embed.add_field(name="Text", value="```{}```".format(cmd['text']))
embed.add_field(name="Adds Roles", value=process_roles(cmd['aroles']), inline=True)
embed.add_field(name="Removes Roles", value=process_roles(cmd['rroles']), inline=True)
embed.add_field(name="Role Restrictions", value=process_roles(cmd['proles']), inline=True)
await ctx.send(embed=embed)
@ccrole.command(name="list")
async def ccrole_list(self, ctx):
"""Shows custom commands list"""
guild = ctx.guild
commands = await self.config.guild(ctx.guild).cmdlist()
if not commands:
await ctx.send("There are no custom commands in this server. Use `{}ccrole add` to start adding some.".format(ctx.prefix))
cmd_list = await self.config.guild(guild).cmdlist()
cmd_list = {k: v for k, v in cmd_list.items() if v}
if not cmd_list:
await ctx.send(
"There are no custom commands in this server. Use `{}ccrole add` to start adding some.".format(
ctx.prefix))
return
commands = ", ".join([ctx.prefix + c for c in sorted(commands.keys())])
commands = "Custom commands:\n\n" + commands
cmd_list = ", ".join([ctx.prefix + c for c in sorted(cmd_list.keys())])
cmd_list = "Custom commands:\n\n" + cmd_list
if len(commands) < 1500:
await ctx.send(box(commands))
if len(cmd_list) < 1500: # I'm allowed to have arbitrary numbers for when it's too much to dm dammit
await ctx.send(box(cmd_list))
else:
for page in pagify(commands, delims=[" ", "\n"]):
for page in pagify(cmd_list, delims=[" ", "\n"]):
await ctx.author.send(box(page))
await ctx.send("Command list DM'd")
@ -177,20 +221,18 @@ class CCRole:
except ValueError:
return
cmdlist = self.config.guild(guild).cmdlist
cmd = message.content[len(prefix):].split()[0]
cmd = await cmdlist.get_raw(cmd.lower(), default=None)
cmd = message.content[len(prefix):].split()[0].lower()
cmd = await cmdlist.get_raw(cmd, default=None)
if cmd:
if cmd is not None:
await self.eval_cc(cmd, message)
async def _get_roles_from_content(self, ctx, content):
content_list = content.split(",")
role_list = []
try:
role_list = [discord.utils.get(ctx.guild.roles, name=role.strip(' ')).id for role in content_list]
except:
except (discord.HTTPException, AttributeError): # None.id is attribute error
return None
else:
return role_list
@ -223,14 +265,13 @@ class CCRole:
if cmd['targeted']:
try:
target = discord.utils.get(message.guild.members, mention=message.content.split()[1])
except:
except IndexError: # .split() return list of len<2
target = None
if not target:
out_message = "This command is targeted! @mention a target\n`{} <target>`".format(message.content.split()[0])
out_message = "This custom command is targeted! @mention a target\n`{} <target>`".format(
message.content.split()[0])
await message.channel.send(out_message)
return
else:
target = message.author
@ -251,37 +292,40 @@ class CCRole:
await target.remove_roles(*rrole_list)
except discord.Forbidden:
await message.channel.send("Permission error: Unable to remove roles")
await message.channel.send(cmd['text'])
# {'text': text, 'aroles': arole_list, 'rroles': rrole_list, "proles", prole_list, "targeted": targeted}
# def format_cc(self, command, message):
# results = re.findall("\{([^}]+)\}", command)
# for result in results:
# param = self.transform_parameter(result, message)
# command = command.replace("{" + result + "}", param)
# return command
# def transform_parameter(self, result, message):
# """
# For security reasons only specific objects are allowed
# Internals are ignored
# """
# raw_result = "{" + result + "}"
# objects = {
# "message" : message,
# "author" : message.author,
# "channel" : message.channel,
# "server" : message.server
# }
# if result in objects:
# return str(objects[result])
# try:
# first, second = result.split(".")
# except ValueError:
# return raw_result
# if first in objects and not second.startswith("_"):
# first = objects[first]
# else:
# return raw_result
# return str(getattr(first, second, raw_result))
out_message = self.format_cc(cmd, message, target)
await message.channel.send(out_message)
def format_cc(self, cmd, message, target):
out = cmd['text']
results = re.findall("{([^}]+)\}", out)
for result in results:
param = self.transform_parameter(result, message, target)
out = out.replace("{" + result + "}", param)
return out
def transform_parameter(self, result, message, target):
"""
For security reasons only specific objects are allowed
Internals are ignored
"""
raw_result = "{" + result + "}"
objects = {
"message": message,
"author": message.author,
"channel": message.channel,
"server": message.guild,
"guild": message.guild,
"target": target
}
if result in objects:
return str(objects[result])
try:
first, second = result.split(".")
except ValueError:
return raw_result
if first in objects and not second.startswith("_"):
first = objects[first]
else:
return raw_result
return str(getattr(first, second, raw_result))

@ -1,10 +1,22 @@
{
"author" : ["Bobloy"],
"bot_version" : [3,0,0],
"author": [
"Bobloy"
],
"bot_version": [
3,
0,
0
],
"description": "[Incomplete] Creates custom commands to adjust roles and send custom messages",
"hidden": false,
"install_msg" : "Thank you for installing Custom Commands w/ Roles.",
"install_msg": "Thank you for installing Custom Commands w/ Roles. Get started with `[p]load ccrole` and `[p]help CCRole`",
"requirements": [],
"short": "[Incomplete] Creates commands that adjust roles",
"tags" : ["fox", "bobloy", "utility", "tools", "roles"]
"tags": [
"fox",
"bobloy",
"utility",
"tools",
"roles"
]
}

@ -1,5 +1,11 @@
from .chatter import Chatter
from . import chatterbot
from .chat import Chatter
def setup(bot):
bot.add_cog(Chatter(bot))
__all__ = (
'chatterbot'
)

@ -0,0 +1,202 @@
import asyncio
import pathlib
from datetime import datetime, timedelta
import discord
from redbot.core import Config
from redbot.core import commands
from redbot.core.data_manager import cog_data_path
from chatter.chatterbot import ChatBot
from chatter.chatterbot.comparisons import levenshtein_distance
from chatter.chatterbot.response_selection import get_first_response
from chatter.chatterbot.trainers import ListTrainer
from typing import Any
Cog: Any = getattr(commands, "Cog", object)
class Chatter(Cog):
"""
This cog trains a chatbot that will talk like members of your Guild
"""
def __init__(self, bot):
self.bot = bot
self.config = Config.get_conf(self, identifier=6710497116116101114)
default_global = {}
default_guild = {
"whitelist": None,
"days": 1
}
path: pathlib.Path = cog_data_path(self)
data_path = path / ("database.sqlite3")
self.chatbot = ChatBot(
"ChatterBot",
storage_adapter='chatter.chatterbot.storage.SQLStorageAdapter',
database=str(data_path),
statement_comparison_function=levenshtein_distance,
response_selection_method=get_first_response,
logic_adapters=[
'chatter.chatterbot.logic.BestMatch',
{
'import_path': 'chatter.chatterbot.logic.LowConfidenceAdapter',
'threshold': 0.65,
'default_response': ':thinking:'
}
]
)
self.chatbot.set_trainer(ListTrainer)
self.config.register_global(**default_global)
self.config.register_guild(**default_guild)
self.loop = asyncio.get_event_loop()
async def _get_conversation(self, ctx, in_channel: discord.TextChannel = None):
"""
Compiles all conversation in the Guild this bot can get it's hands on
Currently takes a stupid long time
Returns a list of text
"""
out = [[]]
after = datetime.today() - timedelta(days=(await self.config.guild(ctx.guild).days()))
def new_message(msg, sent, out_in):
if sent is None:
return False
if len(out_in) < 2:
return False
return msg.created_at - sent >= timedelta(hours=3) # This should be configurable perhaps
for channel in ctx.guild.text_channels:
if in_channel:
channel = in_channel
await ctx.send("Gathering {}".format(channel.mention))
user = None
i = 0
send_time = None
try:
async for message in channel.history(limit=None, reverse=True, after=after):
# if message.author.bot: # Skip bot messages
# continue
if new_message(message, send_time, out[i]):
out.append([])
i += 1
user = None
else:
send_time = message.created_at + timedelta(seconds=1)
if user == message.author:
out[i][-1] += "\n" + message.clean_content
else:
user = message.author
out[i].append(message.clean_content)
except discord.Forbidden:
pass
except discord.HTTPException:
pass
if in_channel:
break
return out
def _train(self, data):
try:
for convo in data:
self.chatbot.train(convo)
except:
return False
return True
@commands.group(invoke_without_command=False)
async def chatter(self, ctx: commands.Context):
"""
Base command for this cog. Check help for the commands list.
"""
if ctx.invoked_subcommand is None:
pass
@chatter.command()
async def age(self, ctx: commands.Context, days: int):
"""
Sets the number of days to look back
Will train on 1 day otherwise
"""
await self.config.guild(ctx.guild).days.set(days)
await ctx.send("Success")
@chatter.command()
async def backup(self, ctx, backupname):
"""
Backup your training data to a json for later use
"""
await ctx.send("Backing up data, this may take a while")
future = await self.loop.run_in_executor(None, self.chatbot.trainer.export_for_training,
'./{}.json'.format(backupname))
if future:
await ctx.send("Backup successful!")
else:
await ctx.send("Error occurred :(")
@chatter.command()
async def train(self, ctx: commands.Context, channel: discord.TextChannel):
"""
Trains the bot based on language in this guild
"""
conversation = await self._get_conversation(ctx, channel)
if not conversation:
await ctx.send("Failed to gather training data")
return
await ctx.send("Gather successful! Training begins now\n(**This will take a long time, be patient**)")
embed = discord.Embed(title="Loading")
embed.set_image(url="http://www.loop.universaleverything.com/animations/1295.gif")
temp_message = await ctx.send(embed=embed)
future = await self.loop.run_in_executor(None, self._train, conversation)
try:
await temp_message.delete()
except:
pass
if future:
await ctx.send("Training successful!")
else:
await ctx.send("Error occurred :(")
async def on_message(self, message: discord.Message):
"""
Credit to https://github.com/Twentysix26/26-Cogs/blob/master/cleverbot/cleverbot.py
for on_message recognition of @bot
"""
author = message.author
try:
guild: discord.Guild = message.guild
except AttributeError: # Not a guild message
return
channel: discord.TextChannel = message.channel
if author.id != self.bot.user.id:
to_strip = "@" + guild.me.display_name + " "
text = message.clean_content
if not text.startswith(to_strip):
return
text = text.replace(to_strip, "", 1)
async with channel.typing():
future = await self.loop.run_in_executor(None, self.chatbot.get_response, text)
if future and str(future):
await channel.send(str(future))
else:
await channel.send(':thinking:')

@ -1,141 +0,0 @@
import asyncio
from typing import List, Union
import discord
from discord.ext import commands
from redbot.core import Config
from redbot.core.bot import Red
from .source import ChatBot
from .source.trainers import ListTrainer
from datetime import datetime,timedelta
class Chatter:
"""
This cog trains a chatbot that will talk like members of your Guild
"""
def __init__(self, bot):
self.bot = bot
self.config = Config.get_conf(self, identifier=6710497116116101114)
default_global = {}
default_guild = {
"whitelist": None,
"days": 1
}
self.chatbot = ChatBot("ChatterBot")
self.chatbot.set_trainer(ListTrainer)
self.config.register_global(**default_global)
self.config.register_guild(**default_guild)
self.loop = asyncio.get_event_loop()
async def _get_conversation(self, ctx, in_channel: discord.TextChannel=None):
"""
Compiles all conversation in the Guild this bot can get it's hands on
Currently takes a stupid long time
Returns a list of text
"""
out = []
after = datetime.today() - timedelta(days=(await self.config.guild(ctx.guild).days()))
for channel in ctx.guild.text_channels:
if in_channel:
channel = in_channel
await ctx.send("Gathering {}".format(channel.mention))
user = None
try:
async for message in channel.history(limit=None, reverse=True, after=after):
if user == message.author:
out[-1] += "\n"+message.clean_content
else:
user = message.author
out.append(message.clean_content)
except discord.Forbidden:
pass
except discord.HTTPException:
pass
if in_channel:
break
return out
def _train(self, data):
try:
self.chatbot.train(data)
except:
return False
return True
@commands.group()
async def chatter(self, ctx: commands.Context):
"""
Base command for this cog. Check help for the commands list.
"""
if ctx.invoked_subcommand is None:
await ctx.send_help()
@chatter.command()
async def age(self, ctx: commands.Context, days: int):
"""
Sets the number of days to look back
Will train on 1 day otherwise
"""
await self.config.guild(ctx.guild).days.set(days)
await ctx.send("Success")
@chatter.command()
async def train(self, ctx: commands.Context, channel: discord.TextChannel = None):
"""
Trains the bot based on language in this guild
"""
conversation = await self._get_conversation(ctx, channel)
if not conversation:
await ctx.send("Failed to gather training data")
return
await ctx.send("Gather successful! Training begins now\n(**This will take a long time, be patient**)")
embed=discord.Embed(title="Loading")
embed.set_image(url="http://www.loop.universaleverything.com/animations/1295.gif")
temp_message = await ctx.send(embed=embed)
future = await self.loop.run_in_executor(None, self._train, conversation)
try:
await temp_message.delete()
except:
pass
if future:
await ctx.send("Training successful!")
else:
await ctx.send("Error occurred :(")
async def on_message(self, message):
"""
Credit to https://github.com/Twentysix26/26-Cogs/blob/master/cleverbot/cleverbot.py
for on_message recognition of @bot
"""
author = message.author
channel = message.channel
if message.author.id != self.bot.user.id:
to_strip = "@" + author.guild.me.display_name + " "
text = message.clean_content
if not text.startswith(to_strip):
return
text = text.replace(to_strip, "", 1)
async with channel.typing():
response = self.chatbot.get_response(text)
if not response:
response = ":thinking:"
await channel.send(response)

@ -3,7 +3,7 @@ ChatterBot is a machine learning, conversational dialog engine.
"""
from .chatterbot import ChatBot
__version__ = '0.8.4'
__version__ = '0.8.5'
__author__ = 'Gunther Cox'
__email__ = 'gunthercx@gmail.com'
__url__ = 'https://github.com/gunthercox/ChatterBot'

@ -1,6 +1,5 @@
import sys
if __name__ == '__main__':
import importlib

@ -16,7 +16,7 @@ class Adapter(object):
"""
Gives the adapter access to an instance of the ChatBot class.
:param chatbot: A chat bot instanse.
:param chatbot: A chat bot instance.
:type chatbot: ChatBot
"""
self.chatbot = chatbot

@ -1,9 +1,8 @@
from __future__ import unicode_literals
import logging
from .storage import StorageAdapter
from .input import InputAdapter
from .output import OutputAdapter
from . import utils
from chatter.chatterbot import utils
class ChatBot(object):
@ -12,7 +11,7 @@ class ChatBot(object):
"""
def __init__(self, name, **kwargs):
from .logic import MultiLogicAdapter
from chatter.chatterbot.logic import MultiLogicAdapter
self.name = name
kwargs['name'] = name
@ -20,20 +19,20 @@ class ChatBot(object):
self.default_session = None
storage_adapter = kwargs.get('storage_adapter', 'chatter.source.storage.SQLStorageAdapter')
storage_adapter = kwargs.get('storage_adapter', 'chatter.chatterbot.storage.SQLStorageAdapter')
logic_adapters = kwargs.get('logic_adapters', [
'chatter.source.logic.BestMatch'
'chatter.chatterbot.logic.BestMatch'
])
input_adapter = kwargs.get('input_adapter', 'chatter.source.input.VariableInputTypeAdapter')
input_adapter = kwargs.get('input_adapter', 'chatter.chatterbot.input.VariableInputTypeAdapter')
output_adapter = kwargs.get('output_adapter', 'chatter.source.output.OutputAdapter')
output_adapter = kwargs.get('output_adapter', 'chatter.chatterbot.output.OutputAdapter')
# Check that each adapter is a valid subclass of it's respective parent
utils.validate_adapter_class(storage_adapter, StorageAdapter)
utils.validate_adapter_class(input_adapter, InputAdapter)
utils.validate_adapter_class(output_adapter, OutputAdapter)
# utils.validate_adapter_class(storage_adapter, StorageAdapter)
# utils.validate_adapter_class(input_adapter, InputAdapter)
# utils.validate_adapter_class(output_adapter, OutputAdapter)
self.logic = MultiLogicAdapter(**kwargs)
self.storage = utils.initialize_class(storage_adapter, **kwargs)
@ -45,7 +44,7 @@ class ChatBot(object):
# Add required system logic adapter
self.logic.system_adapters.append(
utils.initialize_class('chatter.source.logic.NoKnowledgeAdapter', **kwargs)
utils.initialize_class('chatter.chatterbot.logic.NoKnowledgeAdapter', **kwargs)
)
for adapter in logic_adapters:
@ -59,7 +58,7 @@ class ChatBot(object):
preprocessors = kwargs.get(
'preprocessors', [
'chatter.source.preprocessors.clean_whitespace'
'chatter.chatterbot.preprocessors.clean_whitespace'
]
)
@ -69,7 +68,7 @@ class ChatBot(object):
self.preprocessors.append(utils.import_module(preprocessor))
# Use specified trainer or fall back to the default
trainer = kwargs.get('trainer', 'chatter.source.trainers.Trainer')
trainer = kwargs.get('trainer', 'chatter.chatterbot.trainers.Trainer')
TrainerClass = utils.import_module(trainer)
self.trainer = TrainerClass(self.storage, **kwargs)
self.training_data = kwargs.get('training_data')
@ -137,7 +136,7 @@ class ChatBot(object):
"""
Learn that the statement provided is a valid response.
"""
from .conversation import Response
from chatter.chatterbot.conversation import Response
if previous_statement:
statement.add_response(

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-
import sys
"""
@ -58,17 +57,12 @@ class LevenshteinDistance(Comparator):
:rtype: float
"""
PYTHON = sys.version_info[0]
# Return 0 if either statement has a falsy text value
if not statement.text or not other_statement.text:
return 0
# Get the lowercase version of both strings
if PYTHON < 3:
statement_text = unicode(statement.text.lower()) # NOQA
other_statement_text = unicode(other_statement.text.lower()) # NOQA
else:
statement_text = str(statement.text.lower())
other_statement_text = str(other_statement.text.lower())
@ -98,7 +92,7 @@ class SynsetDistance(Comparator):
"""
Download required NLTK corpora if they have not already been downloaded.
"""
from .utils import nltk_download_corpus
from chatter.chatterbot.utils import nltk_download_corpus
nltk_download_corpus('corpora/wordnet')
@ -106,7 +100,7 @@ class SynsetDistance(Comparator):
"""
Download required NLTK corpora if they have not already been downloaded.
"""
from .utils import nltk_download_corpus
from chatter.chatterbot.utils import nltk_download_corpus
nltk_download_corpus('tokenizers/punkt')
@ -114,7 +108,7 @@ class SynsetDistance(Comparator):
"""
Download required NLTK corpora if they have not already been downloaded.
"""
from .utils import nltk_download_corpus
from chatter.chatterbot.utils import nltk_download_corpus
nltk_download_corpus('corpora/stopwords')
@ -130,7 +124,7 @@ class SynsetDistance(Comparator):
"""
from nltk.corpus import wordnet
from nltk import word_tokenize
from . import utils
from chatter.chatterbot import utils
import itertools
tokens1 = word_tokenize(statement.text.lower())
@ -183,7 +177,7 @@ class SentimentComparison(Comparator):
Download the NLTK vader lexicon for sentiment analysis
that is required for this algorithm to run.
"""
from .utils import nltk_download_corpus
from chatter.chatterbot.utils import nltk_download_corpus
nltk_download_corpus('sentiment/vader_lexicon')
@ -258,7 +252,7 @@ class JaccardSimilarity(Comparator):
Download the NLTK wordnet corpora that is required for this algorithm
to run only if the corpora has not already been downloaded.
"""
from .utils import nltk_download_corpus
from chatter.chatterbot.utils import nltk_download_corpus
nltk_download_corpus('corpora/wordnet')

@ -3,6 +3,7 @@ class StatementMixin(object):
This class has shared methods used to
normalize different statement models.
"""
tags = []
def get_tags(self):
"""
@ -25,7 +26,6 @@ class Statement(StatementMixin):
"""
def __init__(self, text, **kwargs):
import sys
# Try not to allow non-string types to be passed to statements
try:
@ -33,13 +33,6 @@ class Statement(StatementMixin):
except UnicodeEncodeError:
pass
# Prefer decoded utf8-strings in Python 2.7
if sys.version_info[0] < 3:
try:
text = text.decode('utf-8')
except UnicodeEncodeError:
pass
self.text = text
self.tags = kwargs.pop('tags', [])
self.in_response_to = kwargs.pop('in_response_to', [])
@ -156,11 +149,7 @@ class Statement(StatementMixin):
:returns: A dictionary representation of the statement object.
:rtype: dict
"""
data = {}
data['text'] = self.text
data['in_response_to'] = []
data['extra_data'] = self.extra_data
data = {'text': self.text, 'in_response_to': [], 'extra_data': self.extra_data}
for response in self.in_response_to:
data['in_response_to'].append(response.serialize())
@ -219,11 +208,6 @@ class Response(object):
return self.text == other
def serialize(self):
data = {}
data['text'] = self.text
data['created_at'] = self.created_at.isoformat()
data['occurrence'] = self.occurrence
data = {'text': self.text, 'created_at': self.created_at.isoformat(), 'occurrence': self.occurrence}
return data

@ -5,7 +5,6 @@ View the corpus on GitHub at https://github.com/gunthercox/chatterbot-corpus
from chatterbot_corpus import Corpus
__all__ = (
'Corpus',
)

@ -1,11 +1,11 @@
from sqlalchemy import Table, Column, Integer, DateTime, ForeignKey, PickleType
from sqlalchemy.ext.declarative import declared_attr, declarative_base
from sqlalchemy.orm import relationship
from sqlalchemy.sql import func
from sqlalchemy.ext.declarative import declared_attr, declarative_base
from ...constants import TAG_NAME_MAX_LENGTH, STATEMENT_TEXT_MAX_LENGTH
from .types import UnicodeString
from ...conversation import StatementMixin
from chatter.chatterbot.constants import TAG_NAME_MAX_LENGTH, STATEMENT_TEXT_MAX_LENGTH
from chatter.chatterbot.conversation import StatementMixin
from chatter.chatterbot.ext.sqlalchemy_app.types import UnicodeString
class ModelBase(object):
@ -29,7 +29,6 @@ class ModelBase(object):
Base = declarative_base(cls=ModelBase)
tag_association_table = Table(
'tag_association',
Base.metadata,
@ -73,8 +72,8 @@ class Statement(Base, StatementMixin):
return [tag.name for tag in self.tags]
def get_statement(self):
from ...conversation import Statement as StatementObject
from ...conversation import Response as ResponseObject
from chatter.chatterbot.conversation import Statement as StatementObject
from chatter.chatterbot.conversation import Response as ResponseObject
statement = StatementObject(
self.text,

@ -13,9 +13,4 @@ class UnicodeString(TypeDecorator):
Coerce Python bytestrings to unicode before
saving them to the database.
"""
import sys
if sys.version_info[0] < 3:
if isinstance(value, str):
value = value.decode('utf-8')
return value

@ -1,12 +1,11 @@
from .input_adapter import InputAdapter
from .microsoft import Microsoft
from .gitter import Gitter
from .hipchat import HipChat
from .mailgun import Mailgun
from .microsoft import Microsoft
from .terminal import TerminalAdapter
from .variable_input_type_adapter import VariableInputTypeAdapter
__all__ = (
'InputAdapter',
'Microsoft',

@ -1,7 +1,9 @@
from __future__ import unicode_literals
from time import sleep
from . import InputAdapter
from ..conversation import Statement
from chatter.chatterbot.conversation import Statement
from chatter.chatterbot.input import InputAdapter
class Gitter(InputAdapter):

@ -1,7 +1,9 @@
from __future__ import unicode_literals
from time import sleep
from . import InputAdapter
from ..conversation import Statement
from chatter.chatterbot.conversation import Statement
from chatter.chatterbot.input import InputAdapter
class HipChat(InputAdapter):

@ -1,5 +1,6 @@
from __future__ import unicode_literals
from ..adapters import Adapter
from chatter.chatterbot.adapters import Adapter
class InputAdapter(Adapter):

@ -1,7 +1,9 @@
from __future__ import unicode_literals
import datetime
from . import InputAdapter
from ..conversation import Statement
from chatter.chatterbot.conversation import Statement
from chatter.chatterbot.input import InputAdapter
class Mailgun(InputAdapter):

@ -1,7 +1,9 @@
from __future__ import unicode_literals
from time import sleep
from . import InputAdapter
from ..conversation import Statement
from chatter.chatterbot.conversation import Statement
from chatter.chatterbot.input import InputAdapter
class Microsoft(InputAdapter):

@ -1,7 +1,8 @@
from __future__ import unicode_literals
from . import InputAdapter
from ..conversation import Statement
from ..utils import input_function
from chatter.chatterbot.conversation import Statement
from chatter.chatterbot.input import InputAdapter
from chatter.chatterbot.utils import input_function
class TerminalAdapter(InputAdapter):

@ -1,21 +1,17 @@
from __future__ import unicode_literals
from . import InputAdapter
from ..conversation import Statement
from chatter.chatterbot.conversation import Statement
from chatter.chatterbot.input import InputAdapter
class VariableInputTypeAdapter(InputAdapter):
class VariableInputTypeAdapter(InputAdapter):
JSON = 'json'
TEXT = 'text'
OBJECT = 'object'
VALID_FORMATS = (JSON, TEXT, OBJECT,)
def detect_type(self, statement):
import sys
if sys.version_info[0] < 3:
string_types = basestring # NOQA
else:
string_types = str
if hasattr(statement, 'text'):

@ -7,7 +7,6 @@ from .no_knowledge_adapter import NoKnowledgeAdapter
from .specific_response import SpecificResponseAdapter
from .time_adapter import TimeLogicAdapter
__all__ = (
'LogicAdapter',
'BestMatch',

@ -1,5 +1,6 @@
from __future__ import unicode_literals
from .logic_adapter import LogicAdapter
from chatter.chatterbot.logic import LogicAdapter
class BestMatch(LogicAdapter):

@ -1,6 +1,7 @@
from __future__ import unicode_literals
from ..adapters import Adapter
from ..utils import import_module
from chatter.chatterbot.adapters import Adapter
from chatter.chatterbot.utils import import_module
class LogicAdapter(Adapter):
@ -17,8 +18,8 @@ class LogicAdapter(Adapter):
def __init__(self, **kwargs):
super(LogicAdapter, self).__init__(**kwargs)
from ..comparisons import levenshtein_distance
from ..response_selection import get_first_response
from chatter.chatterbot.comparisons import levenshtein_distance
from chatter.chatterbot.response_selection import get_first_response
# Import string module parameters
if 'statement_comparison_function' in kwargs:

@ -1,6 +1,7 @@
from __future__ import unicode_literals
from ..conversation import Statement
from .best_match import BestMatch
from chatter.chatterbot.conversation import Statement
from chatter.chatterbot.logic import BestMatch
class LowConfidenceAdapter(BestMatch):

@ -1,6 +1,7 @@
from __future__ import unicode_literals
from . import LogicAdapter
from ..conversation import Statement
from chatter.chatterbot.conversation import Statement
from chatter.chatterbot.logic import LogicAdapter
class MathematicalEvaluation(LogicAdapter):

@ -1,7 +1,9 @@
from __future__ import unicode_literals
from collections import Counter
from .. import utils
from .logic_adapter import LogicAdapter
from chatter.chatterbot import utils
from chatter.chatterbot.logic import LogicAdapter
class MultiLogicAdapter(LogicAdapter):
@ -13,7 +15,7 @@ class MultiLogicAdapter(LogicAdapter):
"""
def __init__(self, **kwargs):
super().__init__(**kwargs)
super(MultiLogicAdapter, self).__init__(**kwargs)
# Logic adapters added by the chat bot
self.adapters = []

@ -1,5 +1,6 @@
from __future__ import unicode_literals
from .logic_adapter import LogicAdapter
from chatter.chatterbot.logic import LogicAdapter
class NoKnowledgeAdapter(LogicAdapter):

@ -1,5 +1,6 @@
from __future__ import unicode_literals
from .logic_adapter import LogicAdapter
from chatter.chatterbot.logic import LogicAdapter
class SpecificResponseAdapter(LogicAdapter):
@ -15,7 +16,7 @@ class SpecificResponseAdapter(LogicAdapter):
def __init__(self, **kwargs):
super(SpecificResponseAdapter, self).__init__(**kwargs)
from ..conversation import Statement
from chatter.chatterbot.conversation import Statement
self.input_text = kwargs.get('input_text')

@ -1,6 +1,8 @@
from __future__ import unicode_literals
from datetime import datetime
from .logic_adapter import LogicAdapter
from chatter.chatterbot.logic import LogicAdapter
class TimeLogicAdapter(LogicAdapter):
@ -79,7 +81,7 @@ class TimeLogicAdapter(LogicAdapter):
return features
def process(self, statement):
from ..conversation import Statement
from chatter.chatterbot.conversation import Statement
now = datetime.now()

@ -1,9 +1,9 @@
from .output_adapter import OutputAdapter
from .microsoft import Microsoft
from .terminal import TerminalAdapter
from .mailgun import Mailgun
from .gitter import Gitter
from .hipchat import HipChat
from .mailgun import Mailgun
from .microsoft import Microsoft
from .terminal import TerminalAdapter
__all__ = (
'OutputAdapter',

@ -1,5 +1,6 @@
from __future__ import unicode_literals
from .output_adapter import OutputAdapter
from chatter.chatterbot.output import OutputAdapter
class Gitter(OutputAdapter):

@ -1,6 +1,8 @@
from __future__ import unicode_literals
import json
from .output_adapter import OutputAdapter
from chatter.chatterbot.output import OutputAdapter
class HipChat(OutputAdapter):

@ -1,5 +1,6 @@
from __future__ import unicode_literals
from .output_adapter import OutputAdapter
from chatter.chatterbot.output import OutputAdapter
class Mailgun(OutputAdapter):

@ -1,6 +1,8 @@
from __future__ import unicode_literals
import json
from .output_adapter import OutputAdapter
from chatter.chatterbot.output import OutputAdapter
class Microsoft(OutputAdapter):

@ -1,4 +1,4 @@
from ..adapters import Adapter
from chatter.chatterbot.adapters import Adapter
class OutputAdapter(Adapter):

@ -1,5 +1,6 @@
from __future__ import unicode_literals
from .output_adapter import OutputAdapter
from chatter.chatterbot.output import OutputAdapter
class TerminalAdapter(OutputAdapter):

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
import calendar
import re
from datetime import timedelta, datetime
import calendar
# Variations of dates that the parser can capture
year_variations = ['year', 'years', 'yrs']
@ -611,6 +611,7 @@ def date_from_duration(base_date, number_as_string, unit, duration, base_time=No
if base_time is not None:
base_date = date_from_adverb(base_date, base_time)
num = convert_string_to_number(number_as_string)
args = {}
if unit in day_variations:
args = {'days': num}
elif unit in minute_variations:

@ -27,13 +27,8 @@ def unescape_html(chatbot, statement):
Convert escaped html characters into unescaped html characters.
For example: "&lt;b&gt;" becomes "<b>".
"""
import sys
# Replace HTML escape characters
if sys.version_info[0] < 3:
from HTMLParser import HTMLParser
html = HTMLParser()
else:
import html
statement.text = html.unescape(statement.text)
@ -47,11 +42,6 @@ def convert_to_ascii(chatbot, statement):
For example: "på fédéral" becomes "pa federal".
"""
import unicodedata
import sys
# Normalize unicode characters
if sys.version_info[0] < 3:
statement.text = unicode(statement.text) # NOQA
text = unicodedata.normalize('NFKD', statement.text)
text = text.encode('ascii', 'ignore').decode('utf-8')

@ -1,12 +1,9 @@
from .storage_adapter import StorageAdapter
from .django_storage import DjangoStorageAdapter
from .mongodb import MongoDatabaseAdapter
from .sql_storage import SQLStorageAdapter
__all__ = (
'StorageAdapter',
'DjangoStorageAdapter',
'MongoDatabaseAdapter',
'SQLStorageAdapter',
)

@ -1,9 +1,12 @@
from . import StorageAdapter
from chatter.chatterbot.storage import StorageAdapter
class Query(object):
def __init__(self, query={}):
def __init__(self, query=None):
if query is None:
self.query = {}
else:
self.query = query
def value(self):
@ -116,7 +119,7 @@ class MongoDatabaseAdapter(StorageAdapter):
"""
Return the class for the statement model.
"""
from ..conversation import Statement
from chatter.chatterbot.conversation import Statement
# Create a storage-aware statement
statement = Statement
@ -128,7 +131,7 @@ class MongoDatabaseAdapter(StorageAdapter):
"""
Return the class for the response model.
"""
from ..conversation import Response
from chatter.chatterbot.conversation import Response
# Create a storage-aware response
response = Response

@ -1,8 +1,8 @@
from . import StorageAdapter
from chatter.chatterbot.storage import StorageAdapter
def get_response_table(response):
from ..ext.sqlalchemy_app.models import Response
from chatter.chatterbot.ext.sqlalchemy_app.models import Response
return Response(text=response.text, occurrence=response.occurrence)
@ -86,28 +86,28 @@ class SQLStorageAdapter(StorageAdapter):
"""
Return the statement model.
"""
from ..ext.sqlalchemy_app.models import Statement
from chatter.chatterbot.ext.sqlalchemy_app.models import Statement
return Statement
def get_response_model(self):
"""
Return the response model.
"""
from ..ext.sqlalchemy_app.models import Response
from chatter.chatterbot.ext.sqlalchemy_app.models import Response
return Response
def get_conversation_model(self):
"""
Return the conversation model.
"""
from ..ext.sqlalchemy_app.models import Conversation
from chatter.chatterbot.ext.sqlalchemy_app.models import Conversation
return Conversation
def get_tag_model(self):
"""
Return the conversation model.
"""
from ..ext.sqlalchemy_app.models import Tag
from chatter.chatterbot.ext.sqlalchemy_app.models import Tag
return Tag
def count(self):
@ -183,7 +183,7 @@ class SQLStorageAdapter(StorageAdapter):
if isinstance(_filter, list):
if len(_filter) == 0:
_query = _response_query.filter(
Statement.in_response_to == None # NOQA Here must use == instead of is
Statement.in_response_to is None # NOQA Here must use == instead of is
)
else:
for f in _filter:
@ -193,7 +193,7 @@ class SQLStorageAdapter(StorageAdapter):
if fp == 'in_response_to__contains':
_query = _response_query.join(Response).filter(Response.text == _filter)
else:
_query = _response_query.filter(Statement.in_response_to == None) # NOQA
_query = _response_query.filter(Statement.in_response_to is None) # NOQA
else:
if _query:
_query = _query.filter(Response.statement_text.like('%' + _filter + '%'))
@ -379,14 +379,14 @@ class SQLStorageAdapter(StorageAdapter):
"""
Drop the database attached to a given adapter.
"""
from ..ext.sqlalchemy_app.models import Base
from chatter.chatterbot.ext.sqlalchemy_app.models import Base
Base.metadata.drop_all(self.engine)
def create(self):
"""
Populate the database with the tables.
"""
from ..ext.sqlalchemy_app.models import Base
from chatter.chatterbot.ext.sqlalchemy_app.models import Base
Base.metadata.create_all(self.engine)
def _session_finish(self, session, statement_text=None):

@ -157,7 +157,10 @@ class StorageAdapter(object):
class EmptyDatabaseException(Exception):
def __init__(self, value='The database currently contains no entries. At least one entry is expected. You may need to train your chat bot to populate your database.'):
def __init__(self,
value='The database currently contains no entries. '
'At least one entry is expected. '
'You may need to train your chat bot to populate your database.'):
self.value = value
def __str__(self):

@ -1,8 +1,9 @@
import logging
import os
import sys
from .conversation import Statement, Response
from . import utils
from chatter.chatterbot import utils
from chatter.chatterbot.conversation import Statement, Response
class Trainer(object):
@ -84,7 +85,7 @@ class Trainer(object):
import json
export = {'conversations': self._generate_export_data()}
with open(file_path, 'w+') as jsonfile:
json.dump(export, jsonfile, ensure_ascii=False)
json.dump(export, jsonfile, ensure_ascii=True)
class ListTrainer(Trainer):
@ -126,7 +127,7 @@ class ChatterBotCorpusTrainer(Trainer):
def __init__(self, storage, **kwargs):
super(ChatterBotCorpusTrainer, self).__init__(storage, **kwargs)
from .corpus import Corpus
from chatter.chatterbot.corpus import Corpus
self.corpus = Corpus()
@ -224,7 +225,7 @@ class TwitterTrainer(Trainer):
for word in tweet_words:
# If the word contains only letters with a length from 4 to 9
if word.isalpha() and len(word) > 3 and len(word) <= 9:
if word.isalpha() and 3 < len(word) <= 9:
words.add(word)
return words
@ -392,7 +393,6 @@ class UbuntuCorpusTrainer(Trainer):
file_kwargs = {}
if sys.version_info[0] > 2:
# Specify the encoding in Python versions 3 and up
file_kwargs['encoding'] = 'utf-8'
# WARNING: This might fail to read a unicode corpus file in Python 2.x

@ -46,7 +46,7 @@ def validate_adapter_class(validate_class, adapter_class):
:raises: Adapter.InvalidAdapterTypeException
"""
from .adapters import Adapter
from chatter.chatterbot.adapters import Adapter
# If a dictionary was passed in, check if it has an import_path attribute
if isinstance(validate_class, dict):
@ -75,16 +75,7 @@ def input_function():
Normalizes reading input between python 2 and 3.
The function 'raw_input' becomes 'input' in Python 3.
"""
import sys
if sys.version_info[0] < 3:
user_input = str(raw_input()) # NOQA
# Avoid problems using format strings with unicode characters
if user_input:
user_input = user_input.decode('utf-8')
else:
user_input = input() # NOQA
return user_input
@ -137,7 +128,7 @@ def remove_stopwords(tokens, language):
Stop words are words like "is, the, a, ..."
Be sure to download the required NLTK corpus before calling this function:
- from chatterbot.utils import nltk_download_corpus
- from chatter.chatterbot.utils import nltk_download_corpus
- nltk_download_corpus('corpora/stopwords')
"""
from nltk.corpus import stopwords

@ -1,10 +1,30 @@
{
"author" : ["Bobloy"],
"bot_version" : [3,0,0],
"author": [
"Bobloy"
],
"bot_version": [
3,
0,
0
],
"description": "Create an offline chatbot that talks like your average member using Machine Learning",
"hidden": false,
"install_msg" : "Thank you for installing Chatter!",
"requirements" : ["sqlalchemy<1.3,>=1.2", "python-twitter<4.0,>=3.0", "python-dateutil<2.7,>=2.6", "pymongo<4.0,>=3.3", "nltk<4.0,>=3.2", "mathparse<0.2,>=0.1", "chatterbot-corpus<1.2,>=1.1"],
"install_msg": "Thank you for installing Chatter! Get started ith `[p]load chatter` and `[p]help Chatter`",
"requirements": [
"sqlalchemy<1.3,>=1.2",
"python-twitter<4.0,>=3.0",
"python-dateutil<2.7,>=2.6",
"pymongo<4.0,>=3.3",
"nltk<4.0,>=3.2",
"mathparse<0.2,>=0.1",
"chatterbot-corpus<1.2,>=1.1"
],
"short": "Local Chatbot run on machine learning",
"tags" : ["chat", "chatbot", "cleverbot", "clever","bobloy"]
"tags": [
"chat",
"chatbot",
"cleverbot",
"clever",
"bobloy"
]
}

@ -1,3 +0,0 @@
default_app_config = (
'chatter.source.ext.django_chatterbot.apps.DjangoChatterBotConfig'
)

@ -1,261 +0,0 @@
from ...conversation import StatementMixin
from ... import constants
from django.db import models
from django.apps import apps
from django.utils import timezone
from django.conf import settings
DJANGO_APP_NAME = constants.DEFAULT_DJANGO_APP_NAME
STATEMENT_MODEL = 'Statement'
RESPONSE_MODEL = 'Response'
if hasattr(settings, 'CHATTERBOT'):
"""
Allow related models to be overridden in the project settings.
Default to the original settings if one is not defined.
"""
DJANGO_APP_NAME = settings.CHATTERBOT.get(
'django_app_name',
DJANGO_APP_NAME
)
STATEMENT_MODEL = settings.CHATTERBOT.get(
'statement_model',
STATEMENT_MODEL
)
RESPONSE_MODEL = settings.CHATTERBOT.get(
'response_model',
RESPONSE_MODEL
)
class AbstractBaseStatement(models.Model, StatementMixin):
"""
The abstract base statement allows other models to
be created using the attributes that exist on the
default models.
"""
text = models.CharField(
unique=True,
blank=False,
null=False,
max_length=constants.STATEMENT_TEXT_MAX_LENGTH
)
extra_data = models.CharField(
max_length=500,
blank=True
)
# This is the confidence with which the chat bot believes
# this is an accurate response. This value is set when the
# statement is returned by the chat bot.
confidence = 0
class Meta:
abstract = True
def __str__(self):
if len(self.text.strip()) > 60:
return '{}...'.format(self.text[:57])
elif len(self.text.strip()) > 0:
return self.text
return '<empty>'
def __init__(self, *args, **kwargs):
super(AbstractBaseStatement, self).__init__(*args, **kwargs)
# Responses to be saved if the statement is updated with the storage adapter
self.response_statement_cache = []
@property
def in_response_to(self):
"""
Return the response objects that are for this statement.
"""
ResponseModel = apps.get_model(DJANGO_APP_NAME, RESPONSE_MODEL)
return ResponseModel.objects.filter(statement=self)
def add_extra_data(self, key, value):
"""
Add extra data to the extra_data field.
"""
import json
if not self.extra_data:
self.extra_data = '{}'
extra_data = json.loads(self.extra_data)
extra_data[key] = value
self.extra_data = json.dumps(extra_data)
def add_tags(self, tags):
"""
Add a list of strings to the statement as tags.
(Overrides the method from StatementMixin)
"""
for tag in tags:
self.tags.create(
name=tag
)
def add_response(self, statement):
"""
Add a response to this statement.
"""
self.response_statement_cache.append(statement)
def remove_response(self, response_text):
"""
Removes a response from the statement's response list based
on the value of the response text.
:param response_text: The text of the response to be removed.
:type response_text: str
"""
is_deleted = False
response = self.in_response.filter(response__text=response_text)
if response.exists():
is_deleted = True
return is_deleted
def get_response_count(self, statement):
"""
Find the number of times that the statement has been used
as a response to the current statement.
:param statement: The statement object to get the count for.
:type statement: chatterbot.conversation.Statement
:returns: Return the number of times the statement has been used as a response.
:rtype: int
"""
return self.in_response.filter(response__text=statement.text).count()
def serialize(self):
"""
:returns: A dictionary representation of the statement object.
:rtype: dict
"""
import json
data = {}
if not self.extra_data:
self.extra_data = '{}'
data['text'] = self.text
data['in_response_to'] = []
data['extra_data'] = json.loads(self.extra_data)
for response in self.in_response.all():
data['in_response_to'].append(response.serialize())
return data
class AbstractBaseResponse(models.Model):
"""
The abstract base response allows other models to
be created using the attributes that exist on the
default models.
"""
statement = models.ForeignKey(
STATEMENT_MODEL,
related_name='in_response',
on_delete=models.CASCADE
)
response = models.ForeignKey(
STATEMENT_MODEL,
related_name='responses',
on_delete=models.CASCADE
)
created_at = models.DateTimeField(
default=timezone.now,
help_text='The date and time that this response was created at.'
)
class Meta:
abstract = True
@property
def occurrence(self):
"""
Return a count of the number of times this response has occurred.
"""
ResponseModel = apps.get_model(DJANGO_APP_NAME, RESPONSE_MODEL)
return ResponseModel.objects.filter(
statement__text=self.statement.text,
response__text=self.response.text
).count()
def __str__(self):
statement = self.statement.text
response = self.response.text
return '{} => {}'.format(
statement if len(statement) <= 20 else statement[:17] + '...',
response if len(response) <= 40 else response[:37] + '...'
)
def serialize(self):
"""
:returns: A dictionary representation of the statement object.
:rtype: dict
"""
data = {}
data['text'] = self.response.text
data['created_at'] = self.created_at.isoformat()
data['occurrence'] = self.occurrence
return data
class AbstractBaseConversation(models.Model):
"""
The abstract base conversation allows other models to
be created using the attributes that exist on the
default models.
"""
responses = models.ManyToManyField(
RESPONSE_MODEL,
related_name='conversations',
help_text='The responses in this conversation.'
)
class Meta:
abstract = True
def __str__(self):
return str(self.id)
class AbstractBaseTag(models.Model):
"""
The abstract base tag allows other models to
be created using the attributes that exist on the
default models.
"""
name = models.SlugField(
max_length=constants.TAG_NAME_MAX_LENGTH
)
statements = models.ManyToManyField(
STATEMENT_MODEL,
related_name='tags'
)
class Meta:
abstract = True
def __str__(self):
return self.name

@ -1,31 +0,0 @@
from django.contrib import admin
from .models import (
Statement, Response, Conversation, Tag
)
class StatementAdmin(admin.ModelAdmin):
list_display = ('text', )
list_filter = ('text', )
search_fields = ('text', )
class ResponseAdmin(admin.ModelAdmin):
list_display = ('statement', 'response', 'occurrence', )
search_fields = ['statement__text', 'response__text']
class ConversationAdmin(admin.ModelAdmin):
list_display = ('id', )
class TagAdmin(admin.ModelAdmin):
list_display = ('name', )
list_filter = ('name', )
search_fields = ('name', )
admin.site.register(Statement, StatementAdmin)
admin.site.register(Response, ResponseAdmin)
admin.site.register(Conversation, ConversationAdmin)
admin.site.register(Tag, TagAdmin)

@ -1,8 +0,0 @@
from django.apps import AppConfig
class DjangoChatterBotConfig(AppConfig):
name = 'chatter.source.ext.django_chatterbot'
label = 'django_chatterbot'
verbose_name = 'Django ChatterBot'

@ -1,42 +0,0 @@
"""
These factories are used to generate fake data for testing.
"""
import factory
from . import models
from ... import constants
from factory.django import DjangoModelFactory
class StatementFactory(DjangoModelFactory):
text = factory.Faker(
'text',
max_nb_chars=constants.STATEMENT_TEXT_MAX_LENGTH
)
class Meta:
model = models.Statement
class ResponseFactory(DjangoModelFactory):
statement = factory.SubFactory(StatementFactory)
response = factory.SubFactory(StatementFactory)
class Meta:
model = models.Response
class ConversationFactory(DjangoModelFactory):
class Meta:
model = models.Conversation
class TagFactory(DjangoModelFactory):
name = factory.Faker('word')
class Meta:
model = models.Tag

@ -1,29 +0,0 @@
from django.core.management.base import BaseCommand
class Command(BaseCommand):
"""
A Django management command for calling a
chat bot's training method.
"""
help = 'Trains the database used by the chat bot'
can_import_settings = True
def handle(self, *args, **options):
from ..... import ChatBot
from ... import settings
chatterbot = ChatBot(**settings.CHATTERBOT)
chatterbot.train(chatterbot.training_data)
# Django 1.8 does not define SUCCESS
if hasattr(self.style, 'SUCCESS'):
style = self.style.SUCCESS
else:
style = self.style.NOTICE
self.stdout.write(style('Starting training...'))
training_class = chatterbot.trainer.__class__.__name__
self.stdout.write(style('ChatterBot trained using "%s"' % training_class))

@ -1,39 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = []
operations = [
migrations.CreateModel(
name='Response',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('occurrence', models.PositiveIntegerField(default=0)),
],
),
migrations.CreateModel(
name='Statement',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('text', models.CharField(max_length=255, unique=True)),
],
),
migrations.AddField(
model_name='response',
name='response',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='+', to='django_chatterbot.Statement'),
),
migrations.AddField(
model_name='response',
name='statement',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='in_response_to', to='django_chatterbot.Statement'),
),
]

@ -1,21 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.2 on 2016-10-30 12:13
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('django_chatterbot', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='statement',
name='extra_data',
field=models.CharField(default='{}', max_length=500),
preserve_default=False,
),
]

@ -1,20 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9 on 2016-12-12 00:06
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('django_chatterbot', '0002_statement_extra_data'),
]
operations = [
migrations.AlterField(
model_name='response',
name='occurrence',
field=models.PositiveIntegerField(default=1),
),
]

@ -1,26 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.3 on 2016-12-04 23:52
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('django_chatterbot', '0003_change_occurrence_default'),
]
operations = [
migrations.AlterField(
model_name='response',
name='statement',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='in_response', to='django_chatterbot.Statement'),
),
migrations.AlterField(
model_name='response',
name='response',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='responses', to='django_chatterbot.Statement'),
),
]

@ -1,24 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2016-12-29 19:20
from __future__ import unicode_literals
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('django_chatterbot', '0004_rename_in_response_to'),
]
operations = [
migrations.AddField(
model_name='statement',
name='created_at',
field=models.DateTimeField(
default=django.utils.timezone.now,
help_text='The date and time that this statement was created at.'
),
),
]

@ -1,33 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9 on 2017-01-17 07:02
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('django_chatterbot', '0005_statement_created_at'),
]
operations = [
migrations.CreateModel(
name='Conversation',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
],
),
migrations.AlterField(
model_name='statement',
name='created_at',
field=models.DateTimeField(default=django.utils.timezone.now, help_text='The date and time that this statement was created at.'),
),
migrations.AddField(
model_name='conversation',
name='statements',
field=models.ManyToManyField(help_text='The statements in this conversation.', related_name='conversation', to='django_chatterbot.Statement'),
),
]

@ -1,24 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2017-07-18 00:16
from __future__ import unicode_literals
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('django_chatterbot', '0006_create_conversation'),
]
operations = [
migrations.AddField(
model_name='response',
name='created_at',
field=models.DateTimeField(
default=django.utils.timezone.now,
help_text='The date and time that this response was created at.'
),
),
]

@ -1,32 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2017-07-18 11:25
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('django_chatterbot', '0007_response_created_at'),
]
operations = [
migrations.RemoveField(
model_name='conversation',
name='statements',
),
migrations.RemoveField(
model_name='response',
name='occurrence',
),
migrations.RemoveField(
model_name='statement',
name='created_at',
),
migrations.AddField(
model_name='conversation',
name='responses',
field=models.ManyToManyField(help_text='The responses in this conversation.', related_name='conversations', to='django_chatterbot.Response'),
),
]

@ -1,35 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11a1 on 2017-07-07 00:12
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('django_chatterbot', '0008_update_conversations'),
]
operations = [
migrations.CreateModel(
name='Tag',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.SlugField()),
],
options={
'abstract': False,
},
),
migrations.AlterField(
model_name='statement',
name='text',
field=models.CharField(max_length=255, unique=True),
),
migrations.AddField(
model_name='tag',
name='statements',
field=models.ManyToManyField(related_name='tags', to='django_chatterbot.Statement'),
),
]

@ -1,20 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2017-08-16 00:56
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('django_chatterbot', '0009_tags'),
]
operations = [
migrations.AlterField(
model_name='statement',
name='text',
field=models.CharField(max_length=400, unique=True),
),
]

@ -1,20 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2017-08-20 13:55
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('django_chatterbot', '0010_statement_text'),
]
operations = [
migrations.AlterField(
model_name='statement',
name='extra_data',
field=models.CharField(blank=True, max_length=500),
),
]

@ -1,34 +0,0 @@
from .abstract_models import (
AbstractBaseConversation, AbstractBaseResponse,
AbstractBaseStatement, AbstractBaseTag
)
class Statement(AbstractBaseStatement):
"""
A statement represents a single spoken entity, sentence or
phrase that someone can say.
"""
pass
class Response(AbstractBaseResponse):
"""
A connection between a statement and anther statement
that response to it.
"""
pass
class Conversation(AbstractBaseConversation):
"""
A sequence of statements representing a conversation.
"""
pass
class Tag(AbstractBaseTag):
"""
A label that categorizes a statement.
"""
pass

@ -1,19 +0,0 @@
"""
Default ChatterBot settings for Django.
"""
from django.conf import settings
from ... import constants
CHATTERBOT_SETTINGS = getattr(settings, 'CHATTERBOT', {})
CHATTERBOT_DEFAULTS = {
'name': 'ChatterBot',
'storage_adapter': 'chatter.source.storage.DjangoStorageAdapter',
'input_adapter': 'chatter.source.input.VariableInputTypeAdapter',
'output_adapter': 'chatter.source.output.OutputAdapter',
'django_app_name': constants.DEFAULT_DJANGO_APP_NAME
}
CHATTERBOT = CHATTERBOT_DEFAULTS.copy()
CHATTERBOT.update(CHATTERBOT_SETTINGS)

@ -1,11 +0,0 @@
from django.conf.urls import url
from .views import ChatterBotView
urlpatterns = [
url(
r'^$',
ChatterBotView.as_view(),
name='chatterbot',
),
]

@ -1,118 +0,0 @@
import json
from django.views.generic import View
from django.http import JsonResponse
from ... import ChatBot
from . import settings
class ChatterBotViewMixin(object):
"""
Subclass this mixin for access to the 'chatterbot' attribute.
"""
chatterbot = ChatBot(**settings.CHATTERBOT)
def validate(self, data):
"""
Validate the data recieved from the client.
* The data should contain a text attribute.
"""
from django.core.exceptions import ValidationError
if 'text' not in data:
raise ValidationError('The attribute "text" is required.')
def get_conversation(self, request):
"""
Return the conversation for the session if one exists.
Create a new conversation if one does not exist.
"""
from .models import Conversation, Response
class Obj(object):
def __init__(self):
self.id = None
self.statements = []
conversation = Obj()
conversation.id = request.session.get('conversation_id', 0)
existing_conversation = False
try:
Conversation.objects.get(id=conversation.id)
existing_conversation = True
except Conversation.DoesNotExist:
conversation_id = self.chatterbot.storage.create_conversation()
request.session['conversation_id'] = conversation_id
conversation.id = conversation_id
if existing_conversation:
responses = Response.objects.filter(
conversations__id=conversation.id
)
for response in responses:
conversation.statements.append(response.statement.serialize())
conversation.statements.append(response.response.serialize())
return conversation
class ChatterBotView(ChatterBotViewMixin, View):
"""
Provide an API endpoint to interact with ChatterBot.
"""
def post(self, request, *args, **kwargs):
"""
Return a response to the statement in the posted data.
"""
input_data = json.loads(request.read().decode('utf-8'))
self.validate(input_data)
conversation = self.get_conversation(request)
response = self.chatterbot.get_response(input_data, conversation.id)
response_data = response.serialize()
return JsonResponse(response_data, status=200)
def get(self, request, *args, **kwargs):
"""
Return data corresponding to the current conversation.
"""
conversation = self.get_conversation(request)
data = {
'detail': 'You should make a POST request to this endpoint.',
'name': self.chatterbot.name,
'conversation': conversation.statements
}
# Return a method not allowed response
return JsonResponse(data, status=405)
def patch(self, request, *args, **kwargs):
"""
The patch method is not allowed for this endpoint.
"""
data = {
'detail': 'You should make a POST request to this endpoint.'
}
# Return a method not allowed response
return JsonResponse(data, status=405)
def delete(self, request, *args, **kwargs):
"""
The delete method is not allowed for this endpoint.
"""
data = {
'detail': 'You should make a POST request to this endpoint.'
}
# Return a method not allowed response
return JsonResponse(data, status=405)

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save