При запуске бота вылазит ошибка

Пытаюсь организовать регистрацию пользователей через Google таблицу.

Создал проект, добавил/включил Google Sheets API через Service Accounts, создал API ключ в формате .json

import logging
import aiogram.types as types
import asyncio
import csv
import re
import gspread

from aiogram.utils import executor
from aiogram.contrib.fsm_storage.memory import MemoryStorage
from aiogram.dispatcher.filters import Text
from aiogram.dispatcher.filters.state import State, StatesGroup
from aiogram.types import ReplyKeyboardMarkup, KeyboardButton
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
from aiogram.dispatcher import FSMContext
from aiogram import Bot, Dispatcher
from oauth2client.service_account import ServiceAccountCredentials


logging.basicConfig(level=logging.INFO)

SCOPE = ['https://spreadsheets.google.com/feeds', 'https://www.googleapis.com/auth/drive']
SERVICE_ACCOUNT_FILE = r"credentials.json"  # путь к загруженному сервисному ключу
SPREADSHEET_ID = ''  # идентификатор таблицы Google Sheets
API_TOKEN = ''
ADMIN_CHAT_ID = ''


creds = ServiceAccountCredentials.from_json_keyfile_name(SERVICE_ACCOUNT_FILE, SCOPE)
client = gspread.authorize(creds)
sheet = client.open_by_key(SPREADSHEET_ID).sheet1

loop = asyncio.get_event_loop()
asyncio.set_event_loop(loop)
bot = Bot(token=API_TOKEN, parse_mode=types.ParseMode.HTML)
storage = MemoryStorage()
dp = Dispatcher(bot, storage=storage, loop=loop)


class FAQ(StatesGroup):
    waiting_for_question = State()


class Registration(StatesGroup):
    waiting_for_first_name = State()
    waiting_for_last_name = State()
    waiting_for_dob = State()
    waiting_for_email = State()
    waiting_for_phone = State()


FAQ_FILE = "faq.csv"


def read_faq():
    with open(FAQ_FILE, 'r', encoding='utf-8') as f:
        reader = csv.reader(f)
        faq = {}
        for row in reader:
            faq[row[0]] = row[1]
        return faq


def write_faq(faq):
    with open(FAQ_FILE, 'w', encoding='utf-8', newline='') as f:
        writer = csv.writer(f)
        for q, a in faq.items():
            writer.writerow([q, a])


@dp.message_handler(commands=['start', 'menu'])
async def welcome(message: types.Message):
    """
    This handler will be called when user sends /start command or taps on the Start button
    """
    keyboard = ReplyKeyboardMarkup(resize_keyboard=True)
    start_button = KeyboardButton('Start', callback_data='start')
    faq_button = KeyboardButton('Узнать больше')
    register_button = KeyboardButton('Хочу принять участие')
    website_button = KeyboardButton('Посетить веб-сайт')
    # send_data_button = KeyboardButton('Send Data', callback_data='send_data')
    question_button = KeyboardButton('Задать вопрос', callback_data='question')
    keyboard.add(start_button, faq_button, register_button, website_button, question_button)

    # Send the welcome message with both keyboards
    await message.answer(f"Приветствуем в UWC (United World Colleges).\n\nЧемя могу помочь?", reply_markup=keyboard)


@dp.callback_query_handler(lambda c: c.data == 'start')
async def process_start_callback(callback_query: types.CallbackQuery, state: FSMContext):
    # Stop the current handler chain and clear the state
    await state.finish()
    await bot.answer_callback_query(callback_query.id)

    # Call the /start command handler
    message = types.Message.to_me()
    message.text = "/start"
    await dp.process_update(types.Update.to_update({
        'message': message.to_python()
    }))


@dp.callback_query_handler(lambda c: c.data == 'question')
async def process_question_callback(callback_query: types.CallbackQuery):
    await bot.send_message(callback_query.from_user.id, 'Введите свой вопрос:')
    await FAQ.waiting_for_question.set()
    await bot.answer_callback_query(callback_query.id)

    # Get the user's message and send it to the administrator
    message = f"New question from user {callback_query.from_user.id}:\n{callback_query.message.text}"
    await bot.send_message(ADMIN_CHAT_ID, message)
 @dp.message_handler(Text(equals='Посетить веб-сайт'))
async def visit_website(message: types.Message):
    """
    This handler will be called when user taps on the Visit Website button
    """
    await message.answer(WEBSITE_URL)


name_pattern = r'^[a-zA-Zа-яА-ЯґҐєЄіІїЇ]+(([\`\'\,\.\- ][a-zA-Zа-яА-Я ])?[a-zA-Zа-яА-Я]*)*$'
dob_pattern = r'^\d{2}.\d{2}.\d{4}$'
email_pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
phone_pattern = re.compile(r'^\+\d{12}$')  # 12 digits after '+'
registration_keyboard = ReplyKeyboardMarkup(
    keyboard=[
        [
            KeyboardButton("Имя"),
            KeyboardButton("Фамилия")
        ],
        [
            KeyboardButton("Дата рождения")
        ],
        [
            KeyboardButton("Email адрес")
        ],
        [
            KeyboardButton("Номер телефона")
        ]
    ],
    resize_keyboard=True
)


async def save_data(state: FSMContext):
    # Получение данных из состояния
    user_data = await state.get_data()

    # Запись данных в Google таблицу
    row = [user_data.get("first_name"), user_data.get("last_name"), user_data.get("dob"),
        user_data.get("email"), user_data.get("phone")]
    sheet.insert_row(row, 2)



@dp.message_handler(Text(equals='Хочу принять участие'))  # Handle the registration button
async def register(message: types.Message):
    # Ask for the user's first name
    await message.answer("Пожалуйста, введите ваше имя:")
    await Registration.waiting_for_first_name.set()

@dp.message_handler(state=Registration.waiting_for_first_name)
async def process_phone(message: types.Message, state: FSMContext):
    first_name = message.text.strip()
    if not re.match(name_pattern, first_name):
        await message.answer("Неверный ввод. Пожалуйста введите корректно ваше имя:")
        return

    await state.update_data(first_name=first_name)
    await save_data(state)

    await message.answer("Пожалуйста, введите вашу фамилию:")
    await Registration.waiting_for_last_name.set()


@dp.message_handler(state=Registration.waiting_for_last_name)
async def process_phone(message: types.Message, state: FSMContext):
    last_name = message.text.strip()
    if not re.match(name_pattern, last_name):
        await message.answer("Неверный ввод. Пожалуйста введите корректно вашу фамилию:")
        return

    await state.update_data(last_name=last_name)
    await save_data(state)

        # Ask for the user's date of birth
    await message.answer("Пожалуйста, введите дату вашего рождения (ДД.ММ.ГГ):")
    await Registration.waiting_for_dob.set()


@dp.message_handler(state=Registration.waiting_for_dob)
async def process_phone(message: types.Message, state: FSMContext):
    dob = message.text.strip()
    if not re.match(dob_pattern, dob):
        await message.answer("Неверный ввод. Пожалуйста введите корректно дату вашего рождения (ДД.ММ.ГГ):")
        return

    await state.update_data(dob=dob)
    await save_data(state)

        # Ask for the user's email address
    await message.answer("Пожалуйста, введите ваш email адрес:")
    await Registration.waiting_for_email.set()


@dp.message_handler(state=Registration.waiting_for_email)
async def process_phone(message: types.Message, state: FSMContext):
    email = message.text.strip()
    if not re.match(email_pattern, email):
        await message.answer("Неверный ввод. Пожалуйста введите корректно email адрес:")
        return

    await state.update_data(email=email)
    await save_data(state)


        # Ask for the user's phone number
    await message.answer("Пожалуйста, введите номер вашего телефона (+XXXXXXXXXXXX):")
    await Registration.waiting_for_phone.set()

 
@dp.message_handler(state=Registration.waiting_for_phone)
async def process_phone(message: types.Message, state: FSMContext):
    phone = message.text.strip()
    if not re.match(phone_pattern, phone):
        await message.answer("Пожалуйста, введите корректно номер вашего телефона в формате +XXXXXXXXXXXX.")
        return

    await state.update_data(phone=phone)
    await save_data(state)  # Сохранение данных в Google таблицу
    await message.answer("Спасибо за регистрацию!")
    await state.finish()

@dp.message_handler(lambda message: message.text == 'FAQ')
async def faq_message(message: types.Message):
    faq = read_faq()
    keyboard = types.InlineKeyboardMarkup()
    for q in faq.keys():
        keyboard.add(types.InlineKeyboardButton(q, callback_data=q))
    await message.answer('Here are some frequently asked questions:', reply_markup=keyboard)


@dp.callback_query_handler(lambda c: c.data in read_faq().keys())
async def faq_callback(callback_query: types.CallbackQuery):
    faq = read_faq()
    answer = faq[callback_query.data]
    await bot.answer_callback_query(callback_query.id)
    await bot.send_message(callback_query.from_user.id, answer)


@dp.message_handler(commands=['add_question'], chat_id=ADMIN_CHAT_ID)
async def add_question_handler(message: types.Message):
    await message.answer("Введите свой вопрос и ответ на него в формате 'Вопрос? Ответ!'")
    await FAQ.waiting_for_question.set()


@dp.message_handler(state=FAQ.waiting_for_question, chat_id=ADMIN_CHAT_ID)
async def process_question_answer(message: types.Message, state: FSMContext):
    faq = read_faq()
    question_answer = message.text.split("? ")
    if len(question_answer) == 2:
        question = question_answer[0] + "?"
        answer = question_answer[1]
        faq[question] = answer
        write_faq(faq)
        await state.finish()
        await message.answer("Спасибо, ваш вопрос добавлен в базу.")
    else:
        await message.answer("Некорректный формат ввода. Попробуйте еще раз.")
if __name__ == '__main__':
    asyncio.run(executor.start_polling(dp, skip_updates=True))

Возможно, где-то нужно дополнительно расшарить доступ к таблице:

*raceback (most recent call last):
  File "C:\Users\user\Desktop\test_bot\main.py", line 30, in <module>
    sheet = client.open_by_key(SPREADSHEET_ID).sheet1
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\user\Desktop\test_bot\venv\Lib\site-packages\gspread\client.py", line 170, in open_by_key
    return Spreadsheet(self, {"id": key})
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\user\Desktop\test_bot\venv\Lib\site-packages\gspread\spreadsheet.py", line 34, in __init__
    metadata = self.fetch_sheet_metadata()
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\user\Desktop\test_bot\venv\Lib\site-packages\gspread\spreadsheet.py", line 254, in fetch_sheet_metadata
    r = self.client.request("get", url, params=params)
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\user\Desktop\test_bot\venv\Lib\site-packages\gspread\client.py", line 92, in request
    raise APIError(response)
gspread.exceptions.APIError: {'code': 403, 'message': 'The caller does not have permission', 'status': 'PERMISSION_DENIED'}

Process finished with exit code 1

Помогите пожалуйста исправить.


Ответы (0 шт):