import os

from 底层脚本.数据连接 import 连接数据库
from 底层脚本.爬虫 import 爬虫常用配置
import datetime
# 弄个进度条库
from tqdm import tqdm, trange
# 线程池
from concurrent.futures import ThreadPoolExecutor

from 底层脚本.配置 import 当前日期, 项目_目录
from argparse import ArgumentParser

命令 = ArgumentParser(
    prog='寻觅_漏洞查询脚本',
    usage=
    """
    针对edu写的一个扫描器,可以扫描一些返回内容比较特殊的网站的后台。
    比如下面这个网站无论访问什么页面返回值都是200,但页面会显示一个404的图片,可以使用特征过滤掉显示这个图片的网站,那些不显示这个图片的网站。
    python .\edu.cn专用脚本.py --域名 http://bhi.edu.cn/  --路径文件 数据/常用后台地址/DIR.txt --失败特征 'src=\\"/images/fof.jpg\\"'
    注: 如果特征或者其他参数中如果带双引号,需要用单引号将整个参数引起来,并且在双引号前加入反斜线转义!!!
    比如: src="/images/fof.jpg"需要写成  'src=\\"/images/fof.jpg\\"'
    """,
    description="存活状态 1存活, 0关闭(教育网存活,非教育网不存活为1, 反之为0)",
    add_help=False
)

选填命令 = 命令.add_argument_group('选填命令')
选填命令.add_argument('-y', '--域名', default=None, help='手动指定目标(若不指定目标,默认会使用所有已知的存活edu.cn)')
选填命令.add_argument('-yw', '--域名文件', default=None, help='指定目标文件')
选填命令.add_argument('-l', '--路径', default=None, help='路径(多个路径之间用逗号隔开)')
选填命令.add_argument('-lw', '--路径文件', default=None, help='需要测试路径的文件')
选填命令.add_argument('-s', '--数据', default=None, help='指定文件名')
选填命令.add_argument('-q', '--请求方式', default="get", help='默认使用get,如果想加快速度建议使用head(head作用和get类似但只会获取目标响应头)')
选填命令.add_argument('-ct', '--成功特征', default=None, help='指定成功标志后,只会返回出现了成功标志的一些网站')
选填命令.add_argument('-st', '--失败特征', default=None, help='返回值中会过滤带失败的标志的目标')
其他命令 = 命令.add_argument_group('其他命令')
其他命令.add_argument('-h', '--帮助', action='help', help='查看帮助信息')


class 数据库操作:
    def __init__(self, SQL语句=None):
        self.数据库 = 连接数据库(游标=False)
        self.数据库游标 = 连接数据库()
        self.SQL语句 = SQL语句
        self.数据库游标.execute("SELECT count(*) FROM `cn.edu数据`")
        self.数据库长度 = self.数据库游标.fetchone()[0]

    def 所有数据(self):
        for i in trange(self.数据库长度, desc='网站存活率检测进度: '):
            self.数据库游标.execute(f"SELECT * FROM `cn.edu数据` WHERE `id` = '{i + 1}'")

            数据 = self.数据库游标.fetchone()
            # 小记录: 这里原本返回值用的是`self.数据库游标`, 但是这样`self.数据库游标`都会指向同一个地方,会导致一些数据丢失!
            yield 数据

    def 存活网站地址(self, 网站路径=('/',)):
        for i in trange(self.数据库长度, desc='检测进度: '):
            self.数据库游标.execute(f"SELECT * FROM `cn.edu数据` WHERE `id` = '{i + 1}' and `存活状态` = '1'")
            数据 = self.数据库游标.fetchone()
            if 数据:
                for 单个路径 in 网站路径:
                    if 数据[7] and 数据[7] != "无":
                        url = 数据[7].rstrip("/")
                        目标 = url + 单个路径
                        yield 目标
                    if 数据[6] and 数据[6] != "无":
                        url = 数据[6].rstrip("/")
                        目标 = url + 单个路径
                        yield 目标

    def 存活信息保存(self, sql或id, 存活状态=1):
        数据库游标 = self.数据库.cursor()
        try:
            try:
                int(sql或id)
                sql存储语句 = [
                    "UPDATE `cn.edu数据` SET `存活状态` = '%s' WHERE `id` = '%s'" % (存活状态, sql或id),
                    "UPDATE `网安数据记录`.`cn.edu数据` SET `存活检查日期` = '%s' WHERE `id` = '%s'" % (当前日期, sql或id)
                ]
            except:
                if isinstance(sql或id, list):
                    sql存储语句 = sql或id
                else:
                    sql存储语句 = [sql或id]
            for i in sql存储语句:
                数据库游标.execute(i)
            self.数据库.commit()
        except Exception as 异常:
            print('[数据库]保存数据失败!', "错误信息", {repr(异常)})


def 网站存活检测(网址):
    try:
        爬虫数据 = 爬虫常用配置().运行(网址)
        if 爬虫数据["状态码"] != "200":
            return True, {"网址": 网址, "错误信息": f"网站标题: {爬虫数据['网页标题']}", "备注": 爬虫数据.get("备注")}
        else:
            return False, {"网址": 网址, "错误信息": f"状态码异常: {爬虫数据['状态码']}", "备注": 爬虫数据.get("备注")}
    except Exception as 异常:
        return False, {"网址": 网址, "错误信息": {repr(异常)}}


class 多线程网站存活记录:
    # 推荐线程数范围为[cpu数量*2:cpu数量*5]
    def __init__(self, 线程数=32):
        self.问题网站记录 = []
        self._线程数 = 线程数

    def __信息输出(self, 网站信息):
        正常_网站 = {"数据库id": 网站信息[0], "学校名称": 网站信息[1]}
        数据 = []
        if 网站信息[7] and 网站信息[7] != "无":
            ok, 抓取信息 = 网站存活检测(网站信息[7])
            if ok:
                正常_网站["非教育网地址"] = 网站信息[7]
            else:
                数据.append(抓取信息)
        if 网站信息[6] and 网站信息[6] != "无":
            ok, 抓取信息 = 网站存活检测(网站信息[6])
            if ok:
                正常_网站["教育网地址"] = 网站信息[6]
            else:
                数据.append(抓取信息)
        if ok:
            数据库操作().存活信息保存(正常_网站['数据库id'])
        else:
            数据库操作().存活信息保存(正常_网站['数据库id'], 存活状态=0)
        if 数据:
            问题_网站 = {"数据库id": 网站信息[0], "学校名称": 网站信息[1], "问题信息": 数据}
            self.问题网站记录.append(问题_网站)
        return 正常_网站

    def 运行(self, 保存_错误信息=False):
        print("扫描时请关闭梯子,以免影响结果!")
        线程池_存活测试 = ThreadPoolExecutor(max_workers=self._线程数)
        线程池_存活测试.map(self.__信息输出, 数据库操作().所有数据())
        print('存活扫描数据包发生完成,发现{}个失效网站!'.format(len(self.问题网站记录)))
        if 保存_错误信息:
            with open("edu无法访问网站数据({}).txt".format(当前日期), "a+") as 写入:
                保存记录 = tqdm([str(i) for i in self.问题网站记录])
                for i in 保存记录:
                    保存记录.set_description('保存进度: ' + i)
                    写入.write(i)
            print('生成文件中!!!')
        return True


def 多线程域名访问测试(访问方式='get', 数据=None, 域名: tuple = (), 路径: tuple = (), 特征: tuple = None, 线程数: int = 16):
    if 域名 != ():
        url = []
        for 一个_域名 in 域名:
            for 一个_路径 in 路径:
                url.append(一个_域名.rstrip("/") + 一个_路径)
        url = (i for i in url)
    else:
        url = 数据库操作().存活网站地址(路径)
    if 数据:
        线程池_域名访问 = ThreadPoolExecutor(线程数)
        线程池_域名访问.map(爬虫常用配置(访问方式, 数据, 网页内容=True, 数据保存=True, 特征识别=特征).运行, url)
    else:
        线程池_域名访问 = ThreadPoolExecutor(线程数)
        线程池_域名访问.map(爬虫常用配置(访问方式, 网页内容=True, 数据保存=True, 特征识别=特征).运行, url)


def 转元祖(数据, 文件=False):
    if 数据:
        if 文件:
            try:
                with open(数据) as 文件_数据:
                    return tuple([i.replace('\n', '') for i in 文件_数据.readlines()])
            except:
                print("未找到目标文件:", {数据})
                return ()
        else:
            return tuple(数据.split(','))
    else:
        return ()


if __name__ == '__main__':
    # 多线程网站存活记录().运行(保存_错误信息=True)
    用户输入 = 命令.parse_args()
    获取_域名 = 转元祖(用户输入.域名) + 转元祖(用户输入.域名文件, True) if 用户输入.域名 or 用户输入.域名文件 else None
    获取_路径 = 转元祖(用户输入.路径) + 转元祖(用户输入.路径文件, True) if 用户输入.路径 or 用户输入.路径文件 else None
    if 用户输入.成功特征 or 用户输入.失败特征:
        if 用户输入.成功特征:
            获取_特征 = (True, 用户输入.成功特征)
        else:
            获取_特征 = (False, 用户输入.失败特征)
    else:
        获取_特征 = None
    # with open(os.path.join(项目_目录, "数据\\常用后台地址\\DIR.txt")) as 常用路径_数据:
    #     常用路径_列表 = [i.replace('\n', '') for i in 常用路径_数据.readlines()]
    # 多线程域名访问测试(域名=("http://bhi.edu.cn/", ), 路径=tuple(常用路径_列表), 特征=(False, 'src="/images/fof.jpg"'))
    多线程域名访问测试(用户输入.请求方式, 用户输入.数据, 获取_域名, 获取_路径, 获取_特征)
    # print(用户输入.请求方式, 用户输入.数据, 获取_域名, 获取_路径, 获取_特征)
    pass
