├── README.md ├── app ├── __init__.py ├── data │ ├── group_info_to_config.py │ ├── parser.py │ ├── posts.py │ ├── posts_api.py │ ├── posts_callback.py │ ├── user.py │ └── users_api.py ├── forms.py ├── models.py ├── static │ ├── css │ │ ├── buttons.css │ │ ├── content.css │ │ ├── fonts.css │ │ ├── header.css │ │ └── style.css │ ├── db │ │ └── db.sqlite │ ├── fonts │ │ ├── FuturaBook.otf │ │ ├── FuturaMedium.ttf │ │ ├── FuturaMediumItalic.otf │ │ └── ljk_Applemelon.ttf │ ├── img │ │ ├── favicon.png │ │ ├── file_on_load │ │ │ └── load.png │ │ ├── star.png │ │ └── vk_logo.png │ ├── js │ │ ├── all_posts.js │ │ ├── post.js │ │ └── registration.js │ └── node_modules │ │ └── air-datepicker │ │ ├── dist │ │ ├── css │ │ │ ├── datepicker.css │ │ │ └── datepicker.min.css │ │ └── js │ │ │ ├── datepicker.js │ │ │ ├── datepicker.min.js │ │ │ └── i18n │ │ │ ├── datepicker.cs.js │ │ │ ├── datepicker.da.js │ │ │ ├── datepicker.de.js │ │ │ ├── datepicker.en.js │ │ │ ├── datepicker.es.js │ │ │ ├── datepicker.fi.js │ │ │ ├── datepicker.fr.js │ │ │ ├── datepicker.hu.js │ │ │ ├── datepicker.nl.js │ │ │ ├── datepicker.pl.js │ │ │ ├── datepicker.pt-BR.js │ │ │ ├── datepicker.pt.js │ │ │ ├── datepicker.ro.js │ │ │ ├── datepicker.sk.js │ │ │ └── datepicker.zh.js │ │ └── src │ │ ├── js │ │ ├── air-datepicker.js │ │ ├── body.js │ │ ├── datepicker.js │ │ ├── i18n │ │ │ ├── datepicker.cs.js │ │ │ ├── datepicker.da.js │ │ │ ├── datepicker.de.js │ │ │ ├── datepicker.en.js │ │ │ ├── datepicker.es.js │ │ │ ├── datepicker.fi.js │ │ │ ├── datepicker.fr.js │ │ │ ├── datepicker.hu.js │ │ │ ├── datepicker.nl.js │ │ │ ├── datepicker.pl.js │ │ │ ├── datepicker.pt-BR.js │ │ │ ├── datepicker.pt.js │ │ │ ├── datepicker.ro.js │ │ │ ├── datepicker.sk.js │ │ │ └── datepicker.zh.js │ │ ├── navigation.js │ │ └── timepicker.js │ │ └── sass │ │ ├── _datepicker-config.scss │ │ ├── cell.scss │ │ ├── datepicker.scss │ │ ├── navigation.scss │ │ └── timepicker.scss └── templates │ ├── all_posts.html │ ├── base.html │ ├── base_posts.html │ ├── error.html │ ├── fav_posts.html │ ├── login.html │ ├── post.html │ ├── registration.html │ └── sug_posts.html ├── config.py ├── loading_posts.py ├── main.py ├── migrations ├── README ├── alembic.ini ├── env.py ├── script.py.mako └── versions │ ├── 52092cb33eaa_fav_posts.py │ ├── 84ed8e4ce546_init_users_table.py │ ├── ce77996e3c51_change_registration.py │ ├── dc3ac32df755_unique_vk_domain.py │ ├── e34de35aeb53_edit_posts_table.py │ └── f98bb54185d7_init_posts_table.py └── requirements.txt /README.md: -------------------------------------------------------------------------------- 1 | ##### Configuration tutorial: 2 | 1) Register on Vkontakte *www.vk.com* and create a group if you haven't already 3 | 2) Create your web-app: 4 | 5 | a) Go to *www.vk.com/dev* and click on "My Apps" 6 | 7 | b) Click "Create app" 8 | 9 | c) Choose "Website", enter a title, Website address, Base domain. You can change them later 10 | 11 | d) And click "Connect website" 12 | 13 | ![Creation](https://sun9-21.userapi.com/c858028/v858028031/1e311c/I0BVEQFMy68.jpg) 14 | 3) Go to "Settings". You will need the info here to set up the program 15 | 4) Open "config.py"; 16 | 17 | a) Copy "App ID" and paste it into CLIENT_ID in the config file 18 | 19 | b) Copy "Secure key" and paste it into SECRET_KEY 20 | 21 | c) Copy "Service token" and paste it into ACCESS_TOKEN 22 | 23 | ![Configuration](https://sun9-3.userapi.com/c858028/v858028031/1e312c/ZIs6PEz-DVQ.jpg) 24 | 5) Then you need to find the page's ID, copy it and paste into VK_GROUP_ID **(include the minus before the digits)** 25 | 26 | Everything else will be set up automatically 27 | 28 | 6) Host the app to get the URL address; 29 | 7) Copy the URL // ex: https://yoursite.com. Paste it into Website address in App/Settings (VKontakte app). Paste it into Base domain omitting 'https://' or 'http://'. Save the changes 30 | 31 | ![Getting the URL](https://sun9-21.userapi.com/c858028/v858028031/1e316f/j7iv11eRx8Y.jpg) 32 | 8) Go to your group's settings / manage 33 | 34 | a) Open "API usage" 35 | 36 | b) Go to "Callback API", paste the URL of your site + '/callback' // ex: https://yoursite.com/callback 37 | 38 | ![Configuring Callback API](https://sun9-60.userapi.com/c858028/v858028031/1e3180/oozs3kRJa6M.jpg) 39 | 40 | c) Open app/data/posts_callback. Then, before the function docstring paste 'return' + string to be returned (you can find the string on the page with the Callback API settings) 41 | 42 | d) Confirm the Callback API for your group and delete the line you pasted previously 43 | 44 | And that's all. I hope your app is working. -------------------------------------------------------------------------------- /app/__init__.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | from flask_sqlalchemy import SQLAlchemy 3 | from flask_migrate import Migrate 4 | from flask_login import LoginManager 5 | from flask_restful import Api 6 | 7 | from .data.group_info_to_config import get_info 8 | 9 | from config import DevConfig 10 | 11 | app = Flask(__name__) 12 | app.config.from_object(DevConfig) 13 | 14 | app.config['VK_GROUP_NAME'], app.config['VK_SCREEN_NAME'] = get_info(app.config) 15 | 16 | db = SQLAlchemy(app) 17 | migrate = Migrate(app, db) 18 | 19 | from app import models 20 | 21 | with app.app_context(): 22 | if db.engine.url.drivername == 'sqlite': 23 | migrate.init_app(app, db, render_as_batch=True) 24 | 25 | 26 | def get_db_session() -> db.Session: 27 | return db.session 28 | 29 | 30 | login_manager = LoginManager(app) 31 | 32 | from app.data import posts_api, users_api, posts_callback 33 | 34 | api = Api(app) 35 | api.add_resource(users_api.UsersResource, '/api/users') 36 | api.add_resource(posts_api.FavPost, '/api/favpost') 37 | 38 | app.register_blueprint(users_api.blueprint) 39 | app.register_blueprint(posts_api.blueprint) 40 | app.register_blueprint(posts_callback.blueprint) 41 | -------------------------------------------------------------------------------- /app/data/group_info_to_config.py: -------------------------------------------------------------------------------- 1 | from requests import get 2 | 3 | 4 | def get_info(config): 5 | """ 6 | :param config: app's config 7 | 8 | :return: values for config including group's name & screen name 9 | 10 | Values getting from req to the VK API. 11 | """ 12 | 13 | vk_api_url = "https://api.vk.com/method/" 14 | 15 | params = {'group_ids': config['VK_GROUP_ID'][1:], 16 | 'access_token': config['ACCESS_TOKEN'], 17 | 'v': '5.2'} 18 | 19 | # getting info 20 | group_info = get(vk_api_url + 'groups.getById', params=params).json()['response'][0] 21 | 22 | vk_group_name = group_info['name'] 23 | vk_group_screen_name = group_info['screen_name'] 24 | 25 | return vk_group_name, vk_group_screen_name 26 | -------------------------------------------------------------------------------- /app/data/parser.py: -------------------------------------------------------------------------------- 1 | from flask_restful import reqparse 2 | 3 | user_parser = reqparse.RequestParser() 4 | user_parser.add_argument('nickname', required=True) 5 | user_parser.add_argument('vkDomain', required=True) 6 | user_parser.add_argument('accessToken', required=True) 7 | 8 | fav_post_parser = reqparse.RequestParser() 9 | fav_post_parser.add_argument('post_id', required=True, type=int) 10 | -------------------------------------------------------------------------------- /app/data/posts.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from app import app 3 | 4 | VK_API_URL = "https://api.vk.com/method/" 5 | 6 | 7 | def get_attachment(access_token, data, group_id, user_id): 8 | """ 9 | :param access_token: user's access token 10 | :param data: bytes of img 11 | 12 | :return: str of attachment info for pin to the post 13 | """ 14 | 15 | # getting info of the loading server 16 | upload_server = requests.get(VK_API_URL + 'photos.getWallUploadServer', params={ 17 | 'group_id': group_id, 18 | 'access_token': access_token, 19 | 'v': '5.103' 20 | }).json() 21 | 22 | # get load attr 23 | photo_upload = requests.post(upload_server['response']['upload_url'], 24 | files={'photo': data}).json() 25 | # getting attachment info 26 | photo = requests.get(VK_API_URL + 'photos.saveWallPhoto', params={ 27 | 'user_id': user_id, 28 | 'group_id': group_id, 29 | 'photo': photo_upload['photo'], 30 | 'server': photo_upload['server'], 31 | 'hash': photo_upload['hash'], 32 | 'access_token': access_token, 33 | 'v': '5.103' 34 | }).json() 35 | 36 | return f"photo{photo['response'][0]['owner_id']}_{photo['response'][0]['id']}" 37 | 38 | 39 | def get_suggests(access_token): 40 | """ 41 | :param access_token: access token for getting sug. posts for admin 42 | """ 43 | 44 | # checking if items is empty --> there no posts 45 | is_empty = lambda items: False if items else True 46 | 47 | items, offset = None, 0 48 | posts = [] 49 | 50 | while True: 51 | params = {'owner_id': app.config['VK_GROUP_ID'], 52 | 'extended': '1', 53 | 'count': '100', 54 | 'filter': 'suggests', 55 | 'offset': offset, 56 | 'access_token': access_token, 57 | 'v': '5.2'} 58 | items = requests.get(VK_API_URL + 'wall.get', params=params).json()['response']['items'] 59 | 60 | # if no posts --> exit the func & return the list 61 | if is_empty(items): 62 | return posts 63 | 64 | # getting posts info 65 | for item in items: 66 | if 'attachments' not in item.keys(): 67 | continue 68 | if 'photo' not in item['attachments'][0].keys(): 69 | continue 70 | post = { 71 | 'vk_id': item['id'], 72 | 'photo_url': item['attachments'][0]['photo']['photo_807'] 73 | if 'photo_807' in item['attachments'][0]['photo'].keys() 74 | else item['attachments'][0]['photo']['photo_604'] 75 | } 76 | posts.append(post) 77 | 78 | offset += 100 79 | -------------------------------------------------------------------------------- /app/data/posts_api.py: -------------------------------------------------------------------------------- 1 | from flask import jsonify, Blueprint, request, make_response 2 | from flask_login import login_required, current_user 3 | from flask_restful import Resource 4 | from app.data.parser import fav_post_parser as parser 5 | 6 | from app.models import Post, User 7 | from app import get_db_session, app 8 | from app.data.posts import get_attachment, get_suggests 9 | 10 | from datetime import datetime 11 | from pytz import timezone 12 | from base64 import b64decode 13 | 14 | 15 | class FavPost(Resource): 16 | @login_required 17 | def get(self): 18 | """ 19 | :return: object with list of fav posts 20 | """ 21 | 22 | user_id = current_user.id 23 | session = get_db_session() 24 | user = session.query(User).filter(User.id == user_id).first() 25 | posts = user.favors 26 | return jsonify({'posts': [post.to_dict(only=('id', 'vk_id', 'photo_url')) 27 | for post in posts[::-1]]}) 28 | 29 | @login_required 30 | def post(self): 31 | """ 32 | Getting obj with group id from the request; 33 | Getting current user's id; 34 | 35 | Add to the favorites table association user-post. 36 | """ 37 | 38 | post_id = parser.parse_args()['post_id'] 39 | user_id = current_user.id 40 | session = get_db_session() 41 | user = session.query(User).filter(User.id == user_id).first() 42 | post = session.query(Post).filter(Post.id == post_id).first() 43 | user.favors.append(post) 44 | session.commit() 45 | 46 | @login_required 47 | def delete(self): 48 | """ 49 | Getting obj with group id from the request; 50 | Getting current user's id; 51 | 52 | Delete from favorites table association user-post with. 53 | """ 54 | 55 | post_id = parser.parse_args()['post_id'] 56 | user_id = current_user.id 57 | session = get_db_session() 58 | user = session.query(User).filter(User.id == user_id).first() 59 | post = session.query(Post).filter(Post.id == post_id).first() 60 | user.favors.remove(post) 61 | session.commit() 62 | 63 | 64 | blueprint = Blueprint('posts_rest_api', __name__, template_folder='templates') 65 | 66 | 67 | @blueprint.route('/api/posts') 68 | def get_posts(): 69 | """ 70 | :return: obj with list of posts 71 | 72 | Type of posts is selected based on the type of page, from which the request was received. 73 | """ 74 | 75 | session = get_db_session() 76 | post_type = request.args.get('type') 77 | 78 | posts = [] 79 | if post_type == 'all': 80 | posts = session.query(Post).all() 81 | elif post_type == 'fav': 82 | posts = session.query(User).filter(User.id == current_user.id).first().favors 83 | elif post_type == 'sug': 84 | return jsonify({'posts': get_suggests(current_user.access_token)}) 85 | return jsonify({'posts': [post.to_dict(only=('id', 'vk_id', 'photo_url')) 86 | for post in posts[::-1]]}) 87 | 88 | 89 | @blueprint.route('/api/posts/unixtime__