Вопрос: Как правильно реализовать ООП python?

Лучший ответ: Тут нет ООП. Набор функций (привет, процедурное программирование) зачем-то сгруппирован в класс, у которого инстанцируется единственный экземпляр.

Худший ответ: import telebot
import requests
import pprint
import time

botToken = "token"
chanelName = "@wo"

class Bot:
  def __init__(self,nameChanel,token):
    # todo: проверьте pep8: форматирование (пробелы, indent=4), нейминг (CamelCase vs snake_case)
    self.name = nameChanel
    self.bot = telebot.TeleBot(token)
    # todo: Неадекватное имя bot. Вызывает путаницу в связи с именем класса Bot
    #self.source = source

  def getIDGroup(self,str):
    # todo: str - это встроенный тип, по смыслу должно быть ясно, что можно итерировать
    arr = []
    # todo: Неадекватное имя arr. Имя должно указывать не на тип, а на суть того, что поименовано.
    for nameGroup in str:
      url = f"url"  # todo: Зачем тут форматная строка, если нет переменных? Видимо ошибка.
      response = requests.get(url).json()['response']['object_id']
      # todo: Где проверка ответа на ошибки?
      # todo: Неадекватное имя. Назовите object_id
      arr.extend(self._getPostsGroup(response))
    return self._editPosts(arr)

  def _getPostsGroup(self,number):
    # todo: Неадекватное имя number. Назовите group_id 
    number = str(number)
    url = f"url"
    response = requests.get(url).json()['response']['items']
    # todo: Неадекватное имя. Назовите object_id
    return response

  def _editPosts(self,posts):
    with open('id.txt','r') as file:
      list = file.read().split()
      # todo: имя переменной перекрывает встроенный тип, не отражает содержимое
      # todo: Зачем read и split, если можно list(file) и идентификаторы хранить в строках?
      file.close()
      # todo: Зачем close, если with?!
    
    for item in posts:
      count = list.count(str(item['id']))
      # todo: Зачем вам тут count, если нужна лишь проверка на вхождение?
      if count:
        continue
      photo = ""
      # todo: почему не None?
      for img in item['attachments'][0]['photo']['sizes']:
        if img['height'] > 300:
          photo = img['url']
          break
      text = item['text']

      self._sendMessageBot(text,photo)
      with open('id.txt','a') as file:
        # todo: ну использовали бы уже хотя бы shelve
        file.write(str(item['id'])+' ')
        file.close()
        # todo: опять close


  def _sendMessageBot(self,text,photo):
    if photo:
      if text:
        self.bot.send_photo(self.name,photo,caption=text)
        return
      else:
        self.bot.send_photo(self.name,photo)
    if text:
      self.bot.send_message(self.name,text)

    # Функция могла выглядеть так:
    # if photo:
    #   self.bot.send_photo(self.name, photo, caption=text)
    # elif text:
    #   self.bot.send_message(self.name, text)
    # Но лучше не допускать вызова с пустыми аргументами. Это бессмысленно и плохо

bot = Bot(chanelName, botToken)
# вот как понимать такое: bot.bot? Нейминг!!!
posts = bot.getIDGroup() #name of group
Зачем каждый раз читать и писать файл, когда у вас бот в памяти остаётся?
По меньшей мере вы можете хотя бы не читать каждый раз, сохраняя идентификаторы в множестве памяти (кэш), а загружать это множество из файла только при старте бота. Писать можно и так. Но у вас функции не соответствуют принципу single responsibility (почитайте SOLID)

Всё плохо. Прежде чем думать об ООП почитайте хотя бы туториал по питону или любую книжку Питон для чайников.
Хороший тон давать код на ревью в гитхабе. Там можно удобно, с подсветкой синтаксиса и нумерацией строк прокомментировать весь ваш ... код, а вы сможете его потом итеративно улучшать и мне не придётся заново убеждаться, что вы учли все замеченные проблемы при повторном ревью.
Вот бесит прям. Пожалуй не буду больше ревьюить первые потуги настолько самоуверенных программистов. Просто не вежливо дёргать сообщество не почитав даже основы.