Browse Source

Initial commit

keep-around/a37000e9a7eea5a7aaaee509a40723ad7a382153
Adler Neves 1 year ago
commit
a37000e9a7
20 changed files with 718 additions and 0 deletions
  1. +13
    -0
      .gitignore
  2. +4
    -0
      .gitlab-ci.yml
  3. +44
    -0
      Makefile
  4. +17
    -0
      README.md
  5. +11
    -0
      manage.py
  6. +9
    -0
      propergrammar/__init__.py
  7. +9
    -0
      propergrammar/__main__.py
  8. +13
    -0
      propergrammar/djangosetup.py
  9. +47
    -0
      propergrammar/migrations/0001_initial.py
  10. +0
    -0
      propergrammar/migrations/__init__.py
  11. +66
    -0
      propergrammar/models.py
  12. +42
    -0
      propergrammar/mwt.py
  13. +19
    -0
      propergrammar/settings.py
  14. +308
    -0
      propergrammar/telebot.py
  15. +14
    -0
      requirements.frozen.txt
  16. +3
    -0
      requirements.txt
  17. +1
    -0
      spelling_libreoffice_colibri_cc0.svg
  18. BIN
      spelling_libreoffice_colibri_cc0.svg.png
  19. +85
    -0
      spelling_libreoffice_colibri_cc0_48.svg
  20. +13
    -0
      telegram-bot-use-proper-grammar.service

+ 13
- 0
.gitignore View File

@ -0,0 +1,13 @@
**/*.pyc
**/__pycache__
**/__pycache__/**
virtual_env
virtual_env/**
.vscode
.vscode/**
.idea
.idea/**
.atom
.atom/**
/telegrambot.txt
/db.sqlite3

+ 4
- 0
.gitlab-ci.yml View File

@ -0,0 +1,4 @@
deploy:
stage: deploy
script:
make deploy

+ 44
- 0
Makefile View File

@ -0,0 +1,44 @@
help:
@echo -n
deploy:
-sudo systemctl stop telegram-bot-use-proper-grammar.service
-sudo rm /etc/systemd/system/telegram-bot-use-proper-grammar.service
-sudo install telegram-bot-use-proper-grammar.service /etc/systemd/system
-sudo mkdir -p /var/www/telegram-bot-use-proper-grammar
-sudo rm -rf /var/www/telegram-bot-use-proper-grammar/propergrammar
-sudo rm -rf /var/www/telegram-bot-use-proper-grammar/Makefile
-sudo rm -rf /var/www/telegram-bot-use-proper-grammar/manage.py
-sudo cp -R propergrammar /var/www/telegram-bot-use-proper-grammar/propergrammar
-sudo install manage.py /var/www/telegram-bot-use-proper-grammar
-sudo install Makefile /var/www/telegram-bot-use-proper-grammar
-sudo install requirements.frozen.txt /var/www/telegram-bot-use-proper-grammar
sudo make depends -C /var/www/telegram-bot-use-proper-grammar
sudo make migrate -C /var/www/telegram-bot-use-proper-grammar
cd /var/www/telegram-bot-use-proper-grammar; sudo chown http:http -R .
sudo systemctl daemon-reload
sudo systemctl enable telegram-bot-use-proper-grammar.service
sudo systemctl restart telegram-bot-use-proper-grammar.service
devmigrate: virtual_env
. virtual_env/bin/activate; python manage.py makemigrations
. virtual_env/bin/activate; python manage.py migrate
migrate: virtual_env
. virtual_env/bin/activate; python manage.py migrate
serve: virtual_env
. virtual_env/bin/activate; python -m propergrammar
depends: virtual_env
. virtual_env/bin/activate; pip install -U -r requirements.frozen.txt
depends-latest: virtual_env
. virtual_env/bin/activate; pip install -U -r requirements.txt
freeze: virtual_env
. virtual_env/bin/activate; python -m pip freeze > requirements.frozen.txt
virtual_env:
python3 -m virtualenv virtual_env
make depends

+ 17
- 0
README.md View File

@ -0,0 +1,17 @@
Telegram - Use Proper Grammar, Please!
===========================
Sometimes we need a bot that repeats a set of messages over and over... even more than the frequency you're willing to copy-paste.
Because of those people who need to be reminded more than you're able to remind them, this bot exists.
## Running locally
- Create a bot
- Create the file `telegrambot.txt` containing the API key
- Run `make serve` on a terminal
## Deploying
- Copy `telegrambot.txt` to `/var/www/telegram-bot-use-proper-grammar`
- Run `make deploy`

+ 11
- 0
manage.py View File

@ -0,0 +1,11 @@
#!/usr/bin/env python3
# -*- encoding: utf-8 -*-
import sys
from propergrammar.djangosetup import setup_django
if __name__ == '__main__':
setup_django()
from django.core.management import execute_from_command_line
execute_from_command_line(sys.argv)

+ 9
- 0
propergrammar/__init__.py View File

@ -0,0 +1,9 @@
#!/usr/bin/env python3
# -*- encoding: utf-8 -*-
from pathlib import Path
from .telebot import start_bot
def main():
start_bot(Path('telegrambot.txt').read_text().strip())

+ 9
- 0
propergrammar/__main__.py View File

@ -0,0 +1,9 @@
#!/usr/bin/env python3
# -*- encoding: utf-8 -*-
from . import main
from .djangosetup import setup_django
if __name__ == "__main__":
setup_django()
main()

+ 13
- 0
propergrammar/djangosetup.py View File

@ -0,0 +1,13 @@
#!/usr/bin/env python3
# -*- encoding: utf-8 -*-
import os, sys
def setup_django():
# Setup environ
sys.path.append(os.getcwd())
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "propergrammar.settings")
# Setup django
import django
django.setup()

+ 47
- 0
propergrammar/migrations/0001_initial.py View File

@ -0,0 +1,47 @@
# Generated by Django 3.0.3 on 2020-02-15 23:15
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Group',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('chat_id', models.IntegerField(default=0, unique=True)),
('name', models.CharField(default='', max_length=255)),
],
),
migrations.CreateModel(
name='GroupDictionaryEntry',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('word', models.TextField(default='')),
('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='words', to='propergrammar.Group')),
],
),
migrations.AddIndex(
model_name='group',
index=models.Index(fields=['id'], name='propergramm_id_f9b41c_idx'),
),
migrations.AddIndex(
model_name='group',
index=models.Index(fields=['chat_id'], name='propergramm_chat_id_c6909f_idx'),
),
migrations.AddIndex(
model_name='groupdictionaryentry',
index=models.Index(fields=['id'], name='propergramm_id_1fdb42_idx'),
),
migrations.AddIndex(
model_name='groupdictionaryentry',
index=models.Index(fields=['group_id'], name='propergramm_group_i_fd6c71_idx'),
),
]

+ 0
- 0
propergrammar/migrations/__init__.py View File


+ 66
- 0
propergrammar/models.py View File

@ -0,0 +1,66 @@
#!/usr/bin/env python3
# -*- encoding: utf-8 -*-
from django.db import models
VALID_LANGUAGES_LST = 'en,es,de,fr,pt'.split(',')
VALID_LANGUAGES = tuple(zip(
*(VALID_LANGUAGES_LST, 'English,Español,Deutsch,Français,Português'.split(','))
))
VALID_LANGUAGES_DICT = dict(VALID_LANGUAGES)
LANGUAGES_SCOLDING = '''
I guessed you were writing in "{0}" and I have the following suggestions:
Supuse que estabas escribiendo en "{0}" y tengo las siguientes sugerencias:
Ich vermute, Sie haben auf "{0}" geschrieben und ich habe die folgenden Vorschläge:
J'ai deviné que vous écriviez en "{0}" et j'ai les suggestions suivantes:
Imaginei que você estivesse escrevendo em "{0}" e tenho as seguintes sugestões:'''.splitlines()[1:]
LANGUAGES_SCOLDING_DICT = dict(tuple(zip(
*(VALID_LANGUAGES_LST, LANGUAGES_SCOLDING)
)))
class Group(models.Model):
chat_id = models.IntegerField(
default=0,
blank=False,
null=False,
unique=True
)
name = models.CharField(
default="",
blank=False,
null=False,
max_length=255
)
def __str__(self):
return f'{self.pk} - {self.name}'
class Meta:
indexes = [
models.Index(fields=['id']),
models.Index(fields=['chat_id']),
]
class GroupDictionaryEntry(models.Model):
group = models.ForeignKey(
Group,
on_delete=models.CASCADE,
related_name='words'
)
word = models.TextField(
default="",
blank=False,
null=False
)
def __str__(self):
return f'{self.pk} - {self.word}'
class Meta:
indexes = [
models.Index(fields=['id']),
models.Index(fields=['group_id']),
]

+ 42
- 0
propergrammar/mwt.py View File

@ -0,0 +1,42 @@
#!/usr/bin/env python3
# -*- encoding: utf-8 -*-
import time
class MWT(object):
"""Memoize With Timeout"""
_caches = {}
_timeouts = {}
def __init__(self, timeout=2):
self.timeout = timeout
def collect(self):
"""Clear cache of results which have timed out"""
for func in self._caches:
cache = {}
for key in self._caches[func]:
if (time.time() - self._caches[func][key][1]) < self._timeouts[func]:
cache[key] = self._caches[func][key]
self._caches[func] = cache
def __call__(self, f):
self.cache = self._caches[f] = {}
self._timeouts[f] = self.timeout
def func(*args, **kwargs):
kw = sorted(kwargs.items())
key = (args, tuple(kw))
try:
v = self.cache[key]
# print("cache")
if (time.time() - v[1]) > self.timeout:
raise KeyError
except KeyError:
# print("new")
v = self.cache[key] = f(*args, **kwargs), time.time()
return v[0]
func.func_name = f.__name__
return func

+ 19
- 0
propergrammar/settings.py View File

@ -0,0 +1,19 @@
#!/usr/bin/env python3
# -*- encoding: utf-8 -*-
SECRET_KEY = "plz, stop complaining"
INSTALLED_APPS = [
'propergrammar'
]
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': 'db.sqlite3',
}
}
USE_TZ = True
TIME_ZONE = "UTC"

+ 308
- 0
propergrammar/telebot.py View File

@ -0,0 +1,308 @@
#!/usr/bin/env python3
# -*- encoding: utf-8 -*-
from .mwt import MWT
import sys
import telegram
import telegram.ext
from telegram.ext import Updater
from telegram.ext import CommandHandler
from telegram.ext import MessageHandler
from telegram.ext.filters import Filters
from spellchecker import SpellChecker
import logging
from django.utils import timezone
import random
import datetime
from io import BytesIO, StringIO
from telegram.constants import MAX_MESSAGE_LENGTH
CHECKERS = dict()
Group: 'propergrammar.models.Group' = None
GroupDictionaryEntry: 'propergrammar.models.GroupDictionaryEntry' = None
logger = None
models = None
@MWT(timeout=60*5)
def get_admin_ids(bot, chat_id):
"""Returns a list of admin IDs for a given chat. Results are cached for 1 hour."""
return [admin.user.id for admin in bot.get_chat_administrators(chat_id)]
def start(update: telegram.Update, context: telegram.ext.CallbackContext):
chattype = update.message.chat.type
if chattype in ['private']:
context.bot.send_message(
chat_id=update.message.chat_id,
text="Invite me to your server!"
)
elif chattype in ['group', 'supergroup']:
admins = get_admin_ids(context.bot, update.message.chat_id)
if update.message.from_user.id in admins:
is_group_admin(update, context)
send_help_message(update, context)
def send_help_message(update: telegram.Update, context: telegram.ext.CallbackContext):
context.bot.send_message(
chat_id=update.message.chat_id,
text='''Try using my services with the commands:
/add_group_dictionary
/show_group_dictionary
/remove_group_dictionary
/export_group_dictionary
(there is a clear_group_dictionary, but you'll have to type it yourself)'''
)
def split_long_messages(message: str) -> list:
if len(message) <= MAX_MESSAGE_LENGTH:
return [message]
else:
lst = []
lines = message.splitlines()
size = 0
buffer = ''
for line in lines:
if len(buffer) + len(line) + 1 <= MAX_MESSAGE_LENGTH:
buffer += line + '\n'
else:
lst.append(buffer)
buffer = ''
if len(line) + 1 <= MAX_MESSAGE_LENGTH:
buffer += line + '\n'
else:
remainder = line
while len(remainder) + 1 >= MAX_MESSAGE_LENGTH:
humongous, remainder = (
remainder[:MAX_MESSAGE_LENGTH], remainder[MAX_MESSAGE_LENGTH:])
lst.append(humongous)
lst.append(remainder)
buffer = ''
if len(buffer) > 0:
lst.append(buffer)
return lst
def get_group(chat):
group = Group.objects.filter(chat_id=chat.id).first()
title = chat.title
if title is None:
title = f'{chat.first_name} {chat.last_name}'
if group is None:
group = Group(chat_id=chat.id, name=title)
group.save()
elif group.name != title:
group.name = title
group.save()
return group
def cmd_null(update: telegram.Update, context: telegram.ext.CallbackContext):
return
def remove_links(text, entities):
if isinstance(text, str):
text = list(text)
for entity in entities:
if entity.type in ['url', 'email', 'bot_command']:
for i in range(entity.offset, entity.offset+entity.length):
text[i] = ''
return ''.join(text)
else:
return text
def handle_message(update: telegram.Update, context: telegram.ext.CallbackContext):
chattype = update.message.chat.type
if chattype in ['group', 'supergroup', 'private']:
to_check = ' '.join(
list(filter(lambda x: x is not None, [
remove_links(update.message.text, update.message.entities),
remove_links(update.message.caption,
update.message.caption_entities)
]))
).strip()
if len(to_check) > 0:
group = get_group(update.message.chat)
languages = models.VALID_LANGUAGES_LST
ignored = [x.word for x in group.words.all()]
language_rank = list()
for language in languages:
chk = CHECKERS[language]
words = chk.split_words(to_check)
if len(words) > 0:
language_rank.append(
(len(chk.known(words))/len(words), language)
)
language_rank.sort()
language_rank.reverse()
language_confidence, language_main = language_rank[0]
if language_confidence == 0:
language_main = 'en'
checker_main = CHECKERS[language_main]
words = checker_main.split_words(to_check)
unknown = checker_main.unknown(words)
for lng in languages:
unknown = CHECKERS[lng].unknown(unknown)
unknown = set(unknown)
for unknown_word in list(unknown):
for ignored_word in ignored:
if unknown_word.lower() == ignored_word.lower():
unknown.difference_update(unknown_word)
unknown = sorted(list(unknown))
formatted_suggestions = []
for typo in unknown:
typo_fixed = checker_main.correction(typo)
typo_fixes = checker_main.candidates(typo)
typo_fixes -= {typo_fixed}
formatted_suggestions.append(
f'{typo} → {typo_fixed}; {", ".join(typo_fixes)}'
)
if len(unknown) > 0:
lecture = models.LANGUAGES_SCOLDING_DICT[language_main].format(
models.VALID_LANGUAGES_DICT[language_main]
)+'\n'+"\n".join(formatted_suggestions)
for message_segments in split_long_messages(lecture):
context.bot.send_message(
chat_id=update.message.chat_id,
reply_to_message_id=update.message.message_id,
text=message_segments
)
def cmd_agd(update: telegram.Update, context: telegram.ext.CallbackContext):
chattype = update.message.chat.type
if chattype in ['group', 'supergroup']:
admins = get_admin_ids(context.bot, update.message.chat_id)
if update.message.from_user.id in admins:
group = get_group(update.message.chat)
wordlist = sorted(
list(set([w.word.lower() for w in group.words.all()])))
new_words = ' '.join(remove_links(
update.message.text, update.message.entities).splitlines()).split()
status = f'Adding {len(new_words)} new words...\n'
for nword in new_words:
status += f'{nword}... '
if nword.lower() in wordlist:
status += 'already on list\n'
else:
GroupDictionaryEntry(group=group, word=nword).save()
wordlist.append(nword.lower())
status += 'OK\n'
status += f'New word count: {len(group.words.all())}'
for segment in split_long_messages(status):
context.bot.send_message(
chat_id=update.message.chat_id,
reply_to_message_id=update.message.message_id,
text=segment
)
def cmd_rgd(update: telegram.Update, context: telegram.ext.CallbackContext):
chattype = update.message.chat.type
if chattype in ['group', 'supergroup']:
admins = get_admin_ids(context.bot, update.message.chat_id)
if update.message.from_user.id in admins:
group = get_group(update.message.chat)
old_words = ' '.join(remove_links(
update.message.text, update.message.entities).splitlines()).split()
status = f'Removing {len(old_words)} words...\n'
for oword in old_words:
status += f'{oword}... '
entry = group.words.all().filter(word__iexact=oword).first()
if entry is None:
status += 'not found\n'
else:
entry.delete()
status += 'REMOVED\n'
status += f'New word count: {len(group.words.all())}'
for segment in split_long_messages(status):
context.bot.send_message(
chat_id=update.message.chat_id,
reply_to_message_id=update.message.message_id,
text=segment
)
def cmd_sgd(update: telegram.Update, context: telegram.ext.CallbackContext):
chattype = update.message.chat.type
if chattype in ['group', 'supergroup']:
admins = get_admin_ids(context.bot, update.message.chat_id)
if update.message.from_user.id in admins:
group = get_group(update.message.chat)
wordlist = sorted([w.word for w in group.words.all()])
wordlist_str = '\n'.join(wordlist)
msg = 'Here are all your %d entries you have on your group\'s dictionary:\n%s' % (
len(wordlist), wordlist_str
)
for fragment in split_long_messages(msg):
context.bot.send_message(
chat_id=update.message.chat_id,
reply_to_message_id=update.message.message_id,
text=fragment
)
def cmd_egd(update: telegram.Update, context: telegram.ext.CallbackContext):
chattype = update.message.chat.type
if chattype in ['group', 'supergroup']:
admins = get_admin_ids(context.bot, update.message.chat_id)
if update.message.from_user.id in admins:
group = get_group(update.message.chat)
wordlist = sorted([w.word for w in group.words.all()])
wordlist_str = '\n'.join(wordlist)
wordlist_str += '\n'
bio = BytesIO(wordlist_str.encode('UTF-8'))
bio.name = f'DictExport_{update.message.chat_id}_{str(update.message.date).replace(" ", "_").replace(":", "-")}.txt'
context.bot.send_document(
chat_id=update.message.chat_id,
reply_to_message_id=update.message.message_id,
document=bio
)
def cmd_cgd(update: telegram.Update, context: telegram.ext.CallbackContext):
chattype = update.message.chat.type
if chattype in ['group', 'supergroup']:
admins = get_admin_ids(context.bot, update.message.chat_id)
if update.message.from_user.id in admins:
group = get_group(update.message.chat)
group.words.all().delete()
context.bot.send_message(
chat_id=update.message.chat_id,
reply_to_message_id=update.message.message_id,
text='Erased all entries from local dictionary.'
)
def start_bot(token):
global Group
global GroupDictionaryEntry
global logger
global CHECKERS
global models
from .models import Group
from .models import GroupDictionaryEntry
from . import models
for lng in models.VALID_LANGUAGES_LST:
CHECKERS[lng] = SpellChecker(lng)
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger()
logger.setLevel(logging.INFO)
u = Updater(token, use_context=True)
d = u.dispatcher
d.add_handler(CommandHandler('start', start))
d.add_handler(CommandHandler('add_group_dictionary', cmd_agd))
d.add_handler(CommandHandler('remove_group_dictionary', cmd_rgd))
d.add_handler(CommandHandler('show_group_dictionary', cmd_sgd))
d.add_handler(CommandHandler('export_group_dictionary', cmd_egd))
d.add_handler(CommandHandler('clear_group_dictionary', cmd_cgd))
d.add_handler(CommandHandler('help', send_help_message))
d.add_handler(MessageHandler(Filters.all, handle_message))
u.start_polling()

+ 14
- 0
requirements.frozen.txt View File

@ -0,0 +1,14 @@
asgiref==3.2.3
certifi==2019.11.28
cffi==1.14.0
cryptography==2.8
decorator==4.4.1
Django==3.0.3
future==0.18.2
pycparser==2.19
pyspellchecker==0.5.3
python-telegram-bot==12.4.2
pytz==2019.3
six==1.14.0
sqlparse==0.3.0
tornado==6.0.3

+ 3
- 0
requirements.txt View File

@ -0,0 +1,3 @@
django
python-telegram-bot
pyspellchecker

+ 1
- 0
spelling_libreoffice_colibri_cc0.svg View File

@ -0,0 +1 @@
<svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"><g fill="#696969" transform="matrix(1.1845524 0 0 1.1845524 1.815447 3.785371)"><path d="m10.000001 12h-1.4708747q-.2548545 0-.4077671-.120482-.1529126-.128011-.2257281-.316265l-.6116504-1.9653612h-3.5606793l-.604369 1.9578312q-.058253.165662-.2257281.308735-.1601941.135542-.4004854.135542h-1.4927183l3.5242715-10h1.9514562zm-5.847088-3.7876504h2.6941745l-.9830097-3.1626507q-.0873786-.2334337-.1820388-.5421687-.0946602-.3162652-.1893203-.6852411-.0873787.3689759-.1820388.6852411-.0946603.3162651-.1747574.5496987z"/><path d="m11.000003 11.896693v-9.896693h1.67105v3.8636362q.381578-.4132232.848682-.661157.467105-.2548209 1.052631-.2548209.55921 0 1.006577.2341597.44737.2341598.763158.6749311.315789.4338843.486841 1.053719.171053.6198347.171053 1.3911845 0 .8333333-.197369 1.5151514-.197368.681818-.559209 1.170798-.361842.482094-.86842.750689-.5.261708-1.118419.261708-.302632 0-.546052-.06198-.236842-.06198-.440789-.172176-.203947-.117079-.375001-.275482-.164473-.165289-.322367-.365013l-.07237.440771q-.0329.18595-.131579.261708-.09868.06887-.256579.06887h-1.111841zm2.993417-5.5853984q-.421053 0-.730263.1997244-.30921.1928376-.592104.5647383v3.0165287q.249999.323691.539473.454545.289473.130854.618419.130854.328948 0 .598684-.123966.269738-.130855.453948-.406337.190788-.2823686.289473-.7162529.105263-.4407713.105263-1.060606 0-.5509641-.08553-.9366391-.085523-.3925619-.249996-.6404958-.157894-.2479338-.401315-.3650138-.236843-.1170798-.546052-.1170798z"/><path d="m20.867188 5c-.469484 0-.881719.0898437-1.240235.2675781-.354246.1777345-.653206.4238281-.896484.7382813-.239011.309896-.421149.675781-.544922 1.0996094-.123773.4238283-.185547.8841143-.185547 1.3808593 0 .5559898.07009 1.0527342.210938 1.4902344.140845.4329425.331302.8007815.570312 1.1015625 1.357041 1.83064 3.119404.584038 4.17696-.147974l-.234157-1.0763632c-1.258632.4420062-1.788605 1.2671802-2.720146.2653532-.226207-.382813-.339845-.9264324-.339845-1.6328129 0-.3326824.0269-.6334634.07813-.9023437.05548-.2688804.135486-.4967447.242187-.6835938.106701-.186849.242112-.3313801.404297-.4316406.166453-.1002605.360093-.1503906.582031-.1503906.179257 0 .32617.026041.441406.076172.119506.05013.22128.1061199.306641.1699219.08963.059244.169993.1139319.238281.1640625.06829.050131.141563.076172.222657.076172.08535 0 .152265-.018229.199218-.054687.04694-.041016.09367-.094401.140625-.1582031l.429684-.6210946c-.268886-.3144533-.572982-.5527343-.910157-.7167969-.337174-.1686198-.727999-.2539062-1.171874-.2539062z"/></g><path d="m30.008379 19.015652c-.11263-.02069-.229582-.02189-.349814.0022-.256226.05414-.492352.195183-.658476.393444l-7.352978 8.22655-2.633903-2.575268c-.440541-.430791-1.242149-.430757-1.682772 0-.440564.430768-.440599 1.214497 0 1.645311l3.511871 3.433689.91455.858422.804804-.929957 8.194364-9.156507c.581205-.618654.04074-1.75309-.747646-1.897919z" fill="#76a797"/></svg>

BIN
spelling_libreoffice_colibri_cc0.svg.png View File

Before After
Width: 480  |  Height: 480  |  Size: 13 KiB

+ 85
- 0
spelling_libreoffice_colibri_cc0_48.svg View File

@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
viewBox="0 0 48 48"
version="1.1"
id="svg12"
sodipodi:docname="spelling_libreoffice_colibri_cc0_48.svg"
width="48"
height="48"
inkscape:version="0.92.4 5da689c313, 2019-01-14"
inkscape:export-filename="/home/sfner/Documents/telegram_bots/use_proper_grammar/spelling_libreoffice_colibri_cc0.svg.png"
inkscape:export-xdpi="960"
inkscape:export-ydpi="960">
<metadata
id="metadata18">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs16" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1027"
id="namedview14"
showgrid="false"
inkscape:zoom="10.429825"
inkscape:cx="16.414963"
inkscape:cy="11.338944"
inkscape:window-x="0"
inkscape:window-y="27"
inkscape:window-maximized="1"
inkscape:current-layer="svg12" />
<rect
style="fill:#ffffff;fill-opacity:0.97875815;fill-rule:nonzero;stroke:none;stroke-width:10;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect847"
width="48"
height="48"
x="0"
y="0"
inkscape:export-xdpi="960"
inkscape:export-ydpi="960" />
<g
transform="matrix(1.1845524,0,0,1.1845524,9.8154469,11.785371)"
id="g8"
style="fill:#696969">
<path
d="M 10.000001,12 H 8.5291263 Q 8.2742718,12 8.1213592,11.879518 7.9684466,11.751507 7.8956311,11.563253 L 7.2839807,9.5978918 H 3.7233014 L 3.1189324,11.555723 Q 3.0606794,11.721385 2.8932043,11.864458 2.7330102,12 2.4927189,12 H 1.0000006 L 4.5242721,2 H 6.4757283 Z M 4.152913,8.2123496 H 6.8470875 L 5.8640778,5.0496989 Q 5.7766992,4.8162652 5.682039,4.5075302 5.5873788,4.191265 5.4927187,3.8222891 5.40534,4.191265 5.3106799,4.5075302 5.2160196,4.8237953 5.1359225,5.0572289 Z"
id="path2"
inkscape:connector-curvature="0" />
<path
d="M 11.000003,11.896693 V 2 h 1.67105 v 3.8636362 q 0.381578,-0.4132232 0.848682,-0.661157 0.467105,-0.2548209 1.052631,-0.2548209 0.55921,0 1.006577,0.2341597 0.44737,0.2341598 0.763158,0.6749311 0.315789,0.4338843 0.486841,1.053719 0.171053,0.6198347 0.171053,1.3911845 0,0.8333333 -0.197369,1.5151514 -0.197368,0.681818 -0.559209,1.170798 -0.361842,0.482094 -0.86842,0.750689 -0.5,0.261708 -1.118419,0.261708 -0.302632,0 -0.546052,-0.06198 -0.236842,-0.06198 -0.440789,-0.172176 -0.203947,-0.117079 -0.375001,-0.275482 -0.164473,-0.165289 -0.322367,-0.365013 l -0.07237,0.440771 q -0.0329,0.18595 -0.131579,0.261708 -0.09868,0.06887 -0.256579,0.06887 H 11 Z M 13.99342,6.3112946 q -0.421053,0 -0.730263,0.1997244 -0.30921,0.1928376 -0.592104,0.5647383 v 3.0165287 q 0.249999,0.323691 0.539473,0.454545 0.289473,0.130854 0.618419,0.130854 0.328948,0 0.598684,-0.123966 0.269738,-0.130855 0.453948,-0.406337 0.190788,-0.2823686 0.289473,-0.7162529 0.105263,-0.4407713 0.105263,-1.060606 0,-0.5509641 -0.08553,-0.9366391 Q 15.10526,7.0413221 14.940787,6.7933882 14.782893,6.5454544 14.539472,6.4283744 14.302629,6.3112946 13.99342,6.3112946 Z"
id="path4"
inkscape:connector-curvature="0" />
<path
d="M 20.867188,5 C 20.397704,5 19.985469,5.0898437 19.626953,5.2675781 19.272707,5.4453126 18.973747,5.6914062 18.730469,6.0058594 18.491458,6.3157554 18.30932,6.6816404 18.185547,7.1054688 18.061774,7.5292971 18,7.9895831 18,8.4863281 c 0,0.5559898 0.07009,1.0527342 0.210938,1.4902344 0.140845,0.4329425 0.331302,0.8007815 0.570312,1.1015625 1.357041,1.83064 3.119404,0.584038 4.17696,-0.147974 L 22.724053,9.8537878 C 21.465421,10.295794 20.935448,11.120968 20.003907,10.119141 19.7777,9.736328 19.664062,9.1927086 19.664062,8.4863281 c 0,-0.3326824 0.0269,-0.6334634 0.07813,-0.9023437 0.05548,-0.2688804 0.135486,-0.4967447 0.242187,-0.6835938 0.106701,-0.186849 0.242112,-0.3313801 0.404297,-0.4316406 0.166453,-0.1002605 0.360093,-0.1503906 0.582031,-0.1503906 0.179257,0 0.32617,0.026041 0.441406,0.076172 0.119506,0.05013 0.22128,0.1061199 0.306641,0.1699219 0.08963,0.059244 0.169993,0.1139319 0.238281,0.1640625 0.06829,0.050131 0.141563,0.076172 0.222657,0.076172 0.08535,0 0.152265,-0.018229 0.199218,-0.054687 0.04694,-0.041016 0.09367,-0.094401 0.140625,-0.1582031 L 22.949219,5.9707031 C 22.680333,5.6562498 22.376237,5.4179688 22.039062,5.2539062 21.701888,5.0852864 21.311063,5 20.867188,5 Z"
id="path6"
inkscape:connector-curvature="0" />
</g>
<path
d="m 38.008379,27.015652 c -0.11263,-0.02069 -0.229582,-0.02189 -0.349814,0.0022 -0.256226,0.05414 -0.492352,0.195183 -0.658476,0.393444 l -7.352978,8.22655 -2.633903,-2.575268 c -0.440541,-0.430791 -1.242149,-0.430757 -1.682772,0 -0.440564,0.430768 -0.440599,1.214497 0,1.645311 l 3.511871,3.433689 0.91455,0.858422 0.804804,-0.929957 8.194364,-9.156507 c 0.581205,-0.618654 0.04074,-1.75309 -0.747646,-1.897919 z"
id="path10"
inkscape:connector-curvature="0"
style="fill:#76a797" />
</svg>

+ 13
- 0
telegram-bot-use-proper-grammar.service View File

@ -0,0 +1,13 @@
[Unit]
Description=Use Proper Grammar Bot telegram service
After=network.target
[Service]
User=http
Group=http
WorkingDirectory=/var/www/telegram_use_proper_grammar
ExecStart=/usr/bin/make serve
KillSignal=SIGINT
[Install]
WantedBy=multi-user.target

Loading…
Cancel
Save