|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
"""
|
|
|
|
|
:authors: python273
|
|
|
|
|
:license: Apache License, Version 2.0, see LICENSE file
|
|
|
|
|
|
|
|
|
|
:copyright: (c) 2019 python273
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import requests
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
from .vk_api import VkApi, VkApiMethod
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
STORY_ALLOWED_LINK_TEXTS = {
|
|
|
|
|
'to_store', 'vote', 'more', 'book', 'order',
|
|
|
|
|
'enroll', 'fill', 'signup', 'buy', 'ticket',
|
|
|
|
|
'write', 'open', 'learn_more', 'view', 'go_to',
|
|
|
|
|
'contact', 'watch', 'play', 'install', 'read'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class VkUpload(object):
|
|
|
|
|
""" Загрузка файлов через API (https://vk.com/dev/upload_files)
|
|
|
|
|
|
|
|
|
|
:param vk: объект :class:`VkApi` или :class:`VkApiMethod`
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
__slots__ = ('vk', 'http')
|
|
|
|
|
|
|
|
|
|
def __init__(self, vk):
|
|
|
|
|
|
|
|
|
|
if not isinstance(vk, (VkApi, VkApiMethod)):
|
|
|
|
|
raise TypeError(
|
|
|
|
|
'The arg should be VkApi or VkApiMethod instance'
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if isinstance(vk, VkApiMethod):
|
|
|
|
|
self.vk = vk
|
|
|
|
|
else:
|
|
|
|
|
self.vk = vk.get_api()
|
|
|
|
|
|
|
|
|
|
self.http = requests.Session()
|
|
|
|
|
self.http.headers.pop('user-agent')
|
|
|
|
|
|
|
|
|
|
def photo(self, photos, album_id,
|
|
|
|
|
latitude=None, longitude=None, caption=None, description=None,
|
|
|
|
|
group_id=None):
|
|
|
|
|
""" Загрузка изображений в альбом пользователя
|
|
|
|
|
|
|
|
|
|
:param photos: путь к изображению(ям) или file-like объект(ы)
|
|
|
|
|
:type photos: str or list
|
|
|
|
|
|
|
|
|
|
:param album_id: идентификатор альбома
|
|
|
|
|
:param latitude: географическая широта, заданная в градусах
|
|
|
|
|
(от -90 до 90)
|
|
|
|
|
:param longitude: географическая долгота, заданная в градусах
|
|
|
|
|
(от -180 до 180)
|
|
|
|
|
:param caption: текст описания изображения
|
|
|
|
|
:param description: текст описания альбома
|
|
|
|
|
:param group_id: идентификатор сообщества (если загрузка идет в группу)
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
values = {'album_id': album_id}
|
|
|
|
|
|
|
|
|
|
if group_id:
|
|
|
|
|
values['group_id'] = group_id
|
|
|
|
|
|
|
|
|
|
url = self.vk.photos.getUploadServer(**values)['upload_url']
|
|
|
|
|
|
|
|
|
|
with FilesOpener(photos) as photo_files:
|
|
|
|
|
response = self.http.post(url, files=photo_files).json()
|
|
|
|
|
|
|
|
|
|
if 'album_id' not in response:
|
|
|
|
|
response['album_id'] = response['aid']
|
|
|
|
|
|
|
|
|
|
response.update({
|
|
|
|
|
'latitude': latitude,
|
|
|
|
|
'longitude': longitude,
|
|
|
|
|
'caption': caption,
|
|
|
|
|
'description': description
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
values.update(response)
|
|
|
|
|
|
|
|
|
|
return self.vk.photos.save(**values)
|
|
|
|
|
|
|
|
|
|
def photo_messages(self, photos, peer_id=None):
|
|
|
|
|
""" Загрузка изображений в сообщения
|
|
|
|
|
|
|
|
|
|
:param photos: путь к изображению(ям) или file-like объект(ы)
|
|
|
|
|
:type photos: str or list
|
|
|
|
|
:param peer_id: peer_id беседы
|
|
|
|
|
:type peer_id: int
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
url = self.vk.photos.getMessagesUploadServer(peer_id=peer_id)['upload_url']
|
|
|
|
|
|
|
|
|
|
with FilesOpener(photos) as photo_files:
|
|
|
|
|
response = self.http.post(url, files=photo_files)
|
|
|
|
|
|
|
|
|
|
return self.vk.photos.saveMessagesPhoto(**response.json())
|
|
|
|
|
|
|
|
|
|
def photo_group_widget(self, photo, image_type):
|
|
|
|
|
""" Загрузка изображений в коллекцию сообщества для виджетов приложений сообществ
|
|
|
|
|
|
|
|
|
|
:param photo: путь к изображению или file-like объект
|
|
|
|
|
:type photo: str
|
|
|
|
|
|
|
|
|
|
:param image_type: тип изображиения в зависимости от выбранного виджета
|
|
|
|
|
(https://vk.com/dev/appWidgets.getGroupImageUploadServer)
|
|
|
|
|
:type image_type: str
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
url = self.vk.appWidgets.getGroupImageUploadServer(image_type=image_type)['upload_url']
|
|
|
|
|
|
|
|
|
|
with FilesOpener(photo, key_format='file') as photo_files:
|
|
|
|
|
response = self.http.post(url, files=photo_files)
|
|
|
|
|
|
|
|
|
|
return self.vk.appWidgets.saveGroupImage(**response.json())
|
|
|
|
|
|
|
|
|
|
def photo_profile(self, photo, owner_id=None, crop_x=None, crop_y=None,
|
|
|
|
|
crop_width=None):
|
|
|
|
|
""" Загрузка изображения профиля
|
|
|
|
|
|
|
|
|
|
:param photo: путь к изображению или file-like объект
|
|
|
|
|
:param owner_id: идентификатор сообщества или текущего пользователя.
|
|
|
|
|
По умолчанию загрузка идет в профиль текущего пользователя.
|
|
|
|
|
При отрицательном значении загрузка идет в группу.
|
|
|
|
|
:param crop_x: координата X верхнего правого угла миниатюры.
|
|
|
|
|
:param crop_y: координата Y верхнего правого угла миниатюры.
|
|
|
|
|
:param crop_width: сторона квадрата миниатюры.
|
|
|
|
|
При передаче всех crop_* для фотографии также будет
|
|
|
|
|
подготовлена квадратная миниатюра.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
values = {}
|
|
|
|
|
|
|
|
|
|
if owner_id:
|
|
|
|
|
values['owner_id'] = owner_id
|
|
|
|
|
|
|
|
|
|
crop_params = {}
|
|
|
|
|
|
|
|
|
|
if crop_x is not None and crop_y is not None and crop_width is not None:
|
|
|
|
|
crop_params['_square_crop'] = '{},{},{}'.format(
|
|
|
|
|
crop_x, crop_y, crop_width
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
response = self.vk.photos.getOwnerPhotoUploadServer(**values)
|
|
|
|
|
url = response['upload_url']
|
|
|
|
|
|
|
|
|
|
with FilesOpener(photo, key_format='file') as photo_files:
|
|
|
|
|
response = self.http.post(
|
|
|
|
|
url,
|
|
|
|
|
data=crop_params,
|
|
|
|
|
files=photo_files
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
return self.vk.photos.saveOwnerPhoto(**response.json())
|
|
|
|
|
|
|
|
|
|
def photo_chat(self, photo, chat_id):
|
|
|
|
|
""" Загрузка и смена обложки в беседе
|
|
|
|
|
|
|
|
|
|
:param photo: путь к изображению или file-like объект
|
|
|
|
|
:param chat_id: ID беседы
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
values = {'chat_id': chat_id}
|
|
|
|
|
url = self.vk.photos.getChatUploadServer(**values)['upload_url']
|
|
|
|
|
|
|
|
|
|
with FilesOpener(photo, key_format='file') as photo_file:
|
|
|
|
|
response = self.http.post(url, files=photo_file)
|
|
|
|
|
|
|
|
|
|
return self.vk.messages.setChatPhoto(
|
|
|
|
|
file=response.json()['response']
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def photo_wall(self, photos, user_id=None, group_id=None, caption=None):
|
|
|
|
|
""" Загрузка изображений на стену пользователя или в группу
|
|
|
|
|
|
|
|
|
|
:param photos: путь к изображению(ям) или file-like объект(ы)
|
|
|
|
|
:type photos: str or list
|
|
|
|
|
|
|
|
|
|
:param user_id: идентификатор пользователя
|
|
|
|
|
:param group_id: идентификатор сообщества (если загрузка идет в группу)
|
|
|
|
|
:param caption: текст описания фотографии.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
values = {}
|
|
|
|
|
|
|
|
|
|
if user_id:
|
|
|
|
|
values['user_id'] = user_id
|
|
|
|
|
elif group_id:
|
|
|
|
|
values['group_id'] = group_id
|
|
|
|
|
|
|
|
|
|
if caption:
|
|
|
|
|
values['caption'] = caption
|
|
|
|
|
|
|
|
|
|
response = self.vk.photos.getWallUploadServer(**values)
|
|
|
|
|
url = response['upload_url']
|
|
|
|
|
|
|
|
|
|
with FilesOpener(photos) as photos_files:
|
|
|
|
|
response = self.http.post(url, files=photos_files)
|
|
|
|
|
|
|
|
|
|
values.update(response.json())
|
|
|
|
|
|
|
|
|
|
return self.vk.photos.saveWallPhoto(**values)
|
|
|
|
|
|
|
|
|
|
def photo_market(self, photo, group_id, main_photo=False,
|
|
|
|
|
crop_x=None, crop_y=None, crop_width=None):
|
|
|
|
|
""" Загрузка изображений для товаров в магазине
|
|
|
|
|
|
|
|
|
|
:param photo: путь к изображению(ям) или file-like объект(ы)
|
|
|
|
|
:type photo: str or list
|
|
|
|
|
|
|
|
|
|
:param group_id: идентификатор сообщества, для которого необходимо загрузить фотографию товара
|
|
|
|
|
:type group_id: int
|
|
|
|
|
:param main_photo: является ли фотография обложкой товара
|
|
|
|
|
:type main_photo: bool
|
|
|
|
|
:param crop_x: координата x для обрезки фотографии (верхний правый угол)
|
|
|
|
|
:type crop_x: int
|
|
|
|
|
:param crop_y: координата y для обрезки фотографии (верхний правый угол)
|
|
|
|
|
:type crop_y: int
|
|
|
|
|
:param crop_width: ширина фотографии после обрезки в px
|
|
|
|
|
:type crop_width: int
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
if group_id < 0:
|
|
|
|
|
group_id = abs(group_id)
|
|
|
|
|
|
|
|
|
|
values = {
|
|
|
|
|
'main_photo': main_photo,
|
|
|
|
|
'group_id': group_id,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if crop_x is not None:
|
|
|
|
|
values['crop_x'] = crop_x
|
|
|
|
|
if crop_y is not None:
|
|
|
|
|
values['crop_y'] = crop_y
|
|
|
|
|
if crop_width is not None:
|
|
|
|
|
values['crop_width'] = crop_width
|
|
|
|
|
|
|
|
|
|
response = self.vk.photos.getMarketUploadServer(**values)
|
|
|
|
|
url = response['upload_url']
|
|
|
|
|
|
|
|
|
|
with FilesOpener(photo) as photos_files:
|
|
|
|
|
response = self.http.post(url, files=photos_files)
|
|
|
|
|
|
|
|
|
|
values.update(response.json())
|
|
|
|
|
|
|
|
|
|
return self.vk.photos.saveMarketPhoto(**values)
|
|
|
|
|
|
|
|
|
|
def photo_market_album(self, photo, group_id):
|
|
|
|
|
""" Загрузка фотографии для подборки товаров
|
|
|
|
|
|
|
|
|
|
:param photo: путь к изображению(ям) или file-like объект(ы)
|
|
|
|
|
:type photo: str or list
|
|
|
|
|
|
|
|
|
|
:param group_id: идентификатор сообщества, для которого необходимо загрузить фотографию для подборки товаров
|
|
|
|
|
:type group_id: int
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
if group_id < 0:
|
|
|
|
|
group_id = abs(group_id)
|
|
|
|
|
|
|
|
|
|
values = {
|
|
|
|
|
'group_id': group_id,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
response = self.vk.photos.getMarketAlbumUploadServer(**values)
|
|
|
|
|
url = response['upload_url']
|
|
|
|
|
|
|
|
|
|
with FilesOpener(photo) as photos_files:
|
|
|
|
|
response = self.http.post(url, files=photos_files)
|
|
|
|
|
|
|
|
|
|
values.update(response.json())
|
|
|
|
|
|
|
|
|
|
return self.vk.photos.saveMarketAlbumPhoto(**values)
|
|
|
|
|
|
|
|
|
|
def audio(self, audio, artist, title):
|
|
|
|
|
""" Загрузка аудио
|
|
|
|
|
|
|
|
|
|
:param audio: путь к аудиофайлу или file-like объект
|
|
|
|
|
:param artist: исполнитель
|
|
|
|
|
:param title: название
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
url = self.vk.audio.getUploadServer()['upload_url']
|
|
|
|
|
|
|
|
|
|
with FilesOpener(audio, key_format='file') as f:
|
|
|
|
|
response = self.http.post(url, files=f).json()
|
|
|
|
|
|
|
|
|
|
response.update({
|
|
|
|
|
'artist': artist,
|
|
|
|
|
'title': title
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return self.vk.audio.save(**response)
|
|
|
|
|
|
|
|
|
|
def video(self, video_file=None, link=None, name=None, description=None,
|
|
|
|
|
is_private=None, wallpost=None, group_id=None,
|
|
|
|
|
album_id=None, privacy_view=None, privacy_comment=None,
|
|
|
|
|
no_comments=None, repeat=None):
|
|
|
|
|
""" Загрузка видео
|
|
|
|
|
|
|
|
|
|
:param video_file: путь к видеофайлу или file-like объект.
|
|
|
|
|
:type video_file: object or str
|
|
|
|
|
|
|
|
|
|
:param link: url для встраивания видео с внешнего сайта,
|
|
|
|
|
например, с Youtube.
|
|
|
|
|
:type link: str
|
|
|
|
|
|
|
|
|
|
:param name: название видеофайла
|
|
|
|
|
:type name: str
|
|
|
|
|
|
|
|
|
|
:param description: описание видеофайла
|
|
|
|
|
:type description: str
|
|
|
|
|
|
|
|
|
|
:param is_private: указывается 1, если видео загружается для отправки
|
|
|
|
|
личным сообщением. После загрузки с этим параметром видеозапись
|
|
|
|
|
не будет отображаться в списке видеозаписей пользователя и не будет
|
|
|
|
|
доступна другим пользователям по ее идентификатору.
|
|
|
|
|
:type is_private: bool
|
|
|
|
|
|
|
|
|
|
:param wallpost: требуется ли после сохранения опубликовать
|
|
|
|
|
запись с видео на стене.
|
|
|
|
|
:type wallpost: bool
|
|
|
|
|
|
|
|
|
|
:param group_id: идентификатор сообщества, в которое будет сохранен
|
|
|
|
|
видеофайл. По умолчанию файл сохраняется на страницу текущего
|
|
|
|
|
пользователя.
|
|
|
|
|
:type group_id: int
|
|
|
|
|
|
|
|
|
|
:param album_id: идентификатор альбома, в который будет загружен
|
|
|
|
|
видеофайл.
|
|
|
|
|
:type album_id: int
|
|
|
|
|
|
|
|
|
|
:param privacy_view: настройки приватности просмотра видеозаписи в
|
|
|
|
|
специальном формате. (https://vk.com/dev/objects/privacy)
|
|
|
|
|
Приватность доступна для видеозаписей, которые пользователь
|
|
|
|
|
загрузил в профиль. (список слов, разделенных через запятую)
|
|
|
|
|
:param privacy_comment: настройки приватности комментирования
|
|
|
|
|
видеозаписи в специальном формате.
|
|
|
|
|
(https://vk.com/dev/objects/privacy)
|
|
|
|
|
|
|
|
|
|
:param no_comments: 1 — закрыть комментарии (для видео из сообществ).
|
|
|
|
|
:type no_comments: bool
|
|
|
|
|
|
|
|
|
|
:param repeat: зацикливание воспроизведения видеозаписи. Флаг.
|
|
|
|
|
:type repeat: bool
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
if not link and not video_file:
|
|
|
|
|
raise ValueError('Either link or video_file param is required')
|
|
|
|
|
|
|
|
|
|
if link and video_file:
|
|
|
|
|
raise ValueError('Both params link and video_file aren\'t allowed')
|
|
|
|
|
|
|
|
|
|
values = {
|
|
|
|
|
'name': name,
|
|
|
|
|
'description': description,
|
|
|
|
|
'is_private': is_private,
|
|
|
|
|
'wallpost': wallpost,
|
|
|
|
|
'link': link,
|
|
|
|
|
'group_id': group_id,
|
|
|
|
|
'album_id': album_id,
|
|
|
|
|
'privacy_view': privacy_view,
|
|
|
|
|
'privacy_comment': privacy_comment,
|
|
|
|
|
'no_comments': no_comments,
|
|
|
|
|
'repeat': repeat
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
response = self.vk.video.save(**values)
|
|
|
|
|
url = response.pop('upload_url')
|
|
|
|
|
|
|
|
|
|
with FilesOpener(video_file or [], 'video_file') as f:
|
|
|
|
|
response.update(self.http.post(
|
|
|
|
|
url,
|
|
|
|
|
files=f or None
|
|
|
|
|
).json())
|
|
|
|
|
return response
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def document(self, doc, title=None, tags=None, group_id=None,
|
|
|
|
|
to_wall=False, message_peer_id=None, doc_type=None):
|
|
|
|
|
""" Загрузка документа
|
|
|
|
|
|
|
|
|
|
:param doc: путь к документу или file-like объект
|
|
|
|
|
:param title: название документа
|
|
|
|
|
:param tags: метки для поиска
|
|
|
|
|
:param group_id: идентификатор сообщества (если загрузка идет в группу)
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
values = {
|
|
|
|
|
'group_id': group_id,
|
|
|
|
|
'peer_id': message_peer_id,
|
|
|
|
|
'type': doc_type
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if to_wall:
|
|
|
|
|
method = self.vk.docs.getWallUploadServer
|
|
|
|
|
elif message_peer_id:
|
|
|
|
|
method = self.vk.docs.getMessagesUploadServer
|
|
|
|
|
else:
|
|
|
|
|
method = self.vk.docs.getUploadServer
|
|
|
|
|
|
|
|
|
|
url = method(**values)['upload_url']
|
|
|
|
|
|
|
|
|
|
with FilesOpener(doc, 'file') as files:
|
|
|
|
|
response = self.http.post(url, files=files).json()
|
|
|
|
|
|
|
|
|
|
response.update({
|
|
|
|
|
'title': title,
|
|
|
|
|
'tags': tags
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return self.vk.docs.save(**response)
|
|
|
|
|
|
|
|
|
|
def document_wall(self, doc, title=None, tags=None, group_id=None):
|
|
|
|
|
""" Загрузка документа в папку Отправленные,
|
|
|
|
|
для последующей отправки документа на стену
|
|
|
|
|
или личным сообщением.
|
|
|
|
|
|
|
|
|
|
:param doc: путь к документу или file-like объект
|
|
|
|
|
:param title: название документа
|
|
|
|
|
:param tags: метки для поиска
|
|
|
|
|
:param group_id: идентификатор сообщества (если загрузка идет в группу)
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
return self.document(doc, title, tags, group_id, to_wall=True)
|
|
|
|
|
|
|
|
|
|
def document_message(self, doc, title=None, tags=None, peer_id=None):
|
|
|
|
|
""" Загрузка документа для отправки личным сообщением.
|
|
|
|
|
|
|
|
|
|
:param doc: путь к документу или file-like объект
|
|
|
|
|
:param title: название документа
|
|
|
|
|
:param tags: метки для поиска
|
|
|
|
|
:param peer_id: peer_id беседы
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
return self.document(doc, title, tags, message_peer_id=peer_id)
|
|
|
|
|
|
|
|
|
|
def audio_message(self, audio, peer_id=None, group_id=None):
|
|
|
|
|
""" Загрузка аудио-сообщения.
|
|
|
|
|
|
|
|
|
|
:param audio: путь к аудиофайлу или file-like объект
|
|
|
|
|
:param peer_id: идентификатор диалога
|
|
|
|
|
:param group_id: для токена группы, можно передавать ID группы,
|
|
|
|
|
вместо peer_id
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
return self.document(
|
|
|
|
|
audio,
|
|
|
|
|
doc_type='audio_message',
|
|
|
|
|
message_peer_id=peer_id,
|
|
|
|
|
group_id=group_id,
|
|
|
|
|
to_wall=group_id is not None
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def graffiti(self, image, peer_id=None, group_id=None):
|
|
|
|
|
""" Загрузка граффити
|
|
|
|
|
|
|
|
|
|
:param image: путь к png изображению или file-like объект.
|
|
|
|
|
:param peer_id: идентификатор диалога (только для авторизации пользователя)
|
|
|
|
|
:param group_id: для токена группы, нужно передавать ID группы,
|
|
|
|
|
вместо peer_id
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
return self.document(
|
|
|
|
|
image,
|
|
|
|
|
doc_type='graffiti',
|
|
|
|
|
message_peer_id=peer_id,
|
|
|
|
|
group_id=group_id,
|
|
|
|
|
to_wall=group_id is not None
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def photo_cover(self, photo, group_id,
|
|
|
|
|
crop_x=None, crop_y=None,
|
|
|
|
|
crop_x2=None, crop_y2=None):
|
|
|
|
|
""" Загрузка изображения профиля
|
|
|
|
|
|
|
|
|
|
:param photo: путь к изображению или file-like объект
|
|
|
|
|
:param group_id: идентификатор сообщества
|
|
|
|
|
:param crop_x: координата X верхнего левого угла для обрезки изображения
|
|
|
|
|
:param crop_y: координата Y верхнего левого угла для обрезки изображения
|
|
|
|
|
:param crop_x2: коорд. X нижнего правого угла для обрезки изображения
|
|
|
|
|
:param crop_y2: коорд. Y нижнего правого угла для обрезки изображения
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
values = {
|
|
|
|
|
'group_id': group_id,
|
|
|
|
|
'crop_x': crop_x,
|
|
|
|
|
'crop_y': crop_y,
|
|
|
|
|
'crop_x2': crop_x2,
|
|
|
|
|
'crop_y2': crop_y2
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
url = self.vk.photos.getOwnerCoverPhotoUploadServer(**values)['upload_url']
|
|
|
|
|
|
|
|
|
|
with FilesOpener(photo, key_format='file') as photo_files:
|
|
|
|
|
response = self.http.post(url, files=photo_files)
|
|
|
|
|
|
|
|
|
|
return self.vk.photos.saveOwnerCoverPhoto(
|
|
|
|
|
**response.json()
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def story(self, file, file_type, add_to_news=True, user_ids=None,
|
|
|
|
|
reply_to_story=None, link_text=None,
|
|
|
|
|
link_url=None, group_id=None):
|
|
|
|
|
""" Загрузка истории
|
|
|
|
|
|
|
|
|
|
:param file: путь к изображению, гифке или видео или file-like объект
|
|
|
|
|
:param file_type: тип истории (photo или video)
|
|
|
|
|
:param add_to_news: размещать ли историю в новостях
|
|
|
|
|
:param user_ids: идентификаторы пользователей,
|
|
|
|
|
которые будут видеть историю
|
|
|
|
|
:param reply_to_story: идентификатор истории,
|
|
|
|
|
в ответ на которую создается новая
|
|
|
|
|
:param link_text: текст ссылки для перехода из истории
|
|
|
|
|
:param link_url: адрес ссылки для перехода из истории
|
|
|
|
|
:param group_id: идентификатор сообщества,
|
|
|
|
|
в которое должна быть загружена история
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
if user_ids is None:
|
|
|
|
|
user_ids = []
|
|
|
|
|
|
|
|
|
|
if file_type == 'photo':
|
|
|
|
|
method = self.vk.stories.getPhotoUploadServer
|
|
|
|
|
elif file_type == 'video':
|
|
|
|
|
method = self.vk.stories.getVideoUploadServer
|
|
|
|
|
else:
|
|
|
|
|
raise ValueError('type should be either photo or video')
|
|
|
|
|
|
|
|
|
|
if not add_to_news and not user_ids:
|
|
|
|
|
raise ValueError(
|
|
|
|
|
'add_to_news and/or user_ids param is required'
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if (link_text or link_url) and not group_id:
|
|
|
|
|
raise ValueError('Link params available only for communities')
|
|
|
|
|
|
|
|
|
|
if (not link_text) != (not link_url):
|
|
|
|
|
raise ValueError(
|
|
|
|
|
'Either both link_text and link_url or neither one are required'
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if link_text and link_text not in STORY_ALLOWED_LINK_TEXTS:
|
|
|
|
|
raise ValueError('Invalid link_text')
|
|
|
|
|
|
|
|
|
|
if link_url and not link_url.startswith('https://vk.com'):
|
|
|
|
|
raise ValueError(
|
|
|
|
|
'Only internal https://vk.com links are allowed for link_url'
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if link_url and len(link_url) > 2048:
|
|
|
|
|
raise ValueError('link_url is too long. Max length - 2048')
|
|
|
|
|
|
|
|
|
|
values = {
|
|
|
|
|
'add_to_news': int(add_to_news),
|
|
|
|
|
'user_ids': ','.join(map(str, user_ids)),
|
|
|
|
|
'reply_to_story': reply_to_story,
|
|
|
|
|
'link_text': link_text,
|
|
|
|
|
'link_url': link_url,
|
|
|
|
|
'group_id': group_id
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
url = method(**values)['upload_url']
|
|
|
|
|
|
|
|
|
|
with FilesOpener(file, key_format='file') as files:
|
|
|
|
|
return self.http.post(url, files=files)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class FilesOpener(object):
|
|
|
|
|
def __init__(self, paths, key_format='file{}'):
|
|
|
|
|
if not isinstance(paths, list):
|
|
|
|
|
paths = [paths]
|
|
|
|
|
|
|
|
|
|
self.paths = paths
|
|
|
|
|
self.key_format = key_format
|
|
|
|
|
self.opened_files = []
|
|
|
|
|
|
|
|
|
|
def __enter__(self):
|
|
|
|
|
return self.open_files()
|
|
|
|
|
|
|
|
|
|
def __exit__(self, type, value, traceback):
|
|
|
|
|
self.close_files()
|
|
|
|
|
|
|
|
|
|
def open_files(self):
|
|
|
|
|
self.close_files()
|
|
|
|
|
|
|
|
|
|
files = []
|
|
|
|
|
|
|
|
|
|
for x, file in enumerate(self.paths):
|
|
|
|
|
if hasattr(file, 'read'):
|
|
|
|
|
f = file
|
|
|
|
|
|
|
|
|
|
if hasattr(file, 'name'):
|
|
|
|
|
filename = file.name
|
|
|
|
|
else:
|
|
|
|
|
filename = '.jpg'
|
|
|
|
|
else:
|
|
|
|
|
filename = file
|
|
|
|
|
f = open(filename, 'rb')
|
|
|
|
|
self.opened_files.append(f)
|
|
|
|
|
|
|
|
|
|
ext = filename.split('.')[-1]
|
|
|
|
|
files.append(
|
|
|
|
|
(self.key_format.format(x), ('file{}.{}'.format(x, ext), f))
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
return files
|
|
|
|
|
|
|
|
|
|
def close_files(self):
|
|
|
|
|
for f in self.opened_files:
|
|
|
|
|
f.close()
|
|
|
|
|
|
|
|
|
|
self.opened_files = []
|