vastly improved documentation
This commit is contained in:
parent
585ac3965c
commit
00121705f8
45
README.md
45
README.md
|
@ -1,3 +1,46 @@
|
|||
# markovbot
|
||||
|
||||
A python telegram bot to generate hilarious messages using markov chains.
|
||||
A python telegram bot to generate hilarious messages using markov chains.
|
||||
|
||||
## Installation:
|
||||
|
||||
Create a telegram bot using @botfather. Change its privacy accordingly, in case you want to use this bot on a group.
|
||||
|
||||
```bash
|
||||
git clone --recurse-submodules http://git.dekedin.me/raphy/markovbot.git
|
||||
cd markovbot
|
||||
touch conf.ini
|
||||
```
|
||||
|
||||
Edit `conf.ini` as specified below, and then run `main.py`.
|
||||
|
||||
## Configuration
|
||||
|
||||
Here's a sample configuration file:
|
||||
|
||||
```
|
||||
[Telegram]
|
||||
token = <YOUR BOT TOKEN>
|
||||
|
||||
[AccessControl]
|
||||
groups = -4444,-020202,-1111
|
||||
```
|
||||
|
||||
The token entry is necessary. Insert your bot token there.
|
||||
|
||||
AccessControl is entirely optional. If the whole AccessControl block is removed, the bot will work in "single database", where every message from every chat and group will be stored in the same "pool".
|
||||
|
||||
This is not a great idea to prevent spammers and protect privacy, so you can specify a list of comma-separated groups that are allowed to use the bot using the `groups=` voice.
|
||||
|
||||
They don't have to be groups, just the chat\_id matters. Moreover, every group will have an isolated pool of words.
|
||||
|
||||
## Usage
|
||||
|
||||
`/gen` - will generate a message. Use `/gen <number>` to generate more. Currently there's a hard limit of 19 messages.
|
||||
`/status` - will print some statistics regarding the bot.
|
||||
`/debug` - prints chat information. Useful to discover `chat_id`.
|
||||
`/start` - just says hi.
|
||||
|
||||
## future goals
|
||||
|
||||
More config options and more commands, I guess.
|
||||
|
|
18
main.py
18
main.py
|
@ -20,6 +20,7 @@ def debug(bot, text, chat):
|
|||
|
||||
|
||||
def makestore(config, markov: Markov):
|
||||
"""Generate the store command according to markov object and config"""
|
||||
try:
|
||||
groups = config["AccessControl"]["groups"].split(",")
|
||||
|
||||
|
@ -46,7 +47,7 @@ def makestore(config, markov: Markov):
|
|||
|
||||
|
||||
def makegen(config, markov: Markov):
|
||||
|
||||
"""Make gen command according to markov object and config"""
|
||||
try:
|
||||
groups = config["AccessControl"]["groups"].split(",")
|
||||
|
||||
|
@ -56,11 +57,11 @@ def makegen(config, markov: Markov):
|
|||
|
||||
if str(grp) in groups:
|
||||
|
||||
try: # FUCK IT, UNDOCUMENTED FEATURE
|
||||
try: # FUCK IT, UNDOCUMENTED FEATURE. /gen <n> will generate n messages
|
||||
num = int(text.split(" ")[1])
|
||||
|
||||
|
||||
res = ""
|
||||
if num < 20:
|
||||
if num < 20: # Soft limit of 20 messages in a row.
|
||||
for _ in range(num):
|
||||
res += " " + markov.generate(grp)
|
||||
return str(res)
|
||||
|
@ -80,8 +81,9 @@ def makegen(config, markov: Markov):
|
|||
|
||||
return groupnocheck
|
||||
|
||||
def makestatus(config, markov: Markov):
|
||||
|
||||
def makestatus(config, markov: Markov):
|
||||
"""Make status command according to markov object and options"""
|
||||
try:
|
||||
groups = config["AccessControl"]["groups"].split(",")
|
||||
|
||||
|
@ -91,7 +93,6 @@ def makestatus(config, markov: Markov):
|
|||
|
||||
if str(grp) in groups:
|
||||
|
||||
|
||||
return markov.status(grp)
|
||||
else:
|
||||
return ""
|
||||
|
@ -107,8 +108,6 @@ def makestatus(config, markov: Markov):
|
|||
return groupnocheck
|
||||
|
||||
|
||||
|
||||
|
||||
def main():
|
||||
print("starting bot lmao")
|
||||
|
||||
|
@ -133,8 +132,9 @@ def main():
|
|||
pass # TODO
|
||||
markov = MarkovLite()
|
||||
except KeyError:
|
||||
markov = MarkovLite(inmemory=False) # starting temporarily in memory
|
||||
markov = MarkovLite(inmemory=False)
|
||||
|
||||
# generate the commands to feed to the telegram bot library
|
||||
store = makestore(config, markov)
|
||||
gen = makegen(config, markov)
|
||||
status = makestatus(config, markov)
|
||||
|
|
|
@ -14,9 +14,9 @@ from typing import Any, Dict, List, Tuple
|
|||
|
||||
def processwords(text: str) -> List[Tuple[str, str]]:
|
||||
|
||||
# words = re.findall(r"\w+|[^\w\s]", text, re.UNICODE)
|
||||
|
||||
# words are just split by whitespaces. This means that punctuation is included in words.
|
||||
words: List[Any] = text.split(" ")
|
||||
|
||||
# Add a NONE at the end of the list, to mark the end of a message AND the beginning of one.
|
||||
words.append(None)
|
||||
words.insert(0, None)
|
||||
|
@ -26,10 +26,9 @@ def processwords(text: str) -> List[Tuple[str, str]]:
|
|||
|
||||
def makemarkov(worddict: Dict[str | None, List[Tuple[str, int]]]) -> str:
|
||||
|
||||
# Initial value is None.
|
||||
|
||||
# curword = random.choice(list(worddict.keys()))
|
||||
|
||||
# Initial value is None. This value is used both to mark the end of a sentence and to mark the beginning of one.
|
||||
curword = None
|
||||
|
||||
res = ""
|
||||
|
@ -38,9 +37,7 @@ def makemarkov(worddict: Dict[str | None, List[Tuple[str, int]]]) -> str:
|
|||
|
||||
mem = {}
|
||||
|
||||
while True: # We just break when curword is NONE again.
|
||||
# append first, then pick next word
|
||||
|
||||
while True: # We just break when curword is NONE again.
|
||||
|
||||
# build frequency dict if not memoize
|
||||
if curword not in mem:
|
||||
|
@ -67,7 +64,7 @@ def makemarkov(worddict: Dict[str | None, List[Tuple[str, int]]]) -> str:
|
|||
pick = random.choices(candidates, weights)
|
||||
|
||||
curword = pick[0]
|
||||
|
||||
|
||||
if curword is None:
|
||||
break
|
||||
else:
|
||||
|
@ -98,7 +95,7 @@ class Markov(ABC):
|
|||
|
||||
class MarkovLite(Markov):
|
||||
def __init__(self, db_file="markov.db", inmemory=False):
|
||||
|
||||
"""Initialize sqlite3 backend markov object."""
|
||||
log.info(sqlite3.version)
|
||||
|
||||
self.inmemory = inmemory
|
||||
|
@ -117,9 +114,12 @@ class MarkovLite(Markov):
|
|||
log.debug(f"storing {text} from group {grp}")
|
||||
|
||||
cursor = self.conn.cursor()
|
||||
|
||||
for word_pair in processwords(text):
|
||||
self.insert_words(cursor, word_pair, grp)
|
||||
|
||||
try:
|
||||
for word_pair in processwords(text):
|
||||
self.insert_words(cursor, word_pair, grp)
|
||||
except Exception as e:
|
||||
log.debug(f"Something went wrong: {e}")
|
||||
|
||||
log.debug("committing changes...")
|
||||
|
||||
|
@ -133,13 +133,16 @@ class MarkovLite(Markov):
|
|||
return makemarkov(worddict)
|
||||
|
||||
def status(self, grp: int) -> str:
|
||||
|
||||
|
||||
words = self.get_worddict(grp)
|
||||
|
||||
total = sum([p[1] if p[0] is not None else 0
|
||||
for wordlist in words.values()
|
||||
for p in wordlist])
|
||||
|
||||
total = sum(
|
||||
[
|
||||
p[1] if p[0] is not None else 0
|
||||
for wordlist in words.values()
|
||||
for p in wordlist
|
||||
]
|
||||
)
|
||||
|
||||
res = f"""Database type: {"in-memory" if self.inmemory else "SQLite3 file"}
|
||||
|
||||
|
@ -149,9 +152,7 @@ class MarkovLite(Markov):
|
|||
- {total} total words
|
||||
"""
|
||||
|
||||
return res
|
||||
|
||||
|
||||
return res
|
||||
|
||||
def create_connection(self, db_file: str) -> Connection | None:
|
||||
"""create a database connection to a SQLite database"""
|
||||
|
@ -163,6 +164,7 @@ class MarkovLite(Markov):
|
|||
conn = sqlite3.connect(db_file)
|
||||
except Error as e:
|
||||
log.error(e)
|
||||
return None
|
||||
finally:
|
||||
return conn
|
||||
|
||||
|
@ -193,6 +195,7 @@ class MarkovLite(Markov):
|
|||
log.error(e)
|
||||
|
||||
def insert_words(self, cursor: Cursor, word_pair: Tuple[str, str], grp: int):
|
||||
"""Insert a pair of words into the database."""
|
||||
|
||||
log.debug(f"Inserting words {word_pair} for group with id {grp}")
|
||||
|
||||
|
@ -204,7 +207,8 @@ class MarkovLite(Markov):
|
|||
|
||||
cursor.execute(sql, parameters)
|
||||
|
||||
def get_worddict(self, grp: int):
|
||||
def get_worddict(self, grp: int) -> Dict[str | None, List[Tuple[str, int]]]:
|
||||
"""Get from the database a dictionary of words."""
|
||||
|
||||
cursor = self.conn.cursor()
|
||||
|
||||
|
|
Loading…
Reference in New Issue