vastly improved documentation

This commit is contained in:
Raphael Jacobs 2022-02-10 06:27:49 +01:00
parent 585ac3965c
commit 00121705f8
3 changed files with 78 additions and 31 deletions

View File

@ -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
View File

@ -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)

View File

@ -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()