Browse Source

Removed deprecated files

master
Giacomo Ferretti 3 months ago
parent
commit
f044cbe3d0
No account linked to committer's email address

+ 0
- 18
.docker/mcdapibot-Dockerfile View File

@@ -1,18 +0,0 @@
# Grab latest Apline Linux
FROM alpine:latest
LABEL maintainer="me@hexile.xyz"

COPY requirements.txt /

RUN apk add --no-cache --virtual persistent python3 libjpeg-turbo-dev \
&& apk add --no-cache --virtual build python3-dev gcc musl-dev libffi-dev openssl-dev libjpeg-turbo-dev zlib-dev \
&& pip3 install -r /requirements.txt && rm -rf /root/.cache && apk del build

# Fix for timezone
RUN apk add tzdata
RUN cp /usr/share/zoneinfo/Europe/Rome /etc/localtime

COPY . /app
WORKDIR /app

CMD ["python3", "main.py"]

+ 0
- 33
.docker/torproxy-Dockerfile View File

@@ -1,33 +0,0 @@
# Grab latest Apline Linux
FROM alpine:latest
LABEL maintainer="me@hexile.xyz"

# Arguments
ARG socks_port
ARG control_password
ARG control_port

# Install Tor
RUN apk add tor --no-cache --repository http://dl-cdn.alpinelinux.org/alpine/edge/community/
RUN tor --version
EXPOSE $socks_port
# COPY torrc.default /etc/tor/torrc.default
RUN chown -R tor /etc/tor
USER tor

# Generate config
RUN if [ ! -z "$control_password" ] && [ ! -z "$control_port" ]; \
then echo -e "\n"; \
echo "SocksPort 0.0.0.0:$socks_port" >> /etc/tor/torrc.default; \
echo "Your password is: $control_password"; \
echo "HashedControlPassword $(tor --hash-password "$control_password")" >> /etc/tor/torrc.default; \
echo "Your port is: $control_port"; \
echo "ControlPort $control_port" >> /etc/tor/torrc.default; \
echo -e "\n"; \
fi

# Read config
RUN cat /etc/tor/torrc.default

ENTRYPOINT [ "tor" ]
CMD [ "-f", "/etc/tor/torrc.default" ]

+ 0
- 30
.dockerignore View File

@@ -1,30 +0,0 @@
# Byte-compiled / optimized / DLL files
__pycache__/

# Environments
venv/

# Distribution / packaging
build/
dist/
*.egg-info/

# IntelliJ
.idea/

# Docker files
docker-compose.yml
.docker

# Git
.git

# GitHub
.github

# Custom
config.json
users.json
offers.json
templates/
logs/

+ 0
- 24
.github/ISSUE_TEMPLATE/bug_report.md View File

@@ -1,24 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: giacomoferretti

---

**Describe the bug**
A clear and concise description of what the bug is.

**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error

**Expected behavior**
A clear and concise description of what you expected to happen.

**Additional context**
Add any other context about the problem here.

+ 0
- 20
.github/ISSUE_TEMPLATE/feature_request.md View File

@@ -1,20 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: giacomoferretti

---

**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.

**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.

**Additional context**
Add any other context or screenshots about the feature request here.

BIN
.github/header.png View File


+ 0
- 23
CHANGELOG.md View File

@@ -1,23 +0,0 @@
## 0.2.0

### New
**config.json**
* Added the ability to go through a proxy to create coupons

**admin.py**
* A collection of admin commands
* `/broadcast` - It let's you send a message to all active users
* `/send [id] [message]` - It let's you send a message to a specific active user

### Fixes
**General Fixes**
* General optimization and better performance

**config.py**
* Added check for `offers.json` not found

**coupon.py**
* Added check for empty `customTitle`

## 0.1.0
* First public release

+ 0
- 201
LICENSE View File

@@ -1,201 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/

TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

1. Definitions.

"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.

"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.

"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.

"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.

"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.

"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.

"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).

"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.

"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."

"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.

2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.

3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.

4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:

(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and

(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and

(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and

(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.

You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.

5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.

6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.

7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.

8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.

9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.

END OF TERMS AND CONDITIONS

APPENDIX: How to apply the Apache License to your work.

To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.

Copyright 2019 Giacomo Ferretti

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

+ 0
- 62
README.md View File

@@ -1,62 +0,0 @@
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/72f3eec1c1a3426d82c979397577e8ac)](https://app.codacy.com/app/giacomoferretti/mcdapi-telegram-bot?utm_source=github.com&utm_medium=referral&utm_content=giacomoferretti/mcdapi-telegram-bot&utm_campaign=Badge_Grade_Dashboard)
![GitHub tag (latest SemVer)](https://img.shields.io/github/tag/giacomoferretti/mcdapi-telegram-bot.svg?color=blue&label=Stable)
![GitHub tag (latest SemVer pre-release)](https://img.shields.io/github/tag-pre/giacomoferretti/mcdapi-telegram-bot.svg?label=Testing)
[![GitHub license](https://img.shields.io/github/license/giacomoferretti/mcdapi-telegram-bot.svg?color=informational)](https://github.com/giacomoferretti/mcdapi-telegram-bot/blob/master/LICENSE)
[![Donate](https://img.shields.io/badge/Donate-Paypal-blue.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=VN66MND8DYCGE&source=url)

![Header](.github/header.png)

# mcdapi-telegram-bot
`mcdapi-telegram-bot` is a simple bot written in Python that can generate valid coupons and promocodes that you can use at McDonald's.

You can see a running example here: [@mcdapi_bot](https://telegram.me/mcdapi_bot)

## Requirements
* Python 3

## Setting the bot up
Copy or rename `config.base.json` to `config.json` and edit it following this guide:

```text
token: You have to create a bot and get the token from @botfather on Telegram
ownerId: Your Telegram ID (Not necessary)
ownerUsername: Your Telegram username (Not necessary)
adminIds: [id, ...] (Not necessary)
templatesFolder: The name of the folder containing the templates
proxyEnabled: Enables and disables generating coupons through a proxy
proxyUrl: URL of the proxy, if enabled
```

You can download `offers.json` already scraped and parsed with `mcdapi-tools` [here](https://gist.github.com/giacomoferretti/a24797299041692613c155cac79b8127).

## Running the bot
You can run the bot using one of these three methods

### Docker
| Image | Build time (without cache) | Size |
|-----------|----------------------------|--------|
| torproxy | ~13 seconds | ~17MB |
| mcdapibot | ~2 minutes and 30 seconds | ~111MB |

If you have Docker Compose simply run `docker-compose up`.

If not use this commands:
```bash
docker build . -t torproxy -f .docker/torproxy-Dockerfile --build-arg socks_port=9050 --build-arg control_password="password" --build-arg control_port=9051
docker build . -t mcdapibot -f .docker/mcdapibot-Dockerfile

docker run -d --name=torproxy torproxy
docker run -d --name=mcdapibot --link=torproxy -v $(pwd)/images:/app/images -v $(pwd)/logs:/app/logs -v $(pwd)/templates:/app/templates -v $(pwd)/config.json:/app/config.json -v $(pwd)/users.json:/app/users.json -v $(pwd)/offers.json:/app/offers.json mcdapibot
```

### Python 3
```bash
pip3 install -r requirements.txt
python3 main.py
```

## Donate
If this repository helped you in any way, feel free to donate [here](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=VN66MND8DYCGE&source=url).

## Disclaimer
This repository is not affiliated with McDonald's Corp in any way. "McDonald's" is a registered trademark of McDonald's Corp.

+ 0
- 129
commands/admin.py View File

@@ -1,129 +0,0 @@
# -*- coding: utf-8 -*-

# Copyright 2019 Giacomo Ferretti
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import logging
import os
import traceback
from datetime import datetime

import psutil as psutil
from telegram import Update, ParseMode, ChatAction
from telegram.error import Unauthorized, BadRequest
from telegram.ext import CallbackContext, run_async

from commands import base

__broadcast_message__ = 'Reply to this message to send a broadcast'


class AdminManager(base.Command):
name = 'admin'
admins_only = True

def __init__(self, __config__, __users__):
super().__init__(__config__)
self.__users__ = __users__

@run_async
def broadcast(self, update: Update, context: CallbackContext):
if self.can_run(update, context):
context.bot.send_message(chat_id=update.effective_chat.id, text=__broadcast_message__)

@run_async
def send_broadcast(self, update: Update, context: CallbackContext):
if self.can_run(update, context):
__logger__ = logging.getLogger(__name__)

if update.to_dict()['message']['reply_to_message']['text'] == __broadcast_message__ and \
update.to_dict()['message']['reply_to_message']['from']['id'] == context.bot.id:
body = self.__config__.get_template('broadcast.html').format(message=update.message.text,
name=update.effective_user['username'],
id=update.effective_user['id'])

for user in list(self.__users__.get_users()):
try:
context.bot.send_message(chat_id=user, text=body, parse_mode=ParseMode.HTML)
except (Unauthorized, BadRequest):
__logger__.error('Cannot send message to {}. Removing it from list...'.format(user))
self.__users__.remove_user(user)
traceback.print_exc()

context.bot.send_message(chat_id=update.message.chat.id, message_id=update.message.message_id,
text='The message was sent to {} users.'
.format(len(self.__users__.get_users())))

@run_async
def send_message(self, update: Update, context: CallbackContext):
if self.can_run(update, context):
args = update.message.text.split(' ', 2)

if len(args) == 1:
# Show usage
context.bot.send_message(chat_id=update.effective_chat.id, text='Usage: /send [id] [message]')
elif len(args) == 2:
# Error
context.bot.send_message(chat_id=update.effective_chat.id, text='You need to enter a message.')
elif len(args) == 3:
# Send message
try:
id_ = int(update.message.text.split(' ', 2)[1])
message = update.message.text.split(' ', 2)[2]
context.bot.send_message(chat_id=id_, text=message)
context.bot.send_message(chat_id=update.effective_chat.id,
text='The message has been sent to the user')
except ValueError:
context.bot.send_message(chat_id=update.effective_chat.id, text='You need to enter a chat id.')
except (Unauthorized, BadRequest):
context.bot.send_message(chat_id=update.effective_chat.id,
text='There was an error sending the message.')

@run_async
def uptime(self, update: Update, context: CallbackContext):
if self.can_run(update, context):
p = psutil.Process(os.getpid())
uptime = datetime.now() - datetime.fromtimestamp(p.create_time())
hours, remainder = divmod(uptime.seconds, 3600)
minutes, seconds = divmod(remainder, 60)
context.bot.send_message(chat_id=update.effective_chat.id, text='{:02}:{:02}:{:02}'
.format(int(hours), int(minutes), int(seconds)))

@run_async
def users_info(self, update: Update, context: CallbackContext):
if self.can_run(update, context):
body = 'Ci sono <b>{users}</b> utenti attivi.'.format(users=len(self.__users__.to_dict()))
context.bot.send_message(chat_id=update.effective_chat.id, text=body, parse_mode=ParseMode.HTML)

@run_async
def users_list(self, update: Update, context: CallbackContext):
if self.can_run(update, context):
body = ''
for x in self.__users__.to_dict():
user = self.__users__.to_dict()[x]
body += '\n[{id}] {first_name}'.format(id=x, first_name=user['first_name'])
if 'username' in user:
body += ': <a href="tg://user?id={id}">@{username}</a>'.format(id=x, username=user['username'])
context.bot.send_message(chat_id=update.effective_chat.id, text=body, parse_mode=ParseMode.HTML)

@run_async
def check_users(self, update: Update, context: CallbackContext):
if self.can_run(update, context):
for user in list(self.__users__.get_users()):
try:
context.bot.send_chat_action(chat_id=user, action=ChatAction.TYPING)
except Unauthorized:
print('{} blocked the bot.'.format(user))
except BadRequest as e:
print('BadRequest: {}'.format(e.message))

+ 0
- 51
commands/base.py View File

@@ -1,51 +0,0 @@
# -*- coding: utf-8 -*-

# Copyright 2019 Giacomo Ferretti
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from telegram import Update
from telegram.ext import CallbackContext

from utils import config


class Command:
name = ''
admins_only = False
groups_allowed = False
bots_allowed = False

def __init__(self, __config__: config.Config):
self.__config__ = __config__

def check_callback(self, query, command):
return query == '{}_{}'.format(self.name, command)

def can_run(self, update: Update, context: CallbackContext):
if self.__config__.get_maintenance() and not self.__config__.is_admin(update.effective_user['id']):
self.reply_maintenance(update, context)
return False

if self.admins_only and not self.__config__.is_admin(update.effective_user['id']):
return False

if not self.bots_allowed and update.effective_user['is_bot']:
return False

return True

def reply_maintenance(self, update: Update, context: CallbackContext):
context.bot.send_message(chat_id=update.effective_chat.id,
text='\u26d4 Il bot è in manutenzione.\n'
'Manutenzione iniziata {}'.format(self.__config__.get_maintenance_time()))

+ 0
- 388
commands/coupon.py View File

@@ -1,388 +0,0 @@
# -*- coding: utf-8 -*-

# Copyright 2019 Giacomo Ferretti
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import json
import logging
import os
import re
import socket
from datetime import timedelta
from io import BytesIO

import qrcode
import requests
from PIL import Image, ImageDraw
from mcdapi import coupon, endpoints
from telegram import Update, ParseMode, InlineKeyboardButton, InlineKeyboardMarkup, ChatAction
from telegram.error import BadRequest
from telegram.ext import CallbackContext, run_async

from commands import base
from utils import config

__image_folder__ = 'images'


def round_corner(radius, fill):
"""Draw a round corner"""
corner = Image.new('RGBA', (radius, radius), (0, 0, 0, 0))
draw = ImageDraw.Draw(corner)
draw.pieslice((0, 0, radius * 2, radius * 2), 180, 270, fill=fill)
return corner


def round_rectangle(size, radius, fill):
"""Draw a rounded rectangle"""
width, height = size
rectangle = Image.new('RGBA', size, fill)
corner = round_corner(radius, fill)
rectangle.paste(corner, (0, 0))
rectangle.paste(corner.rotate(90), (0, height - radius)) # Rotate the corner and paste it
rectangle.paste(corner.rotate(180), (width - radius, height - radius))
rectangle.paste(corner.rotate(270), (width - radius, 0))
return rectangle


def get_days(l):
days = []
for x in l:
if x == 0:
days.append('Domenica')
elif x == 1:
days.append('Lunedì')
elif x == 2:
days.append('Martedì')
elif x == 3:
days.append('Mercoledì')
elif x == 4:
days.append('Giovedì')
elif x == 5:
days.append('Venerdì')
elif x == 6:
days.append('Sabato')
return ' - '.join(days)


def parse_url(url):
p = '(?:.*://)?(?P<host>[^:/ ]+).?(?P<port>[0-9]*).*'

m = re.search(p, url)
return m.group('host'), int(m.group('port'))


def is_port_open(url):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
s.connect(parse_url(url))
s.shutdown(2)
return True
except ConnectionRefusedError:
return False


def get_headers(config_):
# Get session
session = get_session(config_)

# Generate
android_id = coupon.get_random_device_id()
vmob_uid = coupon.generate_vmob_uid(android_id)
username = coupon.generate_username(android_id)
password = coupon.generate_password(android_id)
plexure = coupon.generate_plexure_api_key(vmob_uid)

headers = coupon.strip_unnecessary_headers(coupon.get_random_headers(vmob_uid, plexure))

r = session.request(endpoints.DEVICE_REGISTRATION['method'], endpoints.DEVICE_REGISTRATION['url'],
data=endpoints.DEVICE_REGISTRATION['body'].format(username=username, password=password),
headers=headers)

if r.status_code == 200:
js = json.loads(r.content)
token = js['access_token']
headers['Authorization'] = 'bearer {}'.format(token)

return headers


def get_session(config_):
__logger__ = logging.getLogger(__name__)

# Get session
session = requests.session()

if is_port_open(config_.get_proxy_url()):
if config_.is_proxy_enabled():
session.proxies = {
'http': config_.get_proxy_url(),
'https': config_.get_proxy_url()
}
else:
__logger__.warning('Cannot connect to proxy.')

return session


def generate_coupon(id_, __config__: config.Config):
__logger__ = logging.getLogger(__name__)

# Get session
session = get_session(__config__)

# Generate
android_id = coupon.get_random_device_id()
vmob_uid = coupon.generate_vmob_uid(android_id)
username = coupon.generate_username(android_id)
password = coupon.generate_password(android_id)
plexure = coupon.generate_plexure_api_key(vmob_uid)

headers = coupon.strip_unnecessary_headers(coupon.get_random_headers(vmob_uid, plexure))

r = session.request(endpoints.DEVICE_REGISTRATION['method'], endpoints.DEVICE_REGISTRATION['url'],
data=endpoints.DEVICE_REGISTRATION['body'].format(username=username, password=password),
headers=headers)

if r.status_code == 200:
js = json.loads(r.content.decode('utf-8'))
token = js['access_token']
headers['Authorization'] = 'bearer {}'.format(token)

r = session.post(endpoints.REDEEM_OFFER['url'], data=endpoints.REDEEM_OFFER['body'].format(id=id_),
headers=headers)

if r.status_code == 200:
__logger__.info('Successfully generated the coupon.')
else:
__logger__.error('There was an error generating the coupon: {}'.format(r.content))

return json.loads(r.content.decode())


class CouponHandler(base.Command):
name = 'coupon'

@run_async
def home(self, update: Update, context: CallbackContext):
body = self.__config__.get_template('home.html')\
.format(id=update.effective_user['id'], coupons=len(self.__config__.__offers__))

# Create inline keyboard
keyboard = [
[
InlineKeyboardButton("\U0001F354 Lista coupons", callback_data='{}_list'.format(self.name))
],
[
InlineKeyboardButton("Genera Promocode", callback_data='promo_generate')
],
[
InlineKeyboardButton("\u2753 F.A.Q.", callback_data='coupon_faq'),
InlineKeyboardButton("Source Code", url='http://bit.ly/2XvxXey')
]
]
reply_markup = InlineKeyboardMarkup(keyboard)

# Send message
context.bot.send_photo(chat_id=update.effective_chat.id, photo=open('header.png', 'rb'),
reply_markup=reply_markup,
caption=body, parse_mode=ParseMode.HTML)

@run_async
def callback(self, update: Update, context: CallbackContext):
query = update.callback_query

if query.data.startswith('{}_homepage'.format(self.name)):
# Return to homepage
self.home(update, context)

if query.data.endswith('_r'):
# Delete calling message
context.bot.delete_message(chat_id=query.message.chat.id, message_id=query.message.message_id)
else:
# Remove keyboard from calling message
context.bot.edit_message_reply_markup(chat_id=query.message.chat.id,
message_id=query.message.message_id,
reply_markup=None)

elif self.check_callback(query.data, 'list'):
# Delete calling message
try:
context.bot.delete_message(chat_id=query.message.chat.id, message_id=query.message.message_id)
except BadRequest:
pass

# Populate keyboard
keyboard = []
for x in self.__config__.__offers__:
offer_title = self.__config__.__offers__[x]['title']
if self.__config__.__offers__[x]['customTitle'] != '':
offer_title = self.__config__.__offers__[x]['customTitle']

if self.__config__.__offers__[x]['special']:
offer_title = '\U0001F552 ' + offer_title

keyboard.append([InlineKeyboardButton(offer_title, callback_data='{}_info_id_{}'.format(self.name, x))])
keyboard.append([InlineKeyboardButton("\u2b05 Menu principale",
callback_data='{}_homepage_r'.format(self.name))])
reply_markup = InlineKeyboardMarkup(keyboard)

# Send message
context.bot.send_message(chat_id=query.message.chat.id, text="Seleziona un'offerta:",
reply_markup=reply_markup)

elif query.data.startswith('{}_info_id'.format(self.name)):
# Answer callback
context.bot.answer_callback_query(query.id, text='Sto caricando l\'offerta...')
context.bot.send_chat_action(chat_id=update.effective_chat.id, action=ChatAction.UPLOAD_PHOTO)

# Get session
session = get_session(self.__config__)

# Delete calling message
context.bot.delete_message(chat_id=query.message.chat.id, message_id=query.message.message_id)

id_ = query.data.split('_')[3]

offer = self.__config__.__offers__[str(id_)]

if offer['promoImagePath'] != None:
if os.path.isfile(os.path.join(__image_folder__, offer['promoImagePath'])):
with open(os.path.join(__image_folder__, offer['promoImagePath']), 'rb') as f:
image = f.read()
else:
params = endpoints.PROMO_IMAGE['params']
params['path'] = offer['promoImagePath']
params['imageFormat'] = 'png'
r = session.request(endpoints.PROMO_IMAGE['method'], endpoints.PROMO_IMAGE['url'].format(size=1080),
params=params)

if r.status_code == 200:
with open(os.path.join(__image_folder__, offer['promoImagePath']), 'wb') as f:
f.write(r.content)
image = r.content
else:
image = None

# Create inline keyboard
keyboard = [
[
InlineKeyboardButton('\u2705 Va bene', callback_data='{}_id_{}'.format(self.name, id_)),
InlineKeyboardButton('\u2b05 Torna indietro', callback_data='{}_list'.format(self.name))
]
]
reply_markup = InlineKeyboardMarkup(keyboard)

extra_content = ''
if offer['special']:
days = get_days(offer['daysOfWeek'])
start_time = str(timedelta(minutes=offer['dailyStartTime']))[:-3]
end_time = str(timedelta(minutes=offer['dailyEndTime']))[:-3]
extra_content = '<b>Questa è un\'offerta speciale!</b>\n' \
'Può essere riscatta solo nei seguenti giorni: <i>{}</i>\n' \
'E solo nei seguenti orari: <i>{} - {}</i>'.format(days, start_time, end_time)

body = self.__config__.get_template('coupon_preview.html')\
.format(title=offer['title'], description=offer['description'], id=id_, start_date=offer['startDate'],
end_date=offer['endDate'], extra=extra_content)

if image != None:
context.bot.send_photo(chat_id=query.message.chat.id, photo=BytesIO(image), caption=body,
parse_mode=ParseMode.HTML, reply_markup=reply_markup)
else:
context.bot.send_message(chat_id=query.message.chat.id, text=body,
parse_mode=ParseMode.HTML, reply_markup=reply_markup)

elif query.data.startswith('{}_id'.format(self.name)):
# Edit message
context.bot.answer_callback_query(query.id, text="Sto generando l'offerta...")

# Remove keyboard from calling message
context.bot.edit_message_reply_markup(chat_id=query.message.chat.id,
message_id=query.message.message_id,
reply_markup=None)

context.bot.send_chat_action(chat_id=update.effective_chat.id, action=ChatAction.UPLOAD_PHOTO)

# Load template
body = self.__config__.get_template('coupon.html')

# Generate coupon
js = generate_coupon(query.data.split('_')[2], self.__config__)

# TODO: Temporary fix
try:
code = js['redemptionText']

# Generate QR code
qr = qrcode.QRCode(version=1, error_correction=qrcode.constants.ERROR_CORRECT_Q, box_size=15, border=0)
qr.add_data(code)
qr.make(fit=True)
qr_image = qr.make_image(fill_color="black", back_color="white")

# Get offer background
id_ = query.data.split('_')[2]
offer = self.__config__.__offers__[str(id_)]

offer_background = Image.open(os.path.join(__image_folder__, offer['promoImagePath']))
qr_background = Image.open(os.path.join(__image_folder__, 'bg.png'))
offset = (350, 350)

image_width, image_height = offer_background.size
final_image = Image.new('RGBA', (image_width, image_height), offer_background.getpixel((0, image_height-1)))

offer_background.putalpha(60)
final_image = Image.alpha_composite(final_image, offer_background)
final_image = Image.alpha_composite(final_image, qr_background)
final_image.paste(qr_image, offset)

# Create inline keyboard
keyboard = [
[
InlineKeyboardButton("\u2b05 Menu principale", callback_data='{}_homepage'.format(self.name))
]
]
reply_markup = InlineKeyboardMarkup(keyboard)

# Read QR code bytes
bio = BytesIO()
final_image.save(bio, 'PNG')
bio.seek(0)

# Send QR code
context.bot.send_photo(chat_id=query.message.chat.id, photo=bio,
caption=body.format(js['title'], js['description'], code, js['id']),
parse_mode=ParseMode.HTML, reply_markup=reply_markup)
except KeyError:
# Create inline keyboard
keyboard = [
[
InlineKeyboardButton("\u2b05 Menu principale",
callback_data='{}_homepage_r'.format(self.name))
]
]
reply_markup = InlineKeyboardMarkup(keyboard)
context.bot.send_message(chat_id=query.message.chat.id,
text='C\'è stato un errore. Riprova più tardi.', reply_markup=reply_markup)

context.bot.delete_message(chat_id=query.message.chat.id, message_id=query.message.message_id)

elif self.check_callback(query.data, 'faq'):
context.bot.answer_callback_query(query.id)

# Load template
body = self.__config__.get_template('faq.html').format(id=self.__config__.get_owner_id(),
name=self.__config__.get_owner_username())

context.bot.send_message(chat_id=query.message.chat.id, text=body, parse_mode=ParseMode.HTML)

+ 0
- 40
commands/promo.py View File

@@ -1,40 +0,0 @@
# -*- coding: utf-8 -*-

# Copyright 2019 Giacomo Ferretti
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from mcdapi import promo
from telegram import Update, ParseMode
from telegram.ext import CallbackContext, run_async

from commands import base


class PromoCommand(base.Command):
name = 'promo'

@run_async
def handler(self, update: Update, context: CallbackContext):
if self.can_run(update, context):
body = self.__config__.get_template('promo.html').format(promo.generate_random_promocode())
context.bot.send_message(chat_id=update.effective_chat.id, text=body, parse_mode=ParseMode.HTML)

@run_async
def callback_handler(self, update: Update, context: CallbackContext):
query = update.callback_query

if self.check_callback(query.data, 'generate'):
body = self.__config__.get_template('promo.html').format(promo.generate_random_promocode())
context.bot.answer_callback_query(query.id, text='Promocode generato.')
context.bot.send_message(chat_id=update.effective_chat.id, text=body, parse_mode=ParseMode.HTML)

+ 0
- 11
config.base.json View File

@@ -1,11 +0,0 @@
{
"token": "token",
"ownerId": 0,
"ownerUsername": "can_be_empty",
"adminIds": [
0
],
"templatesFolder": "templates",
"proxyEnabled": false,
"proxyUrl": "socks5://127.0.0.1:9050"
}

+ 0
- 33
docker-compose.yml View File

@@ -1,33 +0,0 @@
version: "3.7"

services:
torproxy:
image: torproxy
build:
context: .
dockerfile: .docker/torproxy-Dockerfile
args:
socks_port: "9050"
control_password: "password"
control_port: "9051"
networks:
- main
mcdapibot:
image: mcdapibot
build:
context: .
dockerfile: .docker/mcdapibot-Dockerfile
networks:
- main
volumes:
- "./logs:/app/logs"
- "./templates:/app/templates"
- "./config.json:/app/config.json"
- "./users.json:/app/users.json"
- "./offers.json:/app/offers.json"
depends_on:
- torproxy
restart: always

networks:
main:

+ 1
- 0
ffapi/README.md View File

@@ -0,0 +1 @@
Coming soon!

BIN
header.png View File


BIN
images/bg.png View File


+ 0
- 96
main.py View File

@@ -1,96 +0,0 @@
# -*- coding: utf-8 -*-

# Copyright 2019 Giacomo Ferretti
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import logging
import traceback

from telegram import Update
from telegram.ext import Updater, CommandHandler, MessageHandler, \
Filters, CallbackContext, CallbackQueryHandler

from commands import promo, coupon, admin
from utils import config, users, logger

__major__ = 0
__minor__ = 3
__patch__ = 0
__metadata__ = ''
__version__ = '{}.{}.{}{}'.format(__major__, __minor__, __patch__, __metadata__)


def setup():
global __logger__, __users__, __config__
logger.init()

__logger__ = logging.getLogger(__name__)

__logger__.info('Starting mcdapi-telegram-bot ({})...'.format(__version__))
__users__ = users.Users()
__config__ = config.Config()
__logger__.info('Done loading!')


def error(update: Update, context: CallbackContext):
__logger__.warning('Update "%s" caused error "%s"', update, context.error)
traceback.print_exc()


def main():
updater = Updater(__config__.get_token(), use_context=True)

# Handlers
__logger_handler = logger.LoggerHandler(__config__, __users__)
__admin_handler = admin.AdminManager(__config__, __users__)
__promo_handler = promo.PromoCommand(__config__)
__coupon_handler = coupon.CouponHandler(__config__)

# Logger handler - Logs all messages and callbacks
updater.dispatcher.add_handler(MessageHandler(Filters.text | Filters.command, __logger_handler.log_message), group=1)
updater.dispatcher.add_handler(CallbackQueryHandler(__logger_handler.log_callback), group=1)

# Main handlers - Coupons and homepage
updater.dispatcher.add_handler(CommandHandler('start', __coupon_handler.home))
updater.dispatcher.add_handler(CallbackQueryHandler(__coupon_handler.callback, pattern='{}.*'
.format(__coupon_handler.name)))

# Promo handler - Generates promocodes
updater.dispatcher.add_handler(CommandHandler('promo', __promo_handler.handler))
updater.dispatcher.add_handler(CallbackQueryHandler(__promo_handler.callback_handler, pattern='{}.*'
.format(__promo_handler.name)))

# Admin handler - Handles all the admin commands
updater.dispatcher.add_handler(CommandHandler('broadcast', __admin_handler.broadcast))
updater.dispatcher.add_handler(CommandHandler('send', __admin_handler.send_message))
updater.dispatcher.add_handler(CommandHandler('uptime', __admin_handler.uptime))
updater.dispatcher.add_handler(CommandHandler('users', __admin_handler.users_info))
updater.dispatcher.add_handler(CommandHandler('users_list', __admin_handler.users_list))
updater.dispatcher.add_handler(CommandHandler('check', __admin_handler.check_users))
updater.dispatcher.add_handler(MessageHandler(Filters.reply, __admin_handler.send_broadcast))

# Error handler
updater.dispatcher.add_error_handler(error)

# Start the Bot
updater.start_polling()

# Run the bot until the user presses Ctrl-C or the process receives SIGINT,
# SIGTERM or SIGABRT
updater.idle()


if __name__ == '__main__':
setup()
main()

+ 1
- 0
mcmod-server/README.md View File

@@ -0,0 +1 @@
Coming soon!

+ 1
- 0
mcmod/README.md View File

@@ -0,0 +1 @@
Coming soon!

+ 1
- 0
poc/README.md View File

@@ -0,0 +1 @@
Coming soon!

+ 0
- 6
requirements.txt View File

@@ -1,6 +0,0 @@
python-telegram-bot==12.0.0b1
mcdapi
requests[socks]
qrcode
Pillow
psutil

+ 1
- 0
telegram-bot/README.md View File

@@ -0,0 +1 @@
Coming soon!

+ 0
- 3
templates/broadcast.html View File

@@ -1,3 +0,0 @@
{message}

<i>Broadcast by</i> <a href="tg://user?id={id}">@{name}</a>

+ 0
- 7
templates/coupon.html View File

@@ -1,7 +0,0 @@
<b>{}</b>

{}

Codice Totem: <code>{}</code>

ID Offerta: <code>{}</code>

+ 0
- 9
templates/coupon_preview.html View File

@@ -1,9 +0,0 @@
<b>{title}</b>

<i>{description}</i>

ID: <code>{id}</code>
Inizio offerta: <code>{start_date}</code>
Fine offerta: <code>{end_date}</code>

{extra}

+ 0
- 17
templates/faq.html View File

@@ -1,17 +0,0 @@
<b>F.A.Q. (Frequently Asked Questions)</b>

<b>Q: Come utilizzo i coupon?</b>
<i>A: Una volta generato il coupon, dovrete fare lo scan del QR al totem, mettendo il telefono a 45° rispetto al lettore.</i>

<b>Q: Posso pagare alla cassa?</b>
<i>A: In teoria sì, ma per non avere problemi si consiglia di pagare direttamente al totem.</i>

<b>Q: Quanto durano i coupon generati?</b>
<i>A: I coupon durano circa un mese.</i>

<b>Q: Quando escono le nuove offerte?</b>
<i>A: Le offerte si aggiornano ogni mese circa.</i>

---

Per maggiori informazioni contattare <a href="tg://user?id={id}">@{name}</a>

+ 0
- 7
templates/home.html View File

@@ -1,7 +0,0 @@
<b>Benvenuto!</b>

Con questo bot puoi generare tutti i coupon che vuoi!

Attualmente ci sono <b>{coupons}</b> offerte generabili.

User ID: <code>{id}</code>

+ 0
- 8
templates/promo.html View File

@@ -1,8 +0,0 @@
Ecco a te un promo code:
<code>{}</code>

Puoi inserirlo qui: https://mcdonalds.it/tua-opinione-conta

Ricordati che il McDonald's usa questi sondaggi per sapere se ci sono problemi con un ristorante, quindi usalo responsabilmente.

<i>Se il codice non dovesse funzionare, generane un altro.</i>

+ 0
- 106
utils/config.py View File

@@ -1,106 +0,0 @@
# -*- coding: utf-8 -*-

# Copyright 2019 Giacomo Ferretti
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import json
import logging
import os
from datetime import datetime

__logger__ = logging.getLogger(__name__)

__config_file__ = 'config.json'
__offers_file__ = 'offers.json'


class Config:
global __logger__

def __init__(self):
if os.path.isfile(__config_file__):
__logger__.info('Loading "{}"...'.format(__config_file__))
with open(__config_file__) as f:
self.__config__ = json.loads(f.read())
else:
__logger__.error('E101: "{}" not found. Download "config.base.json" from '
'https://github.com/giacomoferretti/mcdapi-telegram-bot'.format(__config_file__))
exit(101)

if os.path.isfile(__offers_file__):
__logger__.info('Loading "{}"...'.format(__offers_file__))
with open(__offers_file__) as f:
self.__offers__ = json.loads(f.read())
else:
__logger__.error('E102: "{}" not found. Checkout https://github.com/giacomoferretti/mcdapi-tools '
'to see how to get the offers.'.format(__offers_file__))
exit(102)

def __save_config(self):
with open(__config_file__, 'w') as f:
f.write(json.dumps(self.__config__, indent=2))

def __reload(self):
self.__init__()

def get_token(self):
return self.__config__['token']

def get_owner_id(self):
return self.__config__['ownerId']

def get_owner_username(self):
return self.__config__['ownerUsername']

def get_admins(self):
return self.__config__['adminIds']

def get_templates_folder(self):
return self.__config__['templatesFolder']

def is_proxy_enabled(self):
return self.__config__['proxyEnabled']

def get_proxy_url(self):
return self.__config__['proxyUrl']

def is_admin(self, id_):
return id_ == self.__config__['ownerId'] or id_ in self.__config__['adminIds']

def add_admin(self, id_):
self.__config__['adminIds'].append(int(id_))
self.__save_config()

def remove_admin(self, id_):
self.__config__['adminIds'].remove(int(id_))
self.__save_config()

def set_maintenance(self, active):
self.__config__['maintenance'] = active
if active:
self.__config__['maintenanceStarted'] = datetime.now().strftime('%Y/%m/%d %H:%M:%S')
self.__save_config()

def get_maintenance_time(self):
return self.__config__['maintenanceStarted']

def get_maintenance(self):
return self.__config__['maintenance']

def get_template(self, template):
with open(os.path.join(self.get_templates_folder(), template)) as _f:
return _f.read()

def to_dict(self):
return self.__config__

+ 0
- 94
utils/logger.py View File

@@ -1,94 +0,0 @@
# -*- coding: utf-8 -*-

# Copyright 2019 Giacomo Ferretti
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import logging
import os
import sys
from logging.handlers import TimedRotatingFileHandler

from telegram import Update
from telegram.ext import run_async, CallbackContext

from commands import base


class InfoFilter(logging.Filter):
def filter(self, rec):
return rec.levelno in [logging.DEBUG, logging.INFO]


def init():
global __logger__

__log_folder = 'logs'
__app_log_file = 'app.log'
__error_log_file = 'error.log'
__log_format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
if not os.path.isdir(__log_folder):
os.makedirs(__log_folder)

__log_formatter = logging.Formatter(__log_format)
__root_logger = logging.getLogger()
__root_logger.setLevel(level=logging.INFO)

__app_file_handler = TimedRotatingFileHandler(os.path.join(__log_folder, __app_log_file), when='midnight',
interval=1)
__app_file_handler.setLevel(level=logging.DEBUG)
__app_file_handler.suffix = '%Y%m%d'
__app_file_handler.setFormatter(__log_formatter)
__app_file_handler.addFilter(InfoFilter())
__root_logger.addHandler(__app_file_handler)

__error_file_handler = TimedRotatingFileHandler(os.path.join(__log_folder, __error_log_file), when='midnight',
interval=1)
__error_file_handler.setLevel(level=logging.WARNING)
__error_file_handler.suffix = '%Y%m%d'
__error_file_handler.setFormatter(__log_formatter)
__root_logger.addHandler(__error_file_handler)

__console_out_handler = logging.StreamHandler(sys.stdout)
__console_out_handler.setLevel(level=logging.DEBUG)
__console_out_handler.setFormatter(__log_formatter)
__console_out_handler.addFilter(InfoFilter())
__root_logger.addHandler(__console_out_handler)

__console_err_handler = logging.StreamHandler(sys.stderr)
__console_err_handler.setLevel(level=logging.WARNING)
__console_err_handler.setFormatter(__log_formatter)
__root_logger.addHandler(__console_err_handler)

__logger__ = logging.getLogger(__name__)


class LoggerHandler(base.Command):
name = 'logger'

def __init__(self, __config__, __users__):
super().__init__(__config__)
self.__users__ = __users__

@run_async
def log_message(self, update: Update, context: CallbackContext):
self.__users__.add_user(update.effective_user.to_dict())
__logger__.info('[{}] {}: {}'.format(update.effective_user['id'], update.effective_user['first_name'],
update.message.text))

@run_async
def log_callback(self, update: Update, context: CallbackContext):
self.__users__.add_user(update.effective_user.to_dict())
__logger__.info('[{}] {} => {}'.format(update.callback_query['message']['chat']['id'],
update.callback_query['message']['chat']['first_name'],
update.callback_query.data))

+ 0
- 63
utils/users.py View File

@@ -1,63 +0,0 @@
# -*- coding: utf-8 -*-

# Copyright 2019 Giacomo Ferretti
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import json
import logging
import os

__logger__ = logging.getLogger(__name__)

__users_file__ = 'users.json'


class Users:
def __init__(self):
self.__users__ = {}

# Load saved users
if os.path.isfile(__users_file__) and os.stat(__users_file__).st_size != 0:
__logger__.info('Loading "{}"...'.format(__users_file__))
with open(__users_file__) as f:
self.__users__ = json.loads(f.read())
__logger__.info('Loaded {} users.'.format(len(self.__users__)))
else:
__logger__.warning('"{}" not found or empty.'.format(__users_file__))

def __save_users(self):
with open(__users_file__, 'w') as f:
f.write(json.dumps(self.__users__, indent=2))

def get_users(self):
return self.__users__

def add_user(self, user):
user_dict = {
'firstName': user.get('first_name', ''),
'lastName': user.get('last_name', ''),
'username': user.get('username', ''),
'languageCode': user.get('language_code', ''),
'active': True,
'offersGenerated': 0
}
self.__users__[str(user['id'])] = user_dict
self.__save_users()

def remove_user(self, id_):
self.__users__.pop(str(id_))
self.__save_users()

def to_dict(self):
return self.__users__

Loading…
Cancel
Save