From bef75562ae1ea020b6df89a4a5ff41b8d9c43dec Mon Sep 17 00:00:00 2001 From: lifegpc Date: Wed, 6 Jan 2021 23:30:59 +0800 Subject: [PATCH] update --- RSSEntry.py | 23 ++-- config.py | 30 +++++ database.py | 75 ++++++++++--- rssbot.py | 294 ++++++++++++++++++++++++++++++++++++++++++++++--- rsstempdict.py | 6 +- 5 files changed, 388 insertions(+), 40 deletions(-) create mode 100644 config.py diff --git a/RSSEntry.py b/RSSEntry.py index 699f6e2..91a4007 100644 --- a/RSSEntry.py +++ b/RSSEntry.py @@ -14,6 +14,18 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . from json import loads +from config import RSSConfig + + +class ChatEntry: + def __init__(self, data=None): + self.chatId = data[0] if data is not None and data[0] is not None else None + self.id = data[1] if data is not None and data[1] is not None else None + try: + self.config = RSSConfig( + loads(data[2])) if data is not None and data[2] is not None else RSSConfig() + except: + self.config = RSSConfig() class RSSEntry: @@ -30,12 +42,7 @@ class RSSEntry: self.lastupdatetime = None if data is not None and data[3] is not None: self.interval = data[3] - self.config = None - if data is not None and data[4] is not None: - try: - self.config = loads(data[4]) - except: - pass self.id = None - if data is not None and data[5] is not None: - self.id = data[5] + if data is not None and data[4] is not None: + self.id = data[4] + self.chatList = [] diff --git a/config.py b/config.py new file mode 100644 index 0000000..3b95a19 --- /dev/null +++ b/config.py @@ -0,0 +1,30 @@ +# (C) 2021 lifegpc +# This file is part of rssbot. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +from json import dumps + + +class RSSConfig: + def __init__(self, d: dict = None): + self.disable_web_page_preview = False + self.show_RSS_title = True + self.show_Content_title = True + if d is not None: + for k in d.keys(): + if hasattr(self, k): + setattr(self, k, d[k]) + + def toJson(self): + return dumps({'disable_web_page_preview': self.disable_web_page_preview, 'show_RSS_title': self.show_RSS_title, 'show_Content_title': self.show_Content_title}, ensure_ascii=False) diff --git a/database.py b/database.py index 5a0c085..20715af 100644 --- a/database.py +++ b/database.py @@ -14,17 +14,16 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . import sqlite3 -from RSSEntry import RSSEntry +from config import RSSConfig +from RSSEntry import RSSEntry, ChatEntry from typing import List from enum import Enum, unique from threading import Lock +from hashl import sha256WithBase64 -class RSSConfig: - def __init__(self): - self.disable_web_page_preview = False - self.show_RSS_title = True - self.show_Content_title = True +def dealtext(s: str): + return s.replace("'", "''") @unique @@ -60,14 +59,14 @@ title TEXT, url TEXT, interval INT, lastupdatetime INT, -config TEXT, id TEXT, PRIMARY KEY (id) );''') - if 'userList' not in self._exist_tables: - self._db.execute('''CREATE TABLE userList ( -userId INT, -id TEXT + if 'chatList' not in self._exist_tables: + self._db.execute('''CREATE TABLE chatList ( +chatId INT, +id TEXT, +config TEXT )''') if 'userStatus' not in self._exist_tables: self._db.execute('''CREATE TABLE userStatus ( @@ -75,16 +74,12 @@ userId INT, status INT, hashd TEXT, PRIMARY KEY (userId) -)''') - if 'channelList' not in self._exist_tables: - self._db.execute('''CREATE TABLE channelList ( -channelId INT, -id TEXT )''') if 'hashList' not in self._exist_tables: self._db.execute('''CREATE TABLE hashList ( id TEXT, hash TEXT, +time INT, PRIMARY KEY (hash) )''') self._db.commit() @@ -97,17 +92,63 @@ PRIMARY KEY (hash) if not ok: self.__create_table() + def __removeRSSEntry(self, id: str) -> bool: + try: + self._db.execute(f'DELETE FROM RSSList WHERE id="{id}"') + self._db.execute(f'DELETE FROM hashList WHERE id="{id}"') + self._db.commit() + return True + except: + return False + def __write_version(self): self._db.execute( f'INSERT INTO config VALUES ({self._version[0]}, {self._version[1]}, {self._version[2]}, {self._version[3]});') self._db.commit() + def addRSSList(self, title: str, url: str, chatId: int, config: RSSConfig, ttl: int = None): + with self._value_lock: + try: + hashd = sha256WithBase64(url) + cur = self._db.execute( + f'SELECT * FROM RSSList WHERE id="{hashd}"') + has_data = False + for i in cur: + has_data = True + if has_data: + self._db.execute(f'DELETE FROM RSSList WHERE id="{hashd}"') + self._db.execute( + f"INSERT INTO RSSList VALUES ('{dealtext(title)}', '{dealtext(url)}', {ttl if ttl is not None else 'null'}, null, '{hashd}')") + cur = self._db.execute( + f'SELECT * FROM chatList WHERE id="{hashd}" AND chatId={chatId}') + has_data = False + for i in cur: + has_data = True + if has_data: + self._db.execute( + f'DELETE FROM chatList WHERE id="{hashd}" AND chatId={chatId}') + self._db.execute( + f"INSERT INTO chatList VALUES ({chatId}, '{hashd}', '{dealtext(config.toJson())}')") + self._db.commit() + return True + except: + return False + def getAllRSSList(self) -> List[RSSEntry]: with self._value_lock: cur = self._db.execute(f'SELECT * FROM RSSList;') r = [] for i in cur: - r.append(RSSEntry(i)) + temp = RSSEntry(i) + cur2 = self._db.execute( + f'SELECT * FROM chatList WHERE id="{temp.id}"') + for i2 in cur2: + temp2 = ChatEntry(i2) + temp.chatList.append(temp2) + if len(temp.chatList) == 0: + self.__removeRSSEntry(temp.id) + else: + r.append(temp) return r def getUserStatus(self, userId: int) -> (userStatus, str): diff --git a/rssbot.py b/rssbot.py index 672586d..95cdf0c 100644 --- a/rssbot.py +++ b/rssbot.py @@ -27,6 +27,7 @@ from enum import Enum from rsstempdict import rssMetaInfo, rssMetaList from random import randrange from textc import textc +from re import search, I def getMediaInfo(m: dict, config: RSSConfig = RSSConfig()) -> str: @@ -45,6 +46,10 @@ def getMediaInfo(m: dict, config: RSSConfig = RSSConfig()) -> str: s = f"""{s}\n群/频道ID:{m['chatId']}""" elif 'userId' in m and m['userId'] is not None: s = f"""{s}\n订阅的账号""" + s = f"{s}\n设置:" + s = f"{s}\n禁用预览:{config.disable_web_page_preview}" + s = f"{s}\n显示RSS标题:{config.show_RSS_title}" + s = f"{s}\n显示内容标题:{config.show_Content_title}" return s @@ -53,6 +58,11 @@ class InlineKeyBoardCallBack(Enum): SendPriview = 1 ModifyChatId = 2 BackUserId = 3 + SettingsPage = 4 + BackToNormalPage = 5 + DisableWebPagePreview = 6 + ShowRSSTitle = 7 + ShowContentTitle = 8 def getInlineKeyBoardWhenRSS(hashd: str, m: dict) -> str: @@ -69,8 +79,6 @@ def getInlineKeyBoardWhenRSS(hashd: str, m: dict) -> str: d[i].append( {'text': '修改群组/频道ID', 'callback_data': f'0,{hashd},{InlineKeyBoardCallBack.ModifyChatId.value}'}) if 'userId' in m and m['userId'] is not None: - d.append([]) - i = i + 1 d[i].append( {'text': '发送至私聊', 'callback_data': f'0,{hashd},{InlineKeyBoardCallBack.BackUserId.value}'}) elif 'userId' in m and m['userId'] is not None: @@ -78,6 +86,30 @@ def getInlineKeyBoardWhenRSS(hashd: str, m: dict) -> str: i = i + 1 d[i].append( {'text': '发送至群组/频道', 'callback_data': f'0,{hashd},{InlineKeyBoardCallBack.ModifyChatId.value}'}) + d.append([]) + i = i + 1 + d[i].append( + {'text': '设置', 'callback_data': f'0,{hashd},{InlineKeyBoardCallBack.SettingsPage.value}'}) + return {'inline_keyboard': d} + + +def getInlineKeyBoardWhenRSS2(hashd: str, config: RSSConfig) -> str: + d = [] + i = 0 + temp = '启用预览' if config.disable_web_page_preview else '禁用预览' + d.append([]) + d[i].append( + {'text': temp, 'callback_data': f'0,{hashd},{InlineKeyBoardCallBack.DisableWebPagePreview.value}'}) + temp = '隐藏RSS标题' if config.show_RSS_title else '显示RSS标题' + d[i].append( + {'text': temp, 'callback_data': f'0,{hashd},{InlineKeyBoardCallBack.ShowRSSTitle.value}'}) + d.append([]) + i = i + 1 + temp = '隐藏内容标题' if config.show_Content_title else '显示内容标题' + d[i].append( + {'text': temp, 'callback_data': f'0,{hashd},{InlineKeyBoardCallBack.ShowContentTitle.value}'}) + d[i].append( + {'text': '返回', 'callback_data': f'0,{hashd},{InlineKeyBoardCallBack.BackToNormalPage.value}'}) return {'inline_keyboard': d} @@ -108,11 +140,14 @@ class main: if config.show_RSS_title: text.addtotext(f"{escape(meta['title'])}") if config.show_Content_title and 'title' in content and content['title'] is not None and content['title'] != '': - if 'link' in content: + if 'link' in content and content['link'] is not None and content['link'] != '': text.addtotext( f"""{escape(content['title'])}""") else: text.addtotext(f"{escape(content['title'])}") + elif 'link' in content and content['link'] is not None and content['link'] != '': + text.addtotext( + f"""{escape(content['link'])}""") if 'description' in content and content['description'] is not None and content['description'] != '': text.addtotext(content['description']) @@ -193,9 +228,11 @@ class main: return -1 self._r = Session() self._me = self._request('getMe') + self._rssMetaList = rssMetaList() print(self._me) - if self._me is None: + if self._me is None or 'ok' not in self._me or not self._me['ok']: print('无法读取机器人信息') + self._me = self._me['result'] self._upi = None self._updateThread = updateThread(self) self._updateThread.start() @@ -244,10 +281,11 @@ class messageHandle(Thread): def _getCommandlinePara(self) -> List[str]: s = self._data['text'] - for i in self._data['entities']: - if i['type'] == 'bot_command': - s = s[:i['offset']] + s[i['offset']+i['length']:] - break + if 'entities' in self._data: + for i in self._data['entities']: + if i['type'] == 'bot_command': + s = s[:i['offset']] + s[i['offset']+i['length']:] + break l = s.split(' ') r = [] t = '' @@ -277,6 +315,9 @@ class messageHandle(Thread): def run(self): print(self._data) + for key in ["new_chat_members", "left_chat_member", "new_chat_title", "new_chat_photo", "delete_chat_photo", "pinned_message"]: + if key in self._data: + return self._messageId = self._data['message_id'] self._chatId = self.__getChatId() if self._chatId is None: @@ -286,10 +327,138 @@ class messageHandle(Thread): if 'text' in self._data: if 'entities' in self._data: self._botCommand = self.__getBotCommand() + self._fromUserId = self.__getFromUserId() + if self._fromUserId is not None: + di = {'chat_id': self._chatId} + if self.__getChatType() in ['supergroup', 'group'] and self._fromUserId is not None: + di['reply_to_message_id'] = self._messageId + self._userStatus, self._hashd = self._main._db.getUserStatus( + self._fromUserId) + if self._userStatus == userStatus.normalStatus: + pass + elif self._botCommand is not None and self._botCommand == '/cancle': + if self._userStatus == userStatus.needInputChatId: + di['text'] = '已取消输入群/频道ID。' + self._main._db.setUserStatus( + self._fromUserId, userStatus.normalStatus) + self._main._request('sendMessage', 'post', json=di) + return + elif self._userStatus in [userStatus.needInputChatId]: + metainfo = self._main._rssMetaList.getRSSMeta(self._hashd) + if metainfo is None: + self._main._db.setUserStatus( + self._fromUserId, userStatus.normalStatus) + di['text'] = '已过期。' + self._main._request('sendMessage', 'post', json=di) + return + elif self._userStatus == userStatus.needInputChatId: + para = self._getCommandlinePara() + chatId = None + for i in para: + if search(r'^[\+-]?[0-9]+$', i) is not None: + chatId = int(i) + break + if chatId is None: + di['text'] = '找不到ID。' + self._main._request('sendMessage', 'post', json=di) + return + di['text'] = '正在获取群/频道信息……' + re = self._main._request('sendMessage', 'post', json=di) + if re is not None and 'ok' in re and re['ok']: + re = re['result'] + di = {'chat_id': self._chatId, + 'message_id': re['message_id']} + re2 = self._main._request( + 'getChat', 'post', {'chat_id': chatId}) + if re2 is not None and 'ok' in re2 and re2['ok']: + re2 = re2['result'] + if re2['type'] == "private": + di['text'] = '该ID是私聊。' + self._main._request( + 'editMessageText', 'post', json=di) + return + di['text'] = '正在获取群/频道管理员列表……' + self._main._request( + 'editMessageText', 'post', json=di) + re3 = self._main._request( + 'getChatAdministrators', 'post', {'chat_id': chatId}) + if re3 is not None and 'ok' in re3 and re3['ok']: + re3 = re3['result'] + chatM = None + for chatMember in re3: + if chatMember['user']['id'] != self._fromUserId: + continue + if chatMember['status'] not in ['creator', 'administrator']: + continue + if re2['type'] == 'channel' and ('can_post_messages' not in chatMember or not chatMember['can_post_messages']): + continue + if re2['type'] == 'channel' and ('can_edit_messages' not in chatMember or not chatMember['can_edit_messages']): + continue + chatM = chatMember + if chatM is None: + di['text'] = '你没有权限操作。' + self._main._request( + 'editMessageText', 'post', json=di) + return + di['text'] = '正在确认机器人的权限……' + self._main._request( + 'editMessageText', 'post', json=di) + re4 = self._main._request("getChatMember", "post", { + "chat_id": chatId, "user_id": self._main._me['id']}) + if re4 is not None and 'ok' in re4 and re4['ok']: + re4 = re4['result'] + if re2['type'] == 'channel' and (re4['status'] not in ['creator', 'administrator'] or not re4['can_post_messages'] or not re4['can_edit_messages']): + di['text'] = '机器人在频道内缺少必要的权限' + self._main._request( + 'editMessageText', 'post', json=di) + return + if re2['type'] in ["group", "supergroup"]: + if re4['status'] in ['creator', 'administrator']: + pass + elif 'permissions' in re2 and re2['permissions'] is not None and (not re2['permissions']['can_send_messages'] or not re2['permissions']['can_send_media_messages'] or not re2['permissions']['can_send_other_messages'] or not re2['permissions']['can_add_web_page_previews']): + di['text'] = '机器人在群组内缺少必要的权限' + self._main._request( + 'editMessageText', 'post', json=di) + return + elif re4['status'] in ['left', 'kicked']: + di['text'] = '机器人不在群组内' if re4['status'] == 'lefy' else '机器人已被踢' + self._main._request( + 'editMessageText', 'post', json=di) + return + elif re4['status'] == 'restricted' and (not re4['can_send_messages'] or not re4['can_send_media_messages'] or not re4['can_send_other_messages'] or not re4['can_add_web_page_previews']): + di['text'] = '机器人在群组内缺少必要的权限' + self._main._request( + 'editMessageText', 'post', json=di) + return + else: + di['text'] = '获取机器人权限失败!' + self._main._request( + 'editMessageText', 'post', json=di) + return + else: + di['text'] = '获取群/频道信息失败!' + self._main._request( + 'editMessageText', 'post', json=di) + return + metainfo.meta['chatId'] = chatId + metainfo.flushTime() + di['text'] = '修改完成!' + self._main._request('editMessageText', 'post', json=di) + di = {'chat_id': metainfo.chatId, + 'message_id': metainfo.messageId} + di['text'] = getMediaInfo( + metainfo.meta, metainfo.config) + di['parse_mode'] = 'HTML' + di['disable_web_page_preview'] = True + di['reply_markup'] = getInlineKeyBoardWhenRSS( + self._hashd, metainfo.meta) + self._main._request('editMessageText', 'post', json=di) + self._main._db.setUserStatus( + self._fromUserId, userStatus.normalStatus) + return if self._botCommand is None or self._botCommand not in ['/help', '/rss']: self._botCommand = '/help' di = {'chat_id': self._chatId} - self._fromUserId = self.__getFromUserId() if self.__getChatType() in ['supergroup', 'group'] and self._fromUserId is not None: di['reply_to_message_id'] = self._messageId if self._botCommand == '/help': @@ -336,7 +505,7 @@ class messageHandle(Thread): di['reply_markup'] = getInlineKeyBoardWhenRSS( self._hash, media) self._main._rssMetaList.addRSSMeta(rssMetaInfo( - re['message_id'], media, p.itemList, self._hash)) + re['message_id'], chatId, media, p.itemList, self._hash)) self._main._request('editMessageText', 'post', json=di) @@ -346,7 +515,7 @@ class callbackQueryHandle(Thread): self._main = main self._data = data - def answer(self, text: str): + def answer(self, text: str = ''): di = {} di['callback_query_id'] = self._callbackQueryId di['text'] = text @@ -376,12 +545,31 @@ class callbackQueryHandle(Thread): if self._rssMeta is None: self.answer('找不到数据。可能已经超时。') return + self._rssMeta.flushTime() self._userId = None if 'userId' in self._rssMeta.meta and self._rssMeta.meta['userId'] is not None: self._userId = self._rssMeta.meta['userId'] if self._userId is not None and self._data['from']['id'] != self._userId: self.answer('你没有权限操作。') - if self._inlineKeyBoardCommand == InlineKeyBoardCallBack.SendPriview: + if self._inlineKeyBoardCommand == InlineKeyBoardCallBack.Subscribe: + title = self._rssMeta.meta['title'] + url = self._rssMeta.meta['url'] + chatId = None + if 'chatId' in self._rssMeta.meta and self._rssMeta.meta['chatId'] is not None: + chatId = self._rssMeta.meta['chatId'] + elif self._userId is not None: + chatId = self._userId + if chatId is None: + self.answer('缺少发送的位置') + return + config = self._rssMeta.config + ttl = self._rssMeta.meta['ttl'] if 'ttl' in self._rssMeta.meta else None + suc = self._main._db.addRSSList(title, url, chatId, config, ttl) + if suc: + self.answer('订阅成功!') + else: + self.answer('订阅失败!') + elif self._inlineKeyBoardCommand == InlineKeyBoardCallBack.SendPriview: chatId = None if 'chatId' in self._rssMeta.meta and self._rssMeta.meta['chatId'] is not None: chatId = self._rssMeta.meta['chatId'] @@ -402,14 +590,92 @@ class callbackQueryHandle(Thread): self.answer(f'第{ran}条发送失败!') return elif self._userId is not None and self._inlineKeyBoardCommand == InlineKeyBoardCallBack.ModifyChatId: - self._main._db.setUserStatus(self._userId, userStatus.needInputChatId, self._hashd) + self._main._db.setUserStatus( + self._userId, userStatus.needInputChatId, self._hashd) di = {} if 'message' in self._data and self._data['message'] is not None: di['chat_id'] = self._data['message']['chat']['id'] else: di['chat_id'] = self._data['from']['id'] - di["text"] = "请输入群/频道的ID" + di["text"] = "请输入群/频道的ID(使用 /cancle 可以取消):" self._main._request("sendMessage", "post", json=di) + self.answer() + return + elif self._userId is not None and self._inlineKeyBoardCommand == InlineKeyBoardCallBack.BackUserId: + self._rssMeta.meta['chatId'] = None + di = {'chat_id': self._rssMeta.chatId, + 'message_id': self._rssMeta.messageId} + di['text'] = getMediaInfo( + self._rssMeta.meta, self._rssMeta.config) + di['parse_mode'] = 'HTML' + di['disable_web_page_preview'] = True + di['reply_markup'] = getInlineKeyBoardWhenRSS( + self._hashd, self._rssMeta.meta) + self._main._request("editMessageText", "post", json=di) + self.answer() + return + elif self._inlineKeyBoardCommand == InlineKeyBoardCallBack.SettingsPage: + di = {'chat_id': self._rssMeta.chatId, + 'message_id': self._rssMeta.messageId} + di['text'] = getMediaInfo( + self._rssMeta.meta, self._rssMeta.config) + di['parse_mode'] = 'HTML' + di['disable_web_page_preview'] = True + di['reply_markup'] = getInlineKeyBoardWhenRSS2( + self._hashd, self._rssMeta.config) + self._main._request("editMessageText", "post", json=di) + self.answer() + return + elif self._inlineKeyBoardCommand == InlineKeyBoardCallBack.BackToNormalPage: + di = {'chat_id': self._rssMeta.chatId, + 'message_id': self._rssMeta.messageId} + di['text'] = getMediaInfo( + self._rssMeta.meta, self._rssMeta.config) + di['parse_mode'] = 'HTML' + di['disable_web_page_preview'] = True + di['reply_markup'] = getInlineKeyBoardWhenRSS( + self._hashd, self._rssMeta.meta) + self._main._request("editMessageText", "post", json=di) + self.answer() + return + elif self._inlineKeyBoardCommand == InlineKeyBoardCallBack.DisableWebPagePreview: + self._rssMeta.config.disable_web_page_preview = not self._rssMeta.config.disable_web_page_preview + di = {'chat_id': self._rssMeta.chatId, + 'message_id': self._rssMeta.messageId} + di['text'] = getMediaInfo( + self._rssMeta.meta, self._rssMeta.config) + di['parse_mode'] = 'HTML' + di['disable_web_page_preview'] = True + di['reply_markup'] = getInlineKeyBoardWhenRSS2( + self._hashd, self._rssMeta.config) + self._main._request("editMessageText", "post", json=di) + self.answer() + return + elif self._inlineKeyBoardCommand == InlineKeyBoardCallBack.ShowRSSTitle: + self._rssMeta.config.show_RSS_title = not self._rssMeta.config.show_RSS_title + di = {'chat_id': self._rssMeta.chatId, + 'message_id': self._rssMeta.messageId} + di['text'] = getMediaInfo( + self._rssMeta.meta, self._rssMeta.config) + di['parse_mode'] = 'HTML' + di['disable_web_page_preview'] = True + di['reply_markup'] = getInlineKeyBoardWhenRSS2( + self._hashd, self._rssMeta.config) + self._main._request("editMessageText", "post", json=di) + self.answer() + return + elif self._inlineKeyBoardCommand == InlineKeyBoardCallBack.ShowContentTitle: + self._rssMeta.config.show_Content_title = not self._rssMeta.config.show_Content_title + di = {'chat_id': self._rssMeta.chatId, + 'message_id': self._rssMeta.messageId} + di['text'] = getMediaInfo( + self._rssMeta.meta, self._rssMeta.config) + di['parse_mode'] = 'HTML' + di['disable_web_page_preview'] = True + di['reply_markup'] = getInlineKeyBoardWhenRSS2( + self._hashd, self._rssMeta.config) + self._main._request("editMessageText", "post", json=di) + self.answer() return else: self.answer('未知的按钮。') diff --git a/rsstempdict.py b/rsstempdict.py index 2267394..9a621ad 100644 --- a/rsstempdict.py +++ b/rsstempdict.py @@ -18,8 +18,9 @@ from database import RSSConfig class rssMetaInfo: - def __init__(self, messageId: int, meta: dict, itemList: list, hashd: str, config: RSSConfig = None): + def __init__(self, messageId: int, chatId: int, meta: dict, itemList: list, hashd: str, config: RSSConfig = None): self.messageId = messageId + self.chatId = chatId self.meta = meta self.itemList = itemList self.hashd = hashd @@ -29,6 +30,9 @@ class rssMetaInfo: def isTimeOut(self): return True if time() - self.__time > 300 else False + def flushTime(self): + self.__time = time() + class rssMetaList: def __init__(self):