Compare commits
270 Commits
sayurl-dev
...
master
Author | SHA1 | Date |
---|---|---|
WTMike24 | 0aaffbf5f0 | 3 years ago |
bobloy | 771d1457e5 | 3 years ago |
bobloy | d191abf6bd | 3 years ago |
bobloy | aba9840bcb | 3 years ago |
bobloy | 0064e6e9b5 | 3 years ago |
bobloy | 955624ad1a | 3 years ago |
bobloy | a6e0646b85 | 4 years ago |
bobloy | 269266ce04 | 4 years ago |
bobloy | db3ce30122 | 4 years ago |
bobloy | 1e87eacf83 | 4 years ago |
bobloy | 61fa006e33 | 4 years ago |
bobloy | 4183607372 | 4 years ago |
bobloy | 2421c4e9bf | 4 years ago |
bobloy | b2e843e781 | 4 years ago |
aleclol | a7ce815e14 | 4 years ago |
bobloy | 1e535c2f3e | 4 years ago |
bobloy | fd80819618 | 4 years ago |
bobloy | a5ff888f4c | 4 years ago |
bobloy | 3eb499bf0e | 4 years ago |
bobloy | 698dafade4 | 4 years ago |
Brad Duncan | 15ecf72c64 | 4 years ago |
Brad Duncan | 1d514f80c6 | 4 years ago |
bobloy | b752bfd153 | 4 years ago |
bobloy | 47269ba8f4 | 4 years ago |
bobloy | e0a361b952 | 4 years ago |
bobloy | 2a18760a83 | 4 years ago |
bobloy | b26afdf2db | 4 years ago |
bobloy | 86cc1fa35a | 4 years ago |
aleclol | b331238c1c | 4 years ago |
bobloy | 6f0c88b1ac | 4 years ago |
bobloy | c165313031 | 4 years ago |
bobloy | 9c63c12656 | 4 years ago |
bobloy | a55ae8a511 | 4 years ago |
Alexander Soloviev | 1ffac638ed | 4 years ago |
Alexander Soloviev | 5b75002c88 | 4 years ago |
Alexander Soloviev | 7c08a5de0c | 4 years ago |
bobloy | bc12aa866e | 4 years ago |
bobloy | dbafd6ffd7 | 4 years ago |
aleclol | 52ca2f6a45 | 4 years ago |
bobloy | 2937b6ac92 | 4 years ago |
bobloy | dbf84c8b81 | 4 years ago |
PhenoM4n4n | 2f21de6a97 | 4 years ago |
bobloy | ed6cc433c8 | 4 years ago |
bobloy | e1a30359d8 | 4 years ago |
bobloy | 506a79c6d6 | 4 years ago |
bobloy | 1ddcd98078 | 4 years ago |
bobloy | 7bd004c78e | 4 years ago |
bobloy | 184833421a | 4 years ago |
Kreusada | f04ff6886b | 4 years ago |
bobloy | 28edcc1fdd | 4 years ago |
bobloy | 10ed1f9b9f | 4 years ago |
bobloy | 93b403b35f | 4 years ago |
bobloy | 5f30bc1234 | 4 years ago |
bobloy | 9f22dfb790 | 4 years ago |
bobloy | ea126db0c5 | 4 years ago |
bobloy | e1297a4dca | 4 years ago |
bobloy | 87187abbb3 | 4 years ago |
bobloy | db24bb4db4 | 4 years ago |
bobloy | 1319d98972 | 4 years ago |
bobloy | 802929d757 | 4 years ago |
bobloy | 59fd96fc5a | 4 years ago |
bobloy | b4f20dd7d2 | 4 years ago |
bobloy | ac9cf1e589 | 4 years ago |
bobloy | 8feb21e34b | 4 years ago |
bobloy | 04ccb435f8 | 4 years ago |
bobloy | eac7aee82c | 4 years ago |
bobloy | 8200cd9af1 | 4 years ago |
bobloy | ff9610ff77 | 4 years ago |
bobloy | 95931d24f3 | 4 years ago |
bobloy | dad14fe972 | 4 years ago |
bobloy | ea88addc42 | 4 years ago |
Sourcery AI | 0475b18437 | 4 years ago |
bobloy | 7811c71edb | 4 years ago |
bobloy | 8acbc5d964 | 4 years ago |
bobloy | 59090b9eaa | 4 years ago |
bobloy | 11eb4a9dbf | 4 years ago |
bobloy | d32de1586f | 4 years ago |
bobloy | 578ea4a555 | 4 years ago |
bobloy | 920f8817d7 | 4 years ago |
Antoine Rybacki | 42bdc64028 | 4 years ago |
Antoine Rybacki | f7dad0aa3f | 4 years ago |
bobloy | cc199c395d | 4 years ago |
Antoine Rybacki | 5a26b48fda | 4 years ago |
bobloy | bf9115e13c | 4 years ago |
bobloy | a5eda8ca2a | 4 years ago |
bobloy | 221ca4074b | 4 years ago |
bobloy | 8b1ac18609 | 4 years ago |
bobloy | ee8f6bbf57 | 4 years ago |
bobloy | af3de08da2 | 4 years ago |
Obi-Wan3 | 92957bcb1f | 4 years ago |
Obi-Wan3 | 7ad6b15641 | 4 years ago |
Obi-Wan3 | 6363f5eadc | 4 years ago |
bobloy | 0e034d83ef | 4 years ago |
bobloy | 337def2fa3 | 4 years ago |
bobloy | 14f8b825d8 | 4 years ago |
bobloy | dbf6ba5a4b | 4 years ago |
bobloy | bf16630573 | 4 years ago |
bobloy | 6233db2272 | 4 years ago |
bobloy | 3f997fa804 | 4 years ago |
bobloy | 837cff7a26 | 4 years ago |
bobloy | 796edb4d35 | 4 years ago |
bobloy | 6c669dd170 | 4 years ago |
bobloy | 9bdaf73944 | 4 years ago |
bobloy | 3b50785c5b | 4 years ago |
bobloy | d14db16746 | 4 years ago |
bobloy | 2c9f3838da | 4 years ago |
bobloy | 9f10ea262d | 4 years ago |
bobloy | 320f729cc9 | 4 years ago |
bobloy | 9c9b46dc76 | 4 years ago |
bobloy | d5bc5993ea | 4 years ago |
bobloy | ce41c80c3b | 4 years ago |
bobloy | c603e4b326 | 4 years ago |
bobloy | d13fd39cfc | 4 years ago |
bobloy | 8dc81808e6 | 4 years ago |
bobloy | b2c8268c9b | 4 years ago |
bobloy | f3dab0f0c6 | 4 years ago |
bobloy | bae50f6a7a | 4 years ago |
bobloy | 5892bed5b9 | 4 years ago |
bobloy | 36826a44e7 | 4 years ago |
bobloy | 0a0e8650e4 | 4 years ago |
bobloy | d36493f5a8 | 4 years ago |
bobloy | fc8e465c33 | 4 years ago |
bobloy | 087b10deb2 | 4 years ago |
bobloy | bf3c292fee | 4 years ago |
bobloy | c7820ec40c | 4 years ago |
bobloy | b566b58e1a | 4 years ago |
ASSASSIN0831 | 69e2e5acb3 | 4 years ago |
ASSASSIN0831 | bce07f069f | 4 years ago |
ASSASSIN0831 | 9ac89aa369 | 4 years ago |
bobloy | 624e8863b1 | 4 years ago |
bobloy | 51dc2e62d4 | 4 years ago |
bobloy | d85f166062 | 4 years ago |
bobloy | 5ecb8dc826 | 4 years ago |
bobloy | 9411fff5e8 | 4 years ago |
bobloy | 0ff56d933b | 4 years ago |
bobloy | 8ab6c50625 | 4 years ago |
bobloy | 19ee6e6f24 | 4 years ago |
bobloy | b2ebddc825 | 4 years ago |
bobloy | 907fb76574 | 4 years ago |
bobloy | 3c3dd2d6cd | 4 years ago |
bobloy | a946b1b83b | 4 years ago |
bobloy | ca8c762e69 | 4 years ago |
bobloy | 721316a14e | 4 years ago |
bobloy | 40b01cff26 | 4 years ago |
bobloy | 2ea077bb0c | 4 years ago |
bobloy | 99ab9fc1b4 | 4 years ago |
bobloy | a2948322f9 | 4 years ago |
bobloy | 419863b07a | 4 years ago |
bobloy | 8015e4a46d | 4 years ago |
bobloy | 1f1d116a56 | 4 years ago |
bobloy | db538f7530 | 4 years ago |
bobloy | 3bb6af9b9b | 4 years ago |
bobloy | fc0870af68 | 4 years ago |
bobloy | 5fffaf4893 | 4 years ago |
bobloy | 477364f9bf | 4 years ago |
bobloy | 6e2c62d897 | 4 years ago |
bobloy | 675e9b82c8 | 4 years ago |
bobloy | 4634210960 | 4 years ago |
bobloy | a6ebe02233 | 4 years ago |
bobloy | 26234e3b18 | 4 years ago |
bobloy | 37c699eeee | 4 years ago |
bobloy | 5f58d1d658 | 4 years ago |
bobloy | 5ddafff59f | 4 years ago |
bobloy | d71e3afb86 | 4 years ago |
bobloy | 5611f7abe7 | 4 years ago |
bobloy | 1e8d1efb57 | 4 years ago |
bobloy | a92c373b49 | 4 years ago |
bobloy | 60806fb19c | 4 years ago |
bobloy | 4c1cd86930 | 4 years ago |
bobloy | 10767da507 | 4 years ago |
bobloy | c63a4923e7 | 4 years ago |
bobloy | b141accbd9 | 4 years ago |
bobloy | 44035b78f7 | 4 years ago |
bobloy | 815cfcb031 | 4 years ago |
bobloy | 8e0105355c | 4 years ago |
bobloy | ffbed8cb9a | 4 years ago |
bobloy | 5752ba6056 | 4 years ago |
bobloy | 479b23f0f3 | 4 years ago |
bobloy | 54be5addb5 | 4 years ago |
bobloy | 9440f34669 | 4 years ago |
bobloy | 7c95bd4c0f | 4 years ago |
bobloy | 3fceea634b | 4 years ago |
bobloy | b210f4a9ff | 4 years ago |
bobloy | da754e3cb2 | 4 years ago |
BogdanWDK | 960b66a5b8 | 4 years ago |
bobloy | 20d8acc800 | 4 years ago |
bobloy | 266b0a485d | 4 years ago |
bobloy | d0445f41c7 | 4 years ago |
bobloy | 7f8d0f13f7 | 4 years ago |
bobloy | c529d792e6 | 4 years ago |
bobloy | 62a70c52c6 | 4 years ago |
bobloy | 19104241d7 | 4 years ago |
bobloy | 8a4893c5f5 | 4 years ago |
bobloy | 9ca5d37f7e | 4 years ago |
bobloy | f3965b73d8 | 4 years ago |
bobloy | e27cfba763 | 4 years ago |
bobloy | 211df56e1b | 4 years ago |
bobloy | 443c84ccab | 4 years ago |
bobloy | c7d320ccaa | 4 years ago |
bobloy | 2ab87866dd | 4 years ago |
bobloy | a691b2b85a | 4 years ago |
bobloy | 8c0a1db06f | 4 years ago |
bobloy | cd89bd87e9 | 4 years ago |
bobloy | 31c2e77be6 | 4 years ago |
bobloy | 3a6d3df374 | 4 years ago |
bobloy | 94aceb32e8 | 4 years ago |
bobloy | 693964183c | 4 years ago |
bobloy | 8a42b87bd6 | 4 years ago |
bobloy | a36a800b45 | 4 years ago |
bobloy | af41d079d3 | 4 years ago |
bobloy | ad66d171d4 | 4 years ago |
bobloy | b9d8be397c | 4 years ago |
bobloy | 70d50c5e97 | 4 years ago |
bobloy | b27b252e6f | 4 years ago |
bobloy | ab1b069ee9 | 4 years ago |
bobloy | af4cd92488 | 4 years ago |
bobloy | 03f0ef17be | 4 years ago |
bobloy | db1d64ae3e | 4 years ago |
bobloy | 029b6a51b1 | 4 years ago |
bobloy | 61049c2343 | 4 years ago |
bobloy | eb0c79ef1d | 4 years ago |
bobloy | a0c645bd28 | 4 years ago |
bobloy | 762b0fd320 | 4 years ago |
bobloy | 224ff93531 | 4 years ago |
bobloy | f263f97cc2 | 4 years ago |
bobloy | 61d1313411 | 4 years ago |
bobloy | cb0a7f1041 | 4 years ago |
bobloy | 39801aada9 | 4 years ago |
bobloy | eaa3e0a2f7 | 4 years ago |
bobloy | 8ffc8cc707 | 4 years ago |
bobloy | 84ed2728e7 | 4 years ago |
bobloy | 596865e49d | 4 years ago |
bobloy | 5940ab1af9 | 4 years ago |
bobloy | 9f17bca226 | 4 years ago |
bobloy | bdcb74587e | 4 years ago |
bobloy | 8531ff5f91 | 4 years ago |
bobloy | bed6cf8bb7 | 4 years ago |
bobloy | d13331d52d | 4 years ago |
bobloy | 9ea57ee99a | 4 years ago |
bobloy | 608f425965 | 4 years ago |
bobloy | b04e82fa1d | 4 years ago |
bobloy | f05a8bf4f6 | 4 years ago |
bobloy | bf81d7c157 | 4 years ago |
bobloy | e0042780a1 | 4 years ago |
bobloy | 98ae481d14 | 4 years ago |
bobloy | 29aa493033 | 4 years ago |
bobloy | 7109471c35 | 4 years ago |
bobloy | a2eaf55515 | 4 years ago |
bobloy | 06af229a62 | 4 years ago |
bobloy | 8a3f45bdc1 | 4 years ago |
bobloy | ec5d713fa0 | 4 years ago |
bobloy | 1723dc381d | 4 years ago |
bobloy | c428fa3131 | 4 years ago |
bobloy | f69e8fdb1a | 4 years ago |
bobloy | 28bf2a73e1 | 4 years ago |
bobloy | a046102549 | 4 years ago |
bobloy | fe1f11b2eb | 4 years ago |
bobloy | 7e1a6e108e | 4 years ago |
bobloy | 339492d6d9 | 4 years ago |
bobloy | 7a9fb922bd | 4 years ago |
bobloy | 18e5cc12ff | 4 years ago |
bobloy | 8ecdf45fa7 | 4 years ago |
bobloy | 88ef475339 | 4 years ago |
bobloy | 55656ea672 | 4 years ago |
jack1142 | eddac5b8b2 | 4 years ago |
bobloy | d377461602 | 4 years ago |
bobloy | 849262969c | 6 years ago |
bobloy | 53d817756a | 6 years ago |
bobloy | 0ee0199b11 | 6 years ago |
bobloy | 0d59a5220f | 6 years ago |
@ -0,0 +1,26 @@
|
|||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Create an issue to report a bug
|
||||||
|
title: ''
|
||||||
|
labels: bug
|
||||||
|
assignees: bobloy
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Describe the bug**
|
||||||
|
<!--A clear and concise description of what the bug is.-->
|
||||||
|
|
||||||
|
**To Reproduce**
|
||||||
|
<!--Steps to reproduce the behavior:-->
|
||||||
|
1. Load cog '...'
|
||||||
|
2. Run command '....'
|
||||||
|
3. See error
|
||||||
|
|
||||||
|
**Expected behavior**
|
||||||
|
<!--A clear and concise description of what you expected to happen.-->
|
||||||
|
|
||||||
|
**Screenshots or Error Messages**
|
||||||
|
<!--If applicable, add screenshots to help explain your problem.-->
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
<!--Add any other context about the problem here.-->
|
@ -0,0 +1,14 @@
|
|||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest an idea for this project
|
||||||
|
title: "[Feature Request]"
|
||||||
|
labels: enhancement
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Is your feature request related to a problem? Please describe.**
|
||||||
|
<!--A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]-->
|
||||||
|
|
||||||
|
**Describe the solution you'd like**
|
||||||
|
<!--A clear and concise description of what you want to happen. Include which cog or cogs this would interact with-->
|
@ -0,0 +1,26 @@
|
|||||||
|
---
|
||||||
|
name: New AudioTrivia List
|
||||||
|
about: Submit a new AudioTrivia list to be added
|
||||||
|
title: "[AudioTrivia Submission]"
|
||||||
|
labels: 'cog: audiotrivia'
|
||||||
|
assignees: bobloy
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**What is this trivia list?**
|
||||||
|
<!--What's in the list? What kind of category is?-->
|
||||||
|
|
||||||
|
**Number of Questions**
|
||||||
|
<!--Rough estimate at the number of question in this list-->
|
||||||
|
|
||||||
|
**Original Content?**
|
||||||
|
<!--Did you come up with this list yourself or did you get it from some else's work?-->
|
||||||
|
<!--If no, be sure to include the source-->
|
||||||
|
- [ ] Yes
|
||||||
|
- [ ] No
|
||||||
|
|
||||||
|
|
||||||
|
**Did I test the list?**
|
||||||
|
<!--Did you already try out the list and find no bugs?-->
|
||||||
|
- [ ] Yes
|
||||||
|
- [ ] No
|
@ -0,0 +1,62 @@
|
|||||||
|
'cog: announcedaily':
|
||||||
|
- announcedaily/*
|
||||||
|
'cog: audiotrivia':
|
||||||
|
- audiotrivia/*
|
||||||
|
'cog: ccrole':
|
||||||
|
- ccrole/*
|
||||||
|
'cog: chatter':
|
||||||
|
- chatter/*
|
||||||
|
'cog: conquest':
|
||||||
|
- conquest/*
|
||||||
|
'cog: dad':
|
||||||
|
- dad/*
|
||||||
|
'cog: exclusiverole':
|
||||||
|
- exclusiverole/*
|
||||||
|
'cog: fifo':
|
||||||
|
- fifo/*
|
||||||
|
'cog: firstmessage':
|
||||||
|
- firstmessage/*
|
||||||
|
'cog: flag':
|
||||||
|
- flag/*
|
||||||
|
'cog: forcemention':
|
||||||
|
- forcemention/*
|
||||||
|
'cog: hangman':
|
||||||
|
- hangman
|
||||||
|
'cog: infochannel':
|
||||||
|
- infochannel/*
|
||||||
|
'cog: isitdown':
|
||||||
|
- isitdown/*
|
||||||
|
'cog: launchlib':
|
||||||
|
- launchlib/*
|
||||||
|
'cog: leaver':
|
||||||
|
- leaver/*
|
||||||
|
'cog: lovecalculator':
|
||||||
|
- lovecalculator/*
|
||||||
|
'cog: lseen':
|
||||||
|
- lseen/*
|
||||||
|
'cog: nudity':
|
||||||
|
- nudity/*
|
||||||
|
'cog: planttycoon':
|
||||||
|
- planttycoon/*
|
||||||
|
'cog: qrinvite':
|
||||||
|
- qrinvite/*
|
||||||
|
'cog: reactrestrict':
|
||||||
|
- reactrestrict/*
|
||||||
|
'cog: recyclingplant':
|
||||||
|
- recyclingplant/*
|
||||||
|
'cog: rpsls':
|
||||||
|
- rpsls/*
|
||||||
|
'cog: sayurl':
|
||||||
|
- sayurl/*
|
||||||
|
'cog: scp':
|
||||||
|
- scp/*
|
||||||
|
'cog: stealemoji':
|
||||||
|
- stealemoji/*
|
||||||
|
'cog: timerole':
|
||||||
|
- timerole/*
|
||||||
|
'cog: tts':
|
||||||
|
- tts/*
|
||||||
|
'cog: unicode':
|
||||||
|
- unicode/*
|
||||||
|
'cog: werewolf':
|
||||||
|
- werewolf/*
|
@ -0,0 +1,20 @@
|
|||||||
|
# GitHub Action that uses Black to reformat the Python code in an incoming pull request.
|
||||||
|
# If all Python code in the pull request is compliant with Black then this Action does nothing.
|
||||||
|
# Othewrwise, Black is run and its changes are committed back to the incoming pull request.
|
||||||
|
# https://github.com/cclauss/autoblack
|
||||||
|
|
||||||
|
name: black
|
||||||
|
on: [pull_request]
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Set up Python 3.8
|
||||||
|
uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: '3.8'
|
||||||
|
- name: Install Black
|
||||||
|
run: pip install --upgrade --no-cache-dir black
|
||||||
|
- name: Run black --check .
|
||||||
|
run: black --check --diff -l 99 .
|
@ -0,0 +1,19 @@
|
|||||||
|
# This workflow will triage pull requests and apply a label based on the
|
||||||
|
# paths that are modified in the pull request.
|
||||||
|
#
|
||||||
|
# To use this workflow, you will need to set up a .github/labeler.yml
|
||||||
|
# file with configuration. For more information, see:
|
||||||
|
# https://github.com/actions/labeler
|
||||||
|
|
||||||
|
name: Labeler
|
||||||
|
on: [pull_request_target]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
label:
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/labeler@2.2.0
|
||||||
|
with:
|
||||||
|
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
@ -1,4 +1,5 @@
|
|||||||
AUTHOR: Plab
|
AUTHOR: Plab
|
||||||
|
AUDIO: "[Audio] Identify this Anime!"
|
||||||
https://www.youtube.com/watch?v=2uq34TeWEdQ:
|
https://www.youtube.com/watch?v=2uq34TeWEdQ:
|
||||||
- 'Hagane no Renkinjutsushi (2009)'
|
- 'Hagane no Renkinjutsushi (2009)'
|
||||||
- '(2009) الخيميائي المعدني الكامل'
|
- '(2009) الخيميائي المعدني الكامل'
|
@ -1,4 +1,5 @@
|
|||||||
AUTHOR: Lazar
|
AUTHOR: Lazar
|
||||||
|
AUDIO: "[Audio] Identify this NHL Team by their goal horn"
|
||||||
https://youtu.be/6OejNXrGkK0:
|
https://youtu.be/6OejNXrGkK0:
|
||||||
- Anaheim Ducks
|
- Anaheim Ducks
|
||||||
- Anaheim
|
- Anaheim
|
File diff suppressed because it is too large
Load Diff
@ -1,13 +1,14 @@
|
|||||||
AUTHOR: Plab
|
AUTHOR: Plab
|
||||||
https://www.youtube.com/watch?v=--bWm9hhoZo:
|
NEEDS: New links for all songs.
|
||||||
|
https://www.youtube.com/watch?v=f9O2Rjn1azc:
|
||||||
- Transistor
|
- Transistor
|
||||||
https://www.youtube.com/watch?v=-4nCbgayZNE:
|
https://www.youtube.com/watch?v=PgUhYFkVdSY:
|
||||||
- Dark Cloud 2
|
- Dark Cloud 2
|
||||||
- Dark Cloud II
|
- Dark Cloud II
|
||||||
https://www.youtube.com/watch?v=-64NlME4lJU:
|
https://www.youtube.com/watch?v=1T1RZttyMwU:
|
||||||
- Mega Man 7
|
- Mega Man 7
|
||||||
- Mega Man VII
|
- Mega Man VII
|
||||||
https://www.youtube.com/watch?v=-AesqnudNuw:
|
https://www.youtube.com/watch?v=AdDbbzuq1vY:
|
||||||
- Mega Man 9
|
- Mega Man 9
|
||||||
- Mega Man IX
|
- Mega Man IX
|
||||||
https://www.youtube.com/watch?v=-BmGDtP2t7M:
|
https://www.youtube.com/watch?v=-BmGDtP2t7M:
|
@ -1,12 +0,0 @@
|
|||||||
git+git://github.com/gunthercox/chatterbot-corpus@master#egg=chatterbot_corpus
|
|
||||||
mathparse>=0.1,<0.2
|
|
||||||
nltk>=3.2,<4.0
|
|
||||||
pint>=0.8.1
|
|
||||||
python-dateutil>=2.8,<2.9
|
|
||||||
pyyaml>=5.3,<5.4
|
|
||||||
sqlalchemy>=1.3,<1.4
|
|
||||||
pytz
|
|
||||||
spacy>=2.3,<2.4
|
|
||||||
https://github.com/explosion/spacy-models/releases/download/en_core_web_sm-2.3.1/en_core_web_sm-2.3.1.tar.gz#egg=en_core_web_sm
|
|
||||||
https://github.com/explosion/spacy-models/releases/download/en_core_web_md-2.3.1/en_core_web_md-2.3.1.tar.gz#egg=en_core_web_md
|
|
||||||
# https://github.com/explosion/spacy-models/releases/download/en_core_web_lg-2.3.1/en_core_web_lg-2.3.1.tar.gz#egg=en_core_web_lg
|
|
@ -0,0 +1,71 @@
|
|||||||
|
from chatterbot.storage import StorageAdapter, SQLStorageAdapter
|
||||||
|
|
||||||
|
|
||||||
|
class MyDumbSQLStorageAdapter(SQLStorageAdapter):
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
super(SQLStorageAdapter, self).__init__(**kwargs)
|
||||||
|
|
||||||
|
from sqlalchemy import create_engine, inspect
|
||||||
|
from sqlalchemy.orm import sessionmaker
|
||||||
|
|
||||||
|
self.database_uri = kwargs.get("database_uri", False)
|
||||||
|
|
||||||
|
# None results in a sqlite in-memory database as the default
|
||||||
|
if self.database_uri is None:
|
||||||
|
self.database_uri = "sqlite://"
|
||||||
|
|
||||||
|
# Create a file database if the database is not a connection string
|
||||||
|
if not self.database_uri:
|
||||||
|
self.database_uri = "sqlite:///db.sqlite3"
|
||||||
|
|
||||||
|
self.engine = create_engine(self.database_uri, connect_args={"check_same_thread": False})
|
||||||
|
|
||||||
|
if self.database_uri.startswith("sqlite://"):
|
||||||
|
from sqlalchemy.engine import Engine
|
||||||
|
from sqlalchemy import event
|
||||||
|
|
||||||
|
@event.listens_for(Engine, "connect")
|
||||||
|
def set_sqlite_pragma(dbapi_connection, connection_record):
|
||||||
|
dbapi_connection.execute("PRAGMA journal_mode=WAL")
|
||||||
|
dbapi_connection.execute("PRAGMA synchronous=NORMAL")
|
||||||
|
|
||||||
|
if not inspect(self.engine).has_table("Statement"):
|
||||||
|
self.create_database()
|
||||||
|
|
||||||
|
self.Session = sessionmaker(bind=self.engine, expire_on_commit=True)
|
||||||
|
|
||||||
|
|
||||||
|
class AsyncSQLStorageAdapter(SQLStorageAdapter):
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
super(SQLStorageAdapter, self).__init__(**kwargs)
|
||||||
|
|
||||||
|
self.database_uri = kwargs.get("database_uri", False)
|
||||||
|
|
||||||
|
# None results in a sqlite in-memory database as the default
|
||||||
|
if self.database_uri is None:
|
||||||
|
self.database_uri = "sqlite://"
|
||||||
|
|
||||||
|
# Create a file database if the database is not a connection string
|
||||||
|
if not self.database_uri:
|
||||||
|
self.database_uri = "sqlite:///db.sqlite3"
|
||||||
|
|
||||||
|
async def initialize(self):
|
||||||
|
# from sqlalchemy import create_engine
|
||||||
|
from aiomysql.sa import create_engine
|
||||||
|
from sqlalchemy.orm import sessionmaker
|
||||||
|
|
||||||
|
self.engine = await create_engine(self.database_uri, convert_unicode=True)
|
||||||
|
|
||||||
|
if self.database_uri.startswith("sqlite://"):
|
||||||
|
from sqlalchemy.engine import Engine
|
||||||
|
from sqlalchemy import event
|
||||||
|
|
||||||
|
@event.listens_for(Engine, "connect")
|
||||||
|
def set_sqlite_pragma(dbapi_connection, connection_record):
|
||||||
|
dbapi_connection.execute("PRAGMA journal_mode=WAL")
|
||||||
|
dbapi_connection.execute("PRAGMA synchronous=NORMAL")
|
||||||
|
|
||||||
|
if not self.engine.dialect.has_table(self.engine, "Statement"):
|
||||||
|
self.create_database()
|
||||||
|
|
||||||
|
self.Session = sessionmaker(bind=self.engine, expire_on_commit=True)
|
@ -0,0 +1,351 @@
|
|||||||
|
import asyncio
|
||||||
|
import csv
|
||||||
|
import html
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import pathlib
|
||||||
|
import time
|
||||||
|
from functools import partial
|
||||||
|
|
||||||
|
from chatterbot import utils
|
||||||
|
from chatterbot.conversation import Statement
|
||||||
|
from chatterbot.tagging import PosLemmaTagger
|
||||||
|
from chatterbot.trainers import Trainer
|
||||||
|
from redbot.core.bot import Red
|
||||||
|
from dateutil import parser as date_parser
|
||||||
|
from redbot.core.utils import AsyncIter
|
||||||
|
|
||||||
|
log = logging.getLogger("red.fox_v3.chatter.trainers")
|
||||||
|
|
||||||
|
|
||||||
|
class KaggleTrainer(Trainer):
|
||||||
|
def __init__(self, chatbot, datapath: pathlib.Path, **kwargs):
|
||||||
|
super().__init__(chatbot, **kwargs)
|
||||||
|
|
||||||
|
self.data_directory = datapath / kwargs.get("downloadpath", "kaggle_download")
|
||||||
|
|
||||||
|
self.kaggle_dataset = kwargs.get(
|
||||||
|
"kaggle_dataset",
|
||||||
|
"Cornell-University/movie-dialog-corpus",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create the data directory if it does not already exist
|
||||||
|
if not os.path.exists(self.data_directory):
|
||||||
|
os.makedirs(self.data_directory)
|
||||||
|
|
||||||
|
def is_downloaded(self, file_path):
|
||||||
|
"""
|
||||||
|
Check if the data file is already downloaded.
|
||||||
|
"""
|
||||||
|
if os.path.exists(file_path):
|
||||||
|
self.chatbot.logger.info("File is already downloaded")
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def download(self, dataset):
|
||||||
|
import kaggle # This triggers the API token check
|
||||||
|
|
||||||
|
future = await asyncio.get_event_loop().run_in_executor(
|
||||||
|
None,
|
||||||
|
partial(
|
||||||
|
kaggle.api.dataset_download_files,
|
||||||
|
dataset=dataset,
|
||||||
|
path=self.data_directory,
|
||||||
|
quiet=False,
|
||||||
|
unzip=True,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
def train(self, *args, **kwargs):
|
||||||
|
log.error("See asynctrain instead")
|
||||||
|
|
||||||
|
def asynctrain(self, *args, **kwargs):
|
||||||
|
raise self.TrainerInitializationException()
|
||||||
|
|
||||||
|
|
||||||
|
class SouthParkTrainer(KaggleTrainer):
|
||||||
|
def __init__(self, chatbot, datapath: pathlib.Path, **kwargs):
|
||||||
|
super().__init__(
|
||||||
|
chatbot,
|
||||||
|
datapath,
|
||||||
|
downloadpath="ubuntu_data_v2",
|
||||||
|
kaggle_dataset="tovarischsukhov/southparklines",
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class MovieTrainer(KaggleTrainer):
|
||||||
|
def __init__(self, chatbot, datapath: pathlib.Path, **kwargs):
|
||||||
|
super().__init__(
|
||||||
|
chatbot,
|
||||||
|
datapath,
|
||||||
|
downloadpath="kaggle_movies",
|
||||||
|
kaggle_dataset="Cornell-University/movie-dialog-corpus",
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
|
async def run_movie_training(self):
|
||||||
|
dialogue_file = "movie_lines.tsv"
|
||||||
|
conversation_file = "movie_conversations.tsv"
|
||||||
|
log.info(f"Beginning dialogue training on {dialogue_file}")
|
||||||
|
start_time = time.time()
|
||||||
|
|
||||||
|
tagger = PosLemmaTagger(language=self.chatbot.storage.tagger.language)
|
||||||
|
|
||||||
|
# [lineID, characterID, movieID, character name, text of utterance]
|
||||||
|
# File parsing from https://www.kaggle.com/mushaya/conversation-chatbot
|
||||||
|
|
||||||
|
with open(self.data_directory / conversation_file, "r", encoding="utf-8-sig") as conv_tsv:
|
||||||
|
conv_lines = conv_tsv.readlines()
|
||||||
|
with open(self.data_directory / dialogue_file, "r", encoding="utf-8-sig") as lines_tsv:
|
||||||
|
dialog_lines = lines_tsv.readlines()
|
||||||
|
|
||||||
|
# trans_dict = str.maketrans({"<u>": "__", "</u>": "__", '""': '"'})
|
||||||
|
|
||||||
|
lines_dict = {}
|
||||||
|
for line in dialog_lines:
|
||||||
|
_line = line[:-1].strip('"').split("\t")
|
||||||
|
if len(_line) >= 5: # Only good lines
|
||||||
|
lines_dict[_line[0]] = (
|
||||||
|
html.unescape(("".join(_line[4:])).strip())
|
||||||
|
.replace("<u>", "__")
|
||||||
|
.replace("</u>", "__")
|
||||||
|
.replace('""', '"')
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
log.debug(f"Bad line {_line}")
|
||||||
|
|
||||||
|
# collecting line ids for each conversation
|
||||||
|
conv = []
|
||||||
|
for line in conv_lines[:-1]:
|
||||||
|
_line = line[:-1].split("\t")[-1][1:-1].replace("'", "").replace(" ", ",")
|
||||||
|
conv.append(_line.split(","))
|
||||||
|
|
||||||
|
# conversations = csv.reader(conv_tsv, delimiter="\t")
|
||||||
|
#
|
||||||
|
# reader = csv.reader(lines_tsv, delimiter="\t")
|
||||||
|
#
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# lines_dict = {}
|
||||||
|
# for row in reader:
|
||||||
|
# try:
|
||||||
|
# lines_dict[row[0].strip('"')] = row[4]
|
||||||
|
# except:
|
||||||
|
# log.exception(f"Bad line: {row}")
|
||||||
|
# pass
|
||||||
|
# else:
|
||||||
|
# # log.info(f"Good line: {row}")
|
||||||
|
# pass
|
||||||
|
#
|
||||||
|
# # lines_dict = {row[0].strip('"'): row[4] for row in reader_list}
|
||||||
|
|
||||||
|
statements_from_file = []
|
||||||
|
save_every = 300
|
||||||
|
count = 0
|
||||||
|
|
||||||
|
# [characterID of first, characterID of second, movieID, list of utterances]
|
||||||
|
async for lines in AsyncIter(conv):
|
||||||
|
previous_statement_text = None
|
||||||
|
previous_statement_search_text = ""
|
||||||
|
|
||||||
|
for line in lines:
|
||||||
|
text = lines_dict[line]
|
||||||
|
statement = Statement(
|
||||||
|
text=text,
|
||||||
|
in_response_to=previous_statement_text,
|
||||||
|
conversation="training",
|
||||||
|
)
|
||||||
|
|
||||||
|
for preprocessor in self.chatbot.preprocessors:
|
||||||
|
statement = preprocessor(statement)
|
||||||
|
|
||||||
|
statement.search_text = tagger.get_text_index_string(statement.text)
|
||||||
|
statement.search_in_response_to = previous_statement_search_text
|
||||||
|
|
||||||
|
previous_statement_text = statement.text
|
||||||
|
previous_statement_search_text = statement.search_text
|
||||||
|
|
||||||
|
statements_from_file.append(statement)
|
||||||
|
|
||||||
|
count += 1
|
||||||
|
if count >= save_every:
|
||||||
|
if statements_from_file:
|
||||||
|
self.chatbot.storage.create_many(statements_from_file)
|
||||||
|
statements_from_file = []
|
||||||
|
count = 0
|
||||||
|
|
||||||
|
if statements_from_file:
|
||||||
|
self.chatbot.storage.create_many(statements_from_file)
|
||||||
|
|
||||||
|
log.info(f"Training took {time.time() - start_time} seconds.")
|
||||||
|
|
||||||
|
async def asynctrain(self, *args, **kwargs):
|
||||||
|
extracted_lines = self.data_directory / "movie_lines.tsv"
|
||||||
|
extracted_lines: pathlib.Path
|
||||||
|
|
||||||
|
# Download and extract the Ubuntu dialog corpus if needed
|
||||||
|
if not extracted_lines.exists():
|
||||||
|
await self.download(self.kaggle_dataset)
|
||||||
|
else:
|
||||||
|
log.info("Movie dialog already downloaded")
|
||||||
|
if not extracted_lines.exists():
|
||||||
|
raise FileNotFoundError(f"{extracted_lines}")
|
||||||
|
|
||||||
|
await self.run_movie_training()
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
# train_dialogue = kwargs.get("train_dialogue", True)
|
||||||
|
# train_196_dialogue = kwargs.get("train_196", False)
|
||||||
|
# train_301_dialogue = kwargs.get("train_301", False)
|
||||||
|
#
|
||||||
|
# if train_dialogue:
|
||||||
|
# await self.run_dialogue_training(extracted_dir, "dialogueText.csv")
|
||||||
|
#
|
||||||
|
# if train_196_dialogue:
|
||||||
|
# await self.run_dialogue_training(extracted_dir, "dialogueText_196.csv")
|
||||||
|
#
|
||||||
|
# if train_301_dialogue:
|
||||||
|
# await self.run_dialogue_training(extracted_dir, "dialogueText_301.csv")
|
||||||
|
|
||||||
|
|
||||||
|
class UbuntuCorpusTrainer2(KaggleTrainer):
|
||||||
|
def __init__(self, chatbot, datapath: pathlib.Path, **kwargs):
|
||||||
|
super().__init__(
|
||||||
|
chatbot,
|
||||||
|
datapath,
|
||||||
|
downloadpath="kaggle_ubuntu",
|
||||||
|
kaggle_dataset="rtatman/ubuntu-dialogue-corpus",
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
|
async def asynctrain(self, *args, **kwargs):
|
||||||
|
extracted_dir = self.data_directory / "Ubuntu-dialogue-corpus"
|
||||||
|
|
||||||
|
# Download and extract the Ubuntu dialog corpus if needed
|
||||||
|
if not extracted_dir.exists():
|
||||||
|
await self.download(self.kaggle_dataset)
|
||||||
|
else:
|
||||||
|
log.info("Ubuntu dialogue already downloaded")
|
||||||
|
if not extracted_dir.exists():
|
||||||
|
raise FileNotFoundError("Did not extract in the expected way")
|
||||||
|
|
||||||
|
train_dialogue = kwargs.get("train_dialogue", True)
|
||||||
|
train_196_dialogue = kwargs.get("train_196", False)
|
||||||
|
train_301_dialogue = kwargs.get("train_301", False)
|
||||||
|
|
||||||
|
if train_dialogue:
|
||||||
|
await self.run_dialogue_training(extracted_dir, "dialogueText.csv")
|
||||||
|
|
||||||
|
if train_196_dialogue:
|
||||||
|
await self.run_dialogue_training(extracted_dir, "dialogueText_196.csv")
|
||||||
|
|
||||||
|
if train_301_dialogue:
|
||||||
|
await self.run_dialogue_training(extracted_dir, "dialogueText_301.csv")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
async def run_dialogue_training(self, extracted_dir, dialogue_file):
|
||||||
|
log.info(f"Beginning dialogue training on {dialogue_file}")
|
||||||
|
start_time = time.time()
|
||||||
|
|
||||||
|
tagger = PosLemmaTagger(language=self.chatbot.storage.tagger.language)
|
||||||
|
|
||||||
|
with open(extracted_dir / dialogue_file, "r", encoding="utf-8") as dg:
|
||||||
|
reader = csv.DictReader(dg)
|
||||||
|
|
||||||
|
next(reader) # Skip the header
|
||||||
|
|
||||||
|
last_dialogue_id = None
|
||||||
|
previous_statement_text = None
|
||||||
|
previous_statement_search_text = ""
|
||||||
|
statements_from_file = []
|
||||||
|
|
||||||
|
save_every = 50
|
||||||
|
count = 0
|
||||||
|
|
||||||
|
async for row in AsyncIter(reader):
|
||||||
|
dialogue_id = row["dialogueID"]
|
||||||
|
if dialogue_id != last_dialogue_id:
|
||||||
|
previous_statement_text = None
|
||||||
|
previous_statement_search_text = ""
|
||||||
|
last_dialogue_id = dialogue_id
|
||||||
|
count += 1
|
||||||
|
if count >= save_every:
|
||||||
|
if statements_from_file:
|
||||||
|
self.chatbot.storage.create_many(statements_from_file)
|
||||||
|
statements_from_file = []
|
||||||
|
count = 0
|
||||||
|
|
||||||
|
if len(row) > 0:
|
||||||
|
statement = Statement(
|
||||||
|
text=row["text"],
|
||||||
|
in_response_to=previous_statement_text,
|
||||||
|
conversation="training",
|
||||||
|
# created_at=date_parser.parse(row["date"]),
|
||||||
|
persona=row["from"],
|
||||||
|
)
|
||||||
|
|
||||||
|
for preprocessor in self.chatbot.preprocessors:
|
||||||
|
statement = preprocessor(statement)
|
||||||
|
|
||||||
|
statement.search_text = tagger.get_text_index_string(statement.text)
|
||||||
|
statement.search_in_response_to = previous_statement_search_text
|
||||||
|
|
||||||
|
previous_statement_text = statement.text
|
||||||
|
previous_statement_search_text = statement.search_text
|
||||||
|
|
||||||
|
statements_from_file.append(statement)
|
||||||
|
|
||||||
|
if statements_from_file:
|
||||||
|
self.chatbot.storage.create_many(statements_from_file)
|
||||||
|
|
||||||
|
log.info(f"Training took {time.time() - start_time} seconds.")
|
||||||
|
|
||||||
|
|
||||||
|
class TwitterCorpusTrainer(Trainer):
|
||||||
|
pass
|
||||||
|
# def train(self, *args, **kwargs):
|
||||||
|
# """
|
||||||
|
# Train the chat bot based on the provided list of
|
||||||
|
# statements that represents a single conversation.
|
||||||
|
# """
|
||||||
|
# import twint
|
||||||
|
#
|
||||||
|
# c = twint.Config()
|
||||||
|
# c.__dict__.update(kwargs)
|
||||||
|
# twint.run.Search(c)
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# previous_statement_text = None
|
||||||
|
# previous_statement_search_text = ''
|
||||||
|
#
|
||||||
|
# statements_to_create = []
|
||||||
|
#
|
||||||
|
# for conversation_count, text in enumerate(conversation):
|
||||||
|
# if self.show_training_progress:
|
||||||
|
# utils.print_progress_bar(
|
||||||
|
# 'List Trainer',
|
||||||
|
# conversation_count + 1, len(conversation)
|
||||||
|
# )
|
||||||
|
#
|
||||||
|
# statement_search_text = self.chatbot.storage.tagger.get_text_index_string(text)
|
||||||
|
#
|
||||||
|
# statement = self.get_preprocessed_statement(
|
||||||
|
# Statement(
|
||||||
|
# text=text,
|
||||||
|
# search_text=statement_search_text,
|
||||||
|
# in_response_to=previous_statement_text,
|
||||||
|
# search_in_response_to=previous_statement_search_text,
|
||||||
|
# conversation='training'
|
||||||
|
# )
|
||||||
|
# )
|
||||||
|
#
|
||||||
|
# previous_statement_text = statement.text
|
||||||
|
# previous_statement_search_text = statement_search_text
|
||||||
|
#
|
||||||
|
# statements_to_create.append(statement)
|
||||||
|
#
|
||||||
|
# self.chatbot.storage.create_many(statements_to_create)
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"maps": [
|
"maps": [
|
||||||
"simple_blank_map",
|
"simple",
|
||||||
"test",
|
"ck2",
|
||||||
"test2"
|
"HoI"
|
||||||
]
|
]
|
||||||
}
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
from apscheduler.triggers.date import DateTrigger
|
||||||
|
|
||||||
|
|
||||||
|
class CustomDateTrigger(DateTrigger):
|
||||||
|
def get_next_fire_time(self, previous_fire_time, now):
|
||||||
|
next_run = super().get_next_fire_time(previous_fire_time, now)
|
||||||
|
return next_run if next_run is not None and next_run >= now else None
|
||||||
|
|
||||||
|
def __getstate__(self):
|
||||||
|
return {"version": 1, "run_date": self.run_date}
|
@ -1,5 +1,7 @@
|
|||||||
from .infochannel import InfoChannel
|
from .infochannel import InfoChannel
|
||||||
|
|
||||||
|
|
||||||
def setup(bot):
|
async def setup(bot):
|
||||||
bot.add_cog(InfoChannel(bot))
|
ic_cog = InfoChannel(bot)
|
||||||
|
bot.add_cog(ic_cog)
|
||||||
|
await ic_cog.initialize()
|
||||||
|
@ -0,0 +1,91 @@
|
|||||||
|
"""
|
||||||
|
Role Constants
|
||||||
|
|
||||||
|
Role Alignment guide as follows:
|
||||||
|
Town: 1
|
||||||
|
Werewolf: 2
|
||||||
|
Neutral: 3
|
||||||
|
|
||||||
|
Additional alignments may be added when warring factions are added
|
||||||
|
(Rival werewolves, cultists, vampires)
|
||||||
|
|
||||||
|
Role Category enrollment guide as follows (See Role.category):
|
||||||
|
Town:
|
||||||
|
1: Random, 2: Investigative, 3: Protective, 4: Government,
|
||||||
|
5: Killing, 6: Power (Special night action)
|
||||||
|
|
||||||
|
Werewolf:
|
||||||
|
11: Random, 12: Deception, 15: Killing, 16: Support
|
||||||
|
|
||||||
|
Neutral:
|
||||||
|
21: Benign, 22: Evil, 23: Killing
|
||||||
|
|
||||||
|
|
||||||
|
Example category:
|
||||||
|
category = [1, 5, 6] Could be Veteran
|
||||||
|
category = [1, 5] Could be Bodyguard
|
||||||
|
category = [11, 16] Could be Werewolf Silencer
|
||||||
|
category = [22] Could be Blob (non-killing)
|
||||||
|
category = [22, 23] Could be Serial-Killer
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
ALIGNMENT_TOWN = 1
|
||||||
|
ALIGNMENT_WEREWOLF = 2
|
||||||
|
ALIGNMENT_NEUTRAL = 3
|
||||||
|
ALIGNMENT_MAP = {"Town": 1, "Werewolf": 2, "Neutral": 3}
|
||||||
|
|
||||||
|
# 0-9: Town Role Categories
|
||||||
|
# 10-19: Werewolf Role Categories
|
||||||
|
# 20-29: Neutral Role Categories
|
||||||
|
CATEGORY_TOWN_RANDOM = 1
|
||||||
|
CATEGORY_TOWN_INVESTIGATIVE = 2
|
||||||
|
CATEGORY_TOWN_PROTECTIVE = 3
|
||||||
|
CATEGORY_TOWN_GOVERNMENT = 4
|
||||||
|
CATEGORY_TOWN_KILLING = 5
|
||||||
|
CATEGORY_TOWN_POWER = 6
|
||||||
|
|
||||||
|
CATEGORY_WW_RANDOM = 11
|
||||||
|
CATEGORY_WW_DECEPTION = 12
|
||||||
|
CATEGORY_WW_KILLING = 15
|
||||||
|
CATEGORY_WW_SUPPORT = 16
|
||||||
|
|
||||||
|
CATEGORY_NEUTRAL_BENIGN = 21
|
||||||
|
CATEGORY_NEUTRAL_EVIL = 22
|
||||||
|
CATEGORY_NEUTRAL_KILLING = 23
|
||||||
|
|
||||||
|
ROLE_CATEGORY_DESCRIPTIONS = {
|
||||||
|
CATEGORY_TOWN_RANDOM: "Random",
|
||||||
|
CATEGORY_TOWN_INVESTIGATIVE: "Investigative",
|
||||||
|
CATEGORY_TOWN_PROTECTIVE: "Protective",
|
||||||
|
CATEGORY_TOWN_GOVERNMENT: "Government",
|
||||||
|
CATEGORY_TOWN_KILLING: "Killing",
|
||||||
|
CATEGORY_TOWN_POWER: "Power (Special night action)",
|
||||||
|
CATEGORY_WW_RANDOM: "Random",
|
||||||
|
CATEGORY_WW_DECEPTION: "Deception",
|
||||||
|
CATEGORY_WW_KILLING: "Killing",
|
||||||
|
CATEGORY_WW_SUPPORT: "Support",
|
||||||
|
CATEGORY_NEUTRAL_BENIGN: "Benign",
|
||||||
|
CATEGORY_NEUTRAL_EVIL: "Evil",
|
||||||
|
CATEGORY_NEUTRAL_KILLING: "Killing",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
Listener Actions Priority Guide
|
||||||
|
|
||||||
|
Action priority guide as follows (see listeners.py for wolflistener):
|
||||||
|
_at_night_start
|
||||||
|
0. No Action
|
||||||
|
1. Detain actions (Jailer/Kidnapper)
|
||||||
|
2. Group discussions and choose targets
|
||||||
|
|
||||||
|
_at_night_end
|
||||||
|
0. No Action
|
||||||
|
1. Self actions (Veteran)
|
||||||
|
2. Target switching and role blocks (bus driver, witch, escort)
|
||||||
|
3. Protection / Preempt actions (bodyguard/framer)
|
||||||
|
4. Non-disruptive actions (seer/silencer)
|
||||||
|
5. Disruptive actions (Killing)
|
||||||
|
6. Role altering actions (Cult / Mason / Shifter)
|
||||||
|
"""
|
@ -0,0 +1,28 @@
|
|||||||
|
from typing import TYPE_CHECKING, Union
|
||||||
|
|
||||||
|
import discord
|
||||||
|
from discord.ext.commands import BadArgument, Converter
|
||||||
|
from redbot.core import commands
|
||||||
|
|
||||||
|
from werewolf.player import Player
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
PlayerConverter = Union[int, discord.Member]
|
||||||
|
CronConverter = str
|
||||||
|
else:
|
||||||
|
|
||||||
|
class PlayerConverter(Converter):
|
||||||
|
async def convert(self, ctx, argument) -> Player:
|
||||||
|
|
||||||
|
try:
|
||||||
|
target = await commands.MemberConverter().convert(ctx, argument)
|
||||||
|
except BadArgument:
|
||||||
|
try:
|
||||||
|
target = int(argument)
|
||||||
|
assert target >= 0
|
||||||
|
except (ValueError, AssertionError):
|
||||||
|
raise BadArgument
|
||||||
|
|
||||||
|
# TODO: Get the game for context without making a new one
|
||||||
|
# TODO: Get player from game based on either ID or member object
|
||||||
|
return target
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,106 @@
|
|||||||
|
import inspect
|
||||||
|
|
||||||
|
|
||||||
|
def wolflistener(name=None, priority=0):
|
||||||
|
"""A decorator that marks a function as a listener.
|
||||||
|
|
||||||
|
This is the werewolf.Game equivalent of :meth:`.Cog.listener`.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
------------
|
||||||
|
name: :class:`str`
|
||||||
|
The name of the event being listened to. If not provided, it
|
||||||
|
defaults to the function's name.
|
||||||
|
priority: :class:`int`
|
||||||
|
The priority of the listener.
|
||||||
|
Priority guide as follows:
|
||||||
|
_at_night_start
|
||||||
|
0. No Action
|
||||||
|
1. Detain actions (Jailer/Kidnapper)
|
||||||
|
2. Group discussions and choose targets
|
||||||
|
|
||||||
|
_at_night_end
|
||||||
|
0. No Action
|
||||||
|
1. Self actions (Veteran)
|
||||||
|
2. Target switching and role blocks (bus driver, witch, escort)
|
||||||
|
3. Protection / Preempt actions (bodyguard/framer)
|
||||||
|
4. Non-disruptive actions (seer/silencer)
|
||||||
|
5. Disruptive actions (Killing)
|
||||||
|
6. Role altering actions (Cult / Mason / Shifter)
|
||||||
|
|
||||||
|
Raises
|
||||||
|
--------
|
||||||
|
TypeError
|
||||||
|
The function is not a coroutine function or a string was not passed as
|
||||||
|
the name.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if name is not None and not isinstance(name, str):
|
||||||
|
raise TypeError(
|
||||||
|
"Game.listener expected str but received {0.__class__.__name__!r} instead.".format(
|
||||||
|
name
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def decorator(func):
|
||||||
|
actual = func
|
||||||
|
if isinstance(actual, staticmethod):
|
||||||
|
actual = actual.__func__
|
||||||
|
if not inspect.iscoroutinefunction(actual):
|
||||||
|
raise TypeError("Listener function must be a coroutine function.")
|
||||||
|
actual.__wolf_listener__ = priority
|
||||||
|
to_assign = name or actual.__name__
|
||||||
|
try:
|
||||||
|
actual.__wolf_listener_names__.append((priority, to_assign))
|
||||||
|
except AttributeError:
|
||||||
|
actual.__wolf_listener_names__ = [(priority, to_assign)]
|
||||||
|
# we have to return `func` instead of `actual` because
|
||||||
|
# we need the type to be `staticmethod` for the metaclass
|
||||||
|
# to pick it up but the metaclass unfurls the function and
|
||||||
|
# thus the assignments need to be on the actual function
|
||||||
|
return func
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
class WolfListenerMeta(type):
|
||||||
|
def __new__(mcs, *args, **kwargs):
|
||||||
|
name, bases, attrs = args
|
||||||
|
|
||||||
|
listeners = {}
|
||||||
|
need_at_msg = "Listeners must start with at_ (in method {0.__name__}.{1})"
|
||||||
|
|
||||||
|
new_cls = super().__new__(mcs, name, bases, attrs, **kwargs)
|
||||||
|
for base in reversed(new_cls.__mro__):
|
||||||
|
for elem, value in base.__dict__.items():
|
||||||
|
if elem in listeners:
|
||||||
|
del listeners[elem]
|
||||||
|
|
||||||
|
is_static_method = isinstance(value, staticmethod)
|
||||||
|
if is_static_method:
|
||||||
|
value = value.__func__
|
||||||
|
if inspect.iscoroutinefunction(value):
|
||||||
|
try:
|
||||||
|
is_listener = getattr(value, "__wolf_listener__")
|
||||||
|
except AttributeError:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
# if not elem.startswith("at_"):
|
||||||
|
# raise TypeError(need_at_msg.format(base, elem))
|
||||||
|
listeners[elem] = value
|
||||||
|
|
||||||
|
listeners_as_list = []
|
||||||
|
for listener in listeners.values():
|
||||||
|
for priority, listener_name in listener.__wolf_listener_names__:
|
||||||
|
# I use __name__ instead of just storing the value so I can inject
|
||||||
|
# the self attribute when the time comes to add them to the bot
|
||||||
|
listeners_as_list.append((priority, listener_name, listener.__name__))
|
||||||
|
|
||||||
|
new_cls.__wolf_listeners__ = listeners_as_list
|
||||||
|
return new_cls
|
||||||
|
|
||||||
|
|
||||||
|
class WolfListener(metaclass=WolfListenerMeta):
|
||||||
|
def __init__(self, game):
|
||||||
|
for priority, name, method_name in self.__wolf_listeners__:
|
||||||
|
game.add_ww_listener(getattr(self, method_name), priority, name)
|
@ -0,0 +1,11 @@
|
|||||||
|
from .villager import Villager
|
||||||
|
from .seer import Seer
|
||||||
|
|
||||||
|
from .vanillawerewolf import VanillaWerewolf
|
||||||
|
|
||||||
|
from .shifter import Shifter
|
||||||
|
|
||||||
|
# Don't sort these imports. They are unstably in order
|
||||||
|
# TODO: Replace with unique IDs for roles in the future
|
||||||
|
|
||||||
|
__all__ = ["Seer", "Shifter", "VanillaWerewolf", "Villager"]
|
@ -0,0 +1,101 @@
|
|||||||
|
import logging
|
||||||
|
import random
|
||||||
|
|
||||||
|
from werewolf.constants import ALIGNMENT_NEUTRAL, CATEGORY_NEUTRAL_EVIL
|
||||||
|
from werewolf.listener import wolflistener
|
||||||
|
from werewolf.player import Player
|
||||||
|
from werewolf.role import Role
|
||||||
|
|
||||||
|
log = logging.getLogger("red.fox_v3.werewolf.role.blob")
|
||||||
|
|
||||||
|
|
||||||
|
class TheBlob(Role):
|
||||||
|
rand_choice = True
|
||||||
|
category = [CATEGORY_NEUTRAL_EVIL] # List of enrolled categories
|
||||||
|
alignment = ALIGNMENT_NEUTRAL # 1: Town, 2: Werewolf, 3: Neutral
|
||||||
|
channel_id = "" # Empty for no private channel
|
||||||
|
unique = True # Only one of this role per game
|
||||||
|
game_start_message = (
|
||||||
|
"Your role is **The Blob**\n"
|
||||||
|
"You win by absorbing everyone town\n"
|
||||||
|
"Lynch players during the day with `[p]ww vote <ID>`\n"
|
||||||
|
"Each night you will absorb an adjacent player"
|
||||||
|
)
|
||||||
|
description = (
|
||||||
|
"A mysterious green blob of jelly, slowly growing in size.\n"
|
||||||
|
"The Blob fears no evil, must be dealt with in town"
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self, game):
|
||||||
|
super().__init__(game)
|
||||||
|
|
||||||
|
self.blob_target = None
|
||||||
|
|
||||||
|
async def see_alignment(self, source=None):
|
||||||
|
"""
|
||||||
|
Interaction for investigative roles attempting
|
||||||
|
to see team (Village, Werewolf, Other)
|
||||||
|
"""
|
||||||
|
return ALIGNMENT_NEUTRAL
|
||||||
|
|
||||||
|
async def get_role(self, source=None):
|
||||||
|
"""
|
||||||
|
Interaction for powerful access of role
|
||||||
|
Unlikely to be able to deceive this
|
||||||
|
"""
|
||||||
|
return "The Blob"
|
||||||
|
|
||||||
|
async def see_role(self, source=None):
|
||||||
|
"""
|
||||||
|
Interaction for investigative roles.
|
||||||
|
More common to be able to deceive these roles
|
||||||
|
"""
|
||||||
|
return "The Blob"
|
||||||
|
|
||||||
|
async def kill(self, source):
|
||||||
|
"""
|
||||||
|
Called when someone is trying to kill you!
|
||||||
|
Can you do anything about it?
|
||||||
|
self.player.alive is now set to False, set to True to stay alive
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Blob cannot simply be killed
|
||||||
|
self.player.alive = True
|
||||||
|
|
||||||
|
@wolflistener("at_night_start", priority=2)
|
||||||
|
async def _at_night_start(self):
|
||||||
|
if not self.player.alive:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.blob_target = None
|
||||||
|
idx = self.player.id
|
||||||
|
left_or_right = random.choice((-1, 1))
|
||||||
|
while self.blob_target is None:
|
||||||
|
idx += left_or_right
|
||||||
|
if idx >= len(self.game.players):
|
||||||
|
idx = 0
|
||||||
|
|
||||||
|
player = self.game.players[idx]
|
||||||
|
|
||||||
|
# you went full circle, everyone is a blob or something else is wrong
|
||||||
|
if player == self.player:
|
||||||
|
break
|
||||||
|
|
||||||
|
if player.role.properties.get("been_blobbed", False):
|
||||||
|
self.blob_target = player
|
||||||
|
|
||||||
|
if self.blob_target is not None:
|
||||||
|
await self.player.send_dm(f"**You will attempt to absorb {self.blob_target} tonight**")
|
||||||
|
else:
|
||||||
|
await self.player.send_dm(f"**No player will be absorbed tonight**")
|
||||||
|
|
||||||
|
@wolflistener("at_night_end", priority=4)
|
||||||
|
async def _at_night_end(self):
|
||||||
|
if self.blob_target is None or not self.player.alive:
|
||||||
|
return
|
||||||
|
|
||||||
|
target: "Player" = await self.game.visit(self.blob_target, self.player)
|
||||||
|
|
||||||
|
if target is not None:
|
||||||
|
target.role.properties["been_blobbed"] = True
|
||||||
|
self.game.night_results.append("The Blob grows...")
|
@ -0,0 +1 @@
|
|||||||
|
from .wolfvote import WolfVote
|
Loading…
Reference in new issue