June 8, 2020

Воруем картинки из вк и постим в телегу

Начнём с импортов, их благо не так много (Бот будет работать на python3.7+):

import requests
import telebot
import sqlite3
from time import sleep
import json

Теперь нам нужны переменные с токенами и айдишниками. в переменной v_token будем хранить токен для ВК. Что бы его получить нужно перейти на сайт:
https://vkhost.github.io (я получал там) а затем выбрать приложение. Я выбрал VK API. Ссылка на доку вк (https://vk.com/dev/methods ).

Далее в переменную tb вносим токен телеграмм бота который находится в канале куда постить. Указываем айди админа которому будут приходить ошибки (если таковые будут). В groupid мы указываем группу телеграм в которую будем постить картинки. Что бы получить этот айди вам нужно отправить боту @getmyidbot какой либо пост с канала и тогда он вам вернёт айди канала. Вносить его нужно с минусом.

v_token = "7l0m0v3dkl__t.me/CodingCommunity__83fglkiaguto9602p3nbrbuz67bjf4jj5pqkehivifj0kusc227qut"
tb = telebot.TeleBot("0001337000:@CodingCommunity")
admin_id = 13371337
group_id = -123123123123
image_group = [111111111, 2222222, 3333333, 44444444, 55555555]

Так как нам необходимо проверять картинки которые мы собираемся публиковать, что бы скрипт не брал одни и те же - мы подключим базу данных и будем записывать туда айди группы и последнюю взятую из группы картинку.

def write_group_and_url_photo(group_id, photo_url):
	con = sqlite3.connect("group_and_photo.db")
	cur = con.cursor()
	cur.execute(f"""CREATE TABLE IF NOT EXISTS GAUP (group_id int, image_url text);""") 
	cur.execute(f"INSERT INTO GAUP VALUES(?,?)", (group_id, photo_url))
	con.commit()
	cur.close() 
	con.close()

Давайте не будем обращать внимание на странное название таблицы "GAUP" (Group And Url Photo). для записи мы будем передавать айди группы (которое у нас целочисленное [integet] ), откуда берём ссылку на картинку если она там будет photourl (текстовое значение [string] ). Затем мы подключаемся к базе "group_and_photo.db", создаём курсор и пробуем создать таблицу, если таковой нет, а затем пробуем вставить наши данные. Хочу сразу вам сказать, что изначально таблицу вам надо будет заполнить что бы не было проблем, просто задайте для каждой группы по одному "посту" как это сделал я:

Да да, тут есть группы откуда я брал картинки.

Далее - для того что бы сравнить последнюю картинку с группой нам понадобится функция которая получит по переданному айди группы - image_url

def get_last_post(group_id):
	con = sqlite3.connect("group_and_photo.db")
	cur = con.cursor()	
	cur.execute(f"SELECT * FROM GAUP WHERE group_id = ?", [(group_id)])
	data = cur.fetchall()
	con.commit()
	cur.close() 
	con.close()	
	return data[-1][-1]

Тут мы в значение передаём айди группы, ВСЕ данные записываются в переменную data. Дата это массив с данными для одной группы которую мы передали. Мы достаём последнею запись data[-1] и из неё достаём image_url : data[-1][-1] и возвращаем это туда откуда функция была вызвана.

Далее будет функция которую можно было не писать, но мне с ней было проще работать:

def vk_exec(method, param):
	URL = "https://api.vk.com/method/" + method + "?" + param
	return requests.get(URL).json()

Тут мы будем передавать вк метод который хотим использовать и его параметры, а возвращать он нам будет готовую json строку которую мы будем парсить.

Далее идёт большая функция. Думаю её стоит объяснять по частям.

def wall_img(group_id, offset=None):
	if offset == None:
		get_image = vk_exec("wall.get", f"owner_id=-{group_id}&v=5.35&count=1&access_token={v_token}")
	else:
		get_image = vk_exec("wall.get", f"owner_id=-{group_id}&v=5.35&offset={offset}&count=1&access_token={v_token}")
	with open('data.json', 'w') as f:
		f.write(json.dumps(get_image))
	with open('data.json', 'r', encoding='utf-8') as f:
		dat = json.load(f)

Начало функции вроде не сложное, мы проверяем - было ли передано значение offset, если было передано то мы используем ссылку по которой получаем пост со смещением, дальше объясню зачем это нужно. Если же offset не указан - мы просто получаем первую запись со стены сообщества (то есть последнею опубликованную). Затем мы записываем ответ в файл json (зачем? всё просто, делаем как можно меньше запросов, ибо вк не любит запросы. дальше увидите почему это делается так). Так как get_image сам по себе json - мы можем очень легко его записать в файл.

Сейчас пойдет море проверок:

	try:
		if 'items' in dat['response']:
			if 'is_pinned' not in dat['response']['items'][0]:
				if 'attachments' in dat['response']['items'][0]:
					if 'photo' in dat['response']['items'][0]['attachments'][0] and 'album' not in dat['response']['items'][0]['attachments'][0]:
						photo = dat['response']['items'][0]['attachments'][0]['photo']['photo_604']
						return [group_id, photo]
					else:
						return 'NOIMAGE'	
				else:
					return 'NOIMAGE'
			else:
				return wall_img(image_group[0], 1) # из-за рекурсии
		else:
			return 'NOIMAGE'
	except Exception as e:
		get_image = vk_exec("wall.get", f"owner_id=-{group_id}&v=5.35&count=1&access_token={v_token}")
		if get_image['error']['error_code'] == 5:
			tb.send_message(admin_id, 'Error: \n\n' + 'USER TOKEN IS BLOCKED') # если токен не валид
		elif get_image['error']['error_code'] == 6:
			tb.send_message(admin_id, 'Error: \n\n' + 'TOO MANY REQUESTS PER SECOND') # слишком много запросов в секунду
		else:
			tb.send_message(admin_id, 'Error: \n\n' + str(e))
		sleep(2)

Сначала мы проверяем есть ли items в dat['response'], в них хранятся изображения, видео, альбомы и т.д., если есть то идём дальше, если нет то возвращаем "NOIMAGE", теперь мы проверяем - закреплено ли сообщение, мы ведь не знаем на сколько группа закрепляет сообщение, может оно у них 2 года уже висит, поэтому в случае закрепленного поста мы просто вызываем нашу функцию еще раз с офсетом на один пост, то есть проходим закрепленный пост и переходим к последнему посту. Идём дальше, если пост не закреплен то у него обязательно должны быть вложенные объекты, что бы это не был просто текст, если таковые есть то мы идём дальше, иначе возвращаем "NOIMAGE", теперь самая главная проверка, есть ли ФОТО в сообщении и не является ли это альбомом, если проверка прошла успешно то мы получаем ссылку на фото и возвращаем массив в котором айди группы и ссылка на фотографию. Так же есть разного рода обработчики ошибок, которые будут сообщать админу об ошибках. Они находятся в except. А теперь самое интересное, почему же мы считываем данные с json файла вместо переменной такого типа:

get_image = vk_exec("wall.get", f"owner_id=-{group_id}&v=5.35&count=1&access_token={v_token}")

если мы каждый раз будем обращаться к этой переменной то на каждой проверке в блок if мы будем заново вызывать функцию. Думаете вк это любит? Нет, вк этого не любит, отсюда и это сообщение:

Слишком уж много запросов, лучше в json файл записывать.

ну и теперь будет самый главный цикл где мы всё это объединим:

def main():
	while True:
		for i in image_group:
			try:
				with open('d.json', 'w') as f:
					f.write(json.dumps({'img_f': wall_img(i)}))
				with open('d.json', 'r', encoding='utf-8') as f:
					dat = json.load(f)
				if type(dat['img_f']) is list:
					if dat['img_f'][-1] != get_last_post(i): 
						write_group_and_url_photo(dat['img_f'][0], dat['img_f'][-1])
						tb.send_photo(group_id, dat['img_f'][-1])
			except Exception as e:
				tb.send_message(admin_id, 'In main script error: \n\n' + str(e))
		print('Ahhhh... Again this ..')
		sleep(900)

Тут мы сначала запускаем бесконечный цикл, который обновляется раз в 15 минут (900 секунд), теперь мы идём циклом по группам, тут мы записываем результат выполнения функции которая либо вернет нам массив с группой и ссылкой на картинку, далее мы будем сравнивать, является ли то что нам вернула функция типом list, если это массив то мы проверяем, не совпадает ли ссылка на картинку с последней картинкой в базе, если она не совпадает то мы записываем новую картинку в базу и затем уже публикуем в канал. Если же были ошибки то бот об этом сообщит.

А теперь - как получить айди группы в вк. Выбираем любую группу, находим любой пост и нажимаем на время под названием группы у поста

Затем копируем айди, тот что после wall и до _

Ну и сам вк говорит не наглеть и не делать по этому методу больше 5к запросов в сутки

но мы то делаем не больше 500, так что всё окей (всего на 5 групп, если у вас их будет больше, посчитайте заранее количество запросов в сутки и установите через какое время будете делать запрос).

а вот и весь исходный код

import requests
import telebot
import sqlite3
from time import sleep
import json


v_token = "7l0m0v3dkl__t.me/CodingCommunity__83fglkiaguto9602p3nbrbuz67bjf4jj5pqkehivifj0kusc227qut"
tb = telebot.TeleBot("0001337000:@CodingCommunity")
admin_id = 13371337
group_id = -123123123123
image_group = [111111111, 2222222, 3333333, 44444444, 55555555]


def write_group_and_url_photo(group_id, photo_url):
	con = sqlite3.connect("group_and_photo.db")
	cur = con.cursor()
	cur.execute(f"""CREATE TABLE IF NOT EXISTS GAUP (group_id int, image_url text);""") # GAUP group and url Photo
	cur.execute(f"INSERT INTO GAUP VALUES(?,?)", (group_id, photo_url))
	con.commit()
	cur.close() 
	con.close()


def get_last_post(group_id):
	con = sqlite3.connect("group_and_photo.db")
	cur = con.cursor()	
	cur.execute(f"SELECT * FROM GAUP WHERE group_id = ?", [(group_id)])
	data = cur.fetchall()
	con.commit()
	cur.close() 
	con.close()	
	return data[-1][-1]


def vk_exec(method, param):
	URL = "https://api.vk.com/method/" + method + "?" + param
	return requests.get(URL).json()


def wall_img(group_id, offset=None):
	if offset == None:
		get_image = vk_exec("wall.get", f"owner_id=-{group_id}&v=5.35&count=1&access_token={v_token}")
	else:
		get_image = vk_exec("wall.get", f"owner_id=-{group_id}&v=5.35&offset={offset}&count=1&access_token={v_token}")
	with open('data.json', 'w') as f:
		f.write(json.dumps(get_image))
	with open('data.json', 'r', encoding='utf-8') as f:
		dat = json.load(f)
	try:
		if 'items' in dat['response']:
			if 'is_pinned' not in dat['response']['items'][0]:
				if 'attachments' in dat['response']['items'][0]:
					if 'photo' in dat['response']['items'][0]['attachments'][0] and 'album' not in dat['response']['items'][0]['attachments'][0]:
						photo = dat['response']['items'][0]['attachments'][0]['photo']['photo_604']
						return [group_id, photo]
					else:
						return 'NOIMAGE'	
				else:
					return 'NOIMAGE'
			else:
				return wall_img(image_group[0], 1)
		else:
			return 'NOIMAGE'
	except Exception as e:
		get_image = vk_exec("wall.get", f"owner_id=-{group_id}&v=5.35&count=1&access_token={v_token}")
		if get_image['error']['error_code'] == 5:
			tb.send_message(admin_id, 'Error: \n\n' + 'USER TOKEN IS BLOCKED') # если токен не валид
		elif get_image['error']['error_code'] == 6:
			tb.send_message(admin_id, 'Error: \n\n' + 'TOO MANY REQUESTS PER SECOND') # слишком много запросов в секунду
		else:
			tb.send_message(admin_id, 'Error: \n\n' + str(e))
		sleep(2)


def main():
	while True:
		for i in image_group:
			try:
				with open('d.json', 'w') as f:
					f.write(json.dumps({'img_f': wall_img(i)}))
				with open('d.json', 'r', encoding='utf-8') as f:
					dat = json.load(f)
				if type(dat['img_f']) is list:
					if dat['img_f'][-1] != get_last_post(i): # вот тут ошибка если бд пуста
						write_group_and_url_photo(dat['img_f'][0], dat['img_f'][-1])
						tb.send_photo(group_id, dat['img_f'][-1])
			except Exception as e:
				logging.debug('Exception in main script')
				tb.send_message(admin_id, 'In main script error: \n\n' + str(e))
		print('Ahhhh... Again this...')
		sleep(900)


if __name__ == '__main__':
	print('START')
	main()

Прошу меня простить за мой ужасный код, вы всегда можете модифицировать мой код и использовать как хотите, я не несу за это ответственности.

Телеграм канал: https://t.me/CodingCommunity