├── NUAAiCal ├── __init__.py ├── tests │ ├── __init__.py │ ├── test_main.py │ ├── test_FFDoS.py │ └── test_GenerateICS.py ├── settings.py ├── entry.py ├── Lesson.py ├── FindFirstDayofSemester.py ├── AddToGCal.py ├── GenerateICS.py └── main.py ├── _config.yml ├── MANIFEST.in ├── setup.cfg ├── .travis.yml ├── .gitignore ├── requirement2.txt ├── tox.ini ├── README.rst ├── requirement3.txt ├── LICENSE.md ├── README_zh-hans.md ├── setup.py └── README.md /NUAAiCal/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /NUAAiCal/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | # Include the license file 2 | include LICENSE.md -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal=1 3 | 4 | [aliases] 5 | test=pytest -------------------------------------------------------------------------------- /NUAAiCal/settings.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | DEBUG = False 4 | 5 | VERSION = '0.5.5' 6 | -------------------------------------------------------------------------------- /NUAAiCal/entry.py: -------------------------------------------------------------------------------- 1 | from NUAAiCal.main import main 2 | 3 | if __name__ == '__main__': 4 | main() 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | - "3.6" 5 | install: 6 | script: 7 | - python setup.py test 8 | -------------------------------------------------------------------------------- /NUAAiCal/tests/test_main.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | 3 | from __future__ import unicode_literals 4 | 5 | import pytest 6 | from NUAAiCal.main import main 7 | 8 | 9 | class TestMain: 10 | pass 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.py[cod] 3 | __pycache__ 4 | py3.6 5 | py2.7 6 | target 7 | client_secret.json 8 | NUAAiCal.egg-info/ 9 | build/ 10 | dist/ 11 | NUAAiCal-Data/ 12 | .eggs 13 | .pytest_cache 14 | .tox 15 | .vscode 16 | -------------------------------------------------------------------------------- /requirement2.txt: -------------------------------------------------------------------------------- 1 | appdirs==1.4.3 2 | cached-property==1.4.0 3 | certifi==2018.1.18 4 | chardet==3.0.4 5 | defusedxml==0.5.0 6 | future==0.16.0 7 | icalendar==4.0.1 8 | idna==2.6 9 | isodate==0.6.0 10 | lxml 11 | NUAAiCal==0.3.6 12 | python-dateutil==2.6.1 13 | pytz==2018.3 14 | requests==2.18.4 15 | requests-toolbelt==0.8.0 16 | six==1.11.0 17 | urllib3==1.22 18 | zeep==2.5.0 19 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | # tox (https://tox.readthedocs.io/) is a tool for running tests 2 | # in multiple virtualenvs. This configuration file will run the 3 | # test suite on all supported python versions. To use it, "pip install tox" 4 | # and then run "tox" from this directory. 5 | 6 | [tox] 7 | envlist = py27, py36 8 | 9 | [testenv] 10 | commands = 11 | python setup.py install 12 | pytest --pyargs NUAAiCal 13 | deps = 14 | pytest 15 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | NUAA iCal Python 2 | ================ 3 | 4 | Quick start 5 | -------- 6 | 7 | Install dependencies:: 8 | 9 | $ pip install -r requirement.txt 10 | 11 | Start the application:: 12 | 13 | $ python3 main.py 14 | 15 | Your curriculum will in `target/curriculum.ics`, you can import it to Google 16 | Calendar etc. 17 | 18 | Copyright 19 | ---------- 20 | 21 | Copyright (c) 2018 `TripleZ `_ 22 | -------------------------------------------------------------------------------- /NUAAiCal/tests/test_FFDoS.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | 3 | from __future__ import unicode_literals 4 | 5 | from NUAAiCal.FindFirstDayofSemester import get_semester_start_date 6 | import pytest 7 | from zeep import Client 8 | from datetime import datetime 9 | from pytz import timezone 10 | 11 | 12 | class TestFFDoS: 13 | def test_get_semester_start_date(self): 14 | client = Client( 15 | 'http://ded.nuaa.edu.cn/NetEa/Services/WebService.asmx?WSDL') 16 | assert get_semester_start_date([2017, 2018], '2', client) == \ 17 | datetime(2018, 2, 26, 0, 0, 0, tzinfo=timezone('Asia/Shanghai')) 18 | -------------------------------------------------------------------------------- /requirement3.txt: -------------------------------------------------------------------------------- 1 | appdirs==1.4.3 2 | cached-property==1.3.1 3 | certifi==2018.1.18 4 | chardet==3.0.4 5 | defusedxml==0.5.0 6 | future==0.16.0 7 | google-api-python-client==1.6.5 8 | httplib2==0.10.3 9 | icalendar==4.0.1 10 | idna==2.6 11 | isodate==0.6.0 12 | lxml 13 | -e git+https://github.com/Triple-Z/NUAA-iCal-Python.git@5f3f34d766b60956c3a7031527d292ecd9d68ded#egg=NUAAiCal 14 | oauth2client==4.1.2 15 | pkginfo==1.4.1 16 | pyasn1==0.4.2 17 | pyasn1-modules==0.2.1 18 | python-dateutil==2.6.1 19 | pytz==2018.3 20 | requests==2.18.4 21 | requests-toolbelt==0.8.0 22 | rsa==3.4.2 23 | six==1.11.0 24 | tqdm==4.19.6 25 | twine==1.9.1 26 | uritemplate==3.0.0 27 | urllib3==1.22 28 | zeep==2.5.0 29 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 TripleZ 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /NUAAiCal/Lesson.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | class Lesson: 4 | def __init__(self, year, semester, student_number, lesson_order_number, 5 | lesson_number, name, teacher_number, teacher_name, 6 | school_distinct, day_of_week, unit, lsjs, room_number, weeks): 7 | self.year = year 8 | self.semester = semester 9 | self.student_number = student_number 10 | self.lesson_order_number = lesson_order_number 11 | self.lesson_number = lesson_number 12 | self.name = name 13 | self.teacher_number = teacher_number 14 | self.teacher_name = teacher_name 15 | self.school_distinct = school_distinct 16 | self.day_of_week = day_of_week 17 | self.unit = unit 18 | self.lsjs = lsjs # what is this? 19 | self.room_number = room_number 20 | self.weeks = weeks 21 | 22 | def _print(self): 23 | print('=====================================') 24 | print("学年:" + self.year) 25 | print("学期:" + self.semester) 26 | print("学号:" + self.student_number) 27 | print("课程序号:" + self.lesson_order_number) 28 | print("课程号:" + self.lesson_number) 29 | print("课程名:" + self.name) 30 | print("教师号:" + self.teacher_number) 31 | print("教师名:" + self.teacher_name) 32 | print("校区:" + self.school_distinct) 33 | print("课程时间:星期" + { 34 | '1': '一', 35 | '2': '二', 36 | '3': '三', 37 | '4': '四', 38 | '5': '五', 39 | '6': '六', 40 | '7': '日', 41 | }.get(self.day_of_week) + "第" + self.unit + "节") 42 | print("lsjs:" + self.lsjs) 43 | print("教室号:" + self.room_number) 44 | print("上课周:" + ','.join(map(str, self.weeks))) 45 | -------------------------------------------------------------------------------- /README_zh-hans.md: -------------------------------------------------------------------------------- 1 | # NUAA iCal Python 2 | 3 | ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/NUAAiCal.svg) 4 | [![Build Status](https://travis-ci.org/NUAA-Open-Source/NUAA-iCal-Python.svg)](https://travis-ci.org/NUAA-Open-Source/NUAA-iCal-Python) 5 | [![pypi package](https://img.shields.io/pypi/v/NUAAiCal.svg)](https://pypi.python.org/pypi/NUAAiCal/) 6 | ![PyPI - Status](https://img.shields.io/pypi/status/NUAAiCal.svg) 7 | ![PyPI - License](https://img.shields.io/pypi/l/NUAAiCal.svg) 8 | 9 | :us: [English](/README.md) | :cn: 简体中文 10 | 11 | ⚠️ **由于南京航空航天大学教务处接口变化,该程序使用的接口已无效。请使用其它方式获得课表的程序如 [miaotony/NUAA_ClassSchedule](https://github.com/miaotony/NUAA_ClassSchedule)。感谢你们的支持!** 12 | 13 | 将南京航空航天大学的课程表(不含体育、实验等课程)导出成 `.ics` 日历格式,以将课程事件导入其他日历软件(如 Google Calendar)。 14 | 15 | 欢迎贡献代码,提交 `PR`! 16 | 17 | ## 快速使用 18 | 19 | [![DEMO](https://asciinema.org/a/HNivm2Ax5PpuUx6e5LwMwxffA.png)](https://asciinema.org/a/HNivm2Ax5PpuUx6e5LwMwxffA) 20 | 21 | ## 安装 22 | 23 | ### PyPI 安装 24 | 25 | 安装 `NUAAiCal` python 软件包: 26 | 27 | ```bash 28 | $ pip install NUAAiCal 29 | ``` 30 | 31 | > 若有权限问题,请使用 `pip install NUAAiCal --user` 。 32 | 33 | ### 源码安装 34 | 35 | 从源码构建安装: 36 | 37 | ```bash 38 | $ git clone https://github.com/Triple-Z/NUAA-iCal-Python.git 39 | $ cd NUAA-iCal-Python 40 | $ python setup.py install 41 | ``` 42 | 43 | ## 启动程序 44 | 45 | ```bash 46 | $ nuaaical 47 | ``` 48 | 49 | 你将会看到课程表日历 `.ics` 文件的导出路径, 你可以将其导入至 Google 日历等日历程序。 50 | 51 | ## 疑难解答 52 | 53 | ### 未找到命令 54 | 55 | > nuaaical: command not found 56 | 57 | 可能系统环境变量 `PATH` 缺失了值 `~/.local/bin` , 尝试运行下面的命令后再重新运行 `nuaaical` : 58 | 59 | ```bash 60 | $ export PATH=${HOME}/.local/bin:$PATH 61 | ``` 62 | 63 | ## 版权声明 64 | 65 | 本项目采用 [MIT 许可协议](/LICENSE.md) 。 66 | 67 | Copyright © 2018 [TripleZ](https://github.com/Triple-Z) 68 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | from codecs import open 3 | from os import path 4 | from NUAAiCal.settings import VERSION 5 | 6 | here = path.abspath(path.dirname(__file__)) 7 | 8 | # Get the long description from the README file 9 | with open(path.join(here, 'README.rst'), encoding='utf-8') as f: 10 | long_description = f.read() 11 | setup( 12 | name='NUAAiCal', 13 | version=VERSION, 14 | description='Generate NUAA curriculum to iCalendar file.', 15 | url='https://github.com/Triple-Z/NUAA-iCal-Python', 16 | author='TripleZ', 17 | author_email='me@triplez.cn', 18 | license='MIT', 19 | classifiers=[ 20 | # How mature is this project? Common values are 21 | # 3 - Alpha 22 | # 4 - Beta 23 | # 5 - Production/Stable 24 | 'Development Status :: 4 - Beta', 25 | 'Intended Audience :: Developers', 26 | 'License :: OSI Approved :: MIT License', 27 | # 'Programming Language :: Python :: 2', 28 | # 'Programming Language :: Python :: 2.6', 29 | 'Programming Language :: Python :: 2.7', 30 | # 'Programming Language :: Python :: 3', 31 | # 'Programming Language :: Python :: 3.4', 32 | # 'Programming Language :: Python :: 3.5', 33 | 'Programming Language :: Python :: 3.6', 34 | ], 35 | keywords='NUAA iCalendar ics curriculum iCal schedule calendar', 36 | packages=find_packages(exclude=['contrib', 'docs', 'tests*']), 37 | entry_points={ 38 | 'console_scripts': [ 39 | 'nuaaical = NUAAiCal.main:main', 40 | ], 41 | }, 42 | install_requires=[ 43 | 'future;python_version<"3"', 44 | 'six', 45 | 'lxml', 46 | 'pytz', 47 | 'zeep==2.5.0', 48 | 'icalendar', 49 | ], 50 | # Use pytest 51 | setup_requires=['pytest-runner'], 52 | tests_require=['pytest'], 53 | ) 54 | 55 | -------------------------------------------------------------------------------- /NUAAiCal/FindFirstDayofSemester.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from lxml import etree 4 | from datetime import datetime, timedelta 5 | from pytz import timezone 6 | 7 | 8 | def get_semester_start_date(years, xq, client): 9 | semester = int(xq) 10 | 11 | request_data = { 12 | 1: { 13 | "year": years[0], 14 | "month": 9, 15 | "day": 1, 16 | }, 17 | 2: { 18 | "year": years[1], 19 | "month": 3, 20 | "day": 1, 21 | }, 22 | }.get(semester) 23 | 24 | base_date = datetime(int(request_data['year']), int(request_data['month']), 25 | int(request_data['day']), 0, 0, 0, tzinfo=timezone( 26 | 'Asia/Shanghai')) 27 | 28 | with client.options(raw_response=True): 29 | response = client.service.GeXlInfoByDate(**request_data) 30 | root = etree.fromstring(response.content) 31 | body = root[0] 32 | GeXlInfoByDateResponse = body[0] 33 | GeXlInfoByDateResult = GeXlInfoByDateResponse[0] 34 | try: 35 | ds = GeXlInfoByDateResult[1][0][0] 36 | except IndexError: 37 | print('[ERROR] 这个学期还没有校历信息!') 38 | return False 39 | else: 40 | week_number = int(ds.find('zc').text) 41 | monday = base_date - timedelta(days=base_date.weekday()) 42 | if week_number == 0: 43 | while week_number == 0: 44 | monday = monday + timedelta(weeks=1) 45 | request_data = { 46 | "year": monday.year, 47 | "month": monday.month, 48 | "day": monday.day, 49 | } 50 | response = client.service.GeXlInfoByDate(**request_data) 51 | root = etree.fromstring(response.content) 52 | ds = root[0][0][0][1][0][0] 53 | week_number = int(ds.find('zc').text) 54 | if week_number == 1: 55 | return monday 56 | elif week_number == 1: 57 | return monday 58 | else: 59 | monday = monday - timedelta(weeks=week_number - 1) 60 | return monday 61 | -------------------------------------------------------------------------------- /NUAAiCal/tests/test_GenerateICS.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | 3 | from __future__ import unicode_literals 4 | 5 | from NUAAiCal.GenerateICS import create_ics, export_ics 6 | from NUAAiCal.Lesson import Lesson 7 | import pytest 8 | from zeep import Client 9 | from icalendar import Calendar, Event 10 | from datetime import datetime 11 | from pytz import timezone 12 | 13 | 14 | class TestGenerateICS: 15 | def test_create_ics(self): 16 | new_lesson = Lesson('2017-2018', '2', '161540121', 17 | '03103270_70205111_6', '03103270', '全球导航卫星系统', 18 | '70205111', '曾庆化', '将军路', '3', '3', '2', '2205', 19 | [10, 11, 12, 13, 14, 15, 16, 17]) 20 | lessons = [new_lesson] 21 | assert type(create_ics(lessons, datetime(2018, 2, 26, 0, 0, 0, 22 | tzinfo=timezone('Asia/Shanghai'))) is Calendar) 23 | 24 | def test_export_ics(self): 25 | # test cal 26 | cal = Calendar() 27 | cal.add('prodid', '-//TripleZ//NUAA-iCal-Python//CN') 28 | cal.add('version', '2.0') 29 | cal.add('X-WR-TIMEZONE', 'Asia/Shanghai') 30 | event = Event() 31 | event.add('summary', '全球导航卫星系统') 32 | event.add('dtstart', datetime(2018, 5, 2, 10, 15, 0, 33 | tzinfo=timezone('Asia/Shanghai'))) 34 | event.add('dtend', datetime(2018, 5, 2, 12, 0, 0, 35 | tzinfo=timezone('Asia/Shanghai'))) 36 | event.add('dtstamp', datetime.now(tz=timezone('Asia/Shanghai'))) 37 | event.add('location', '2205' + '@' + '将军路') 38 | try: 39 | event.add('description', "教师:" + '曾庆化' + 40 | "\n" + 41 | "课程序号:03103270_70205111_6" 42 | + "\n\nPowered by NUAA-iCal-Python") 43 | except UnicodeDecodeError: 44 | details = "教师:".decode('utf-8') + '曾庆化' \ 45 | + "\n".decode('utf-8') + \ 46 | "课程序号:03103270_70205111_6".decode('utf-8') \ 47 | + "\n\nPowered by NUAA-iCal-Python".decode( 48 | 'utf-8') 49 | event.add('description', details) 50 | 51 | cal.add_component(event) 52 | 53 | assert export_ics(cal, '2017-2018', '2', '161540121') 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NUAA iCal Python 2 | 3 | 4 | ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/NUAAiCal.svg) 5 | [![Build Status](https://travis-ci.org/NUAA-Open-Source/NUAA-iCal-Python.svg)](https://travis-ci.org/NUAA-Open-Source/NUAA-iCal-Python) 6 | [![pypi package](https://img.shields.io/pypi/v/NUAAiCal.svg)](https://pypi.python.org/pypi/NUAAiCal/) 7 | ![PyPI - Status](https://img.shields.io/pypi/status/NUAAiCal.svg) 8 | ![PyPI - License](https://img.shields.io/pypi/l/NUAAiCal.svg) 9 | [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2FNUAA-Open-Source%2FNUAA-iCal-Python.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2FNUAA-Open-Source%2FNUAA-iCal-Python?ref=badge_shield) 10 | 11 | :us: English | :cn: [简体中文](/README_zh-hans.md) 12 | 13 | :warning: **The NUAA curriculum APIs have been deprecated, so this program DO NOT WORK for now. Please see this repo [miaotony/NUAA_ClassSchedule](https://github.com/miaotony/NUAA_ClassSchedule) for your alternative.** 14 | 15 | Export the curriculum of NUAA to a `.ics` calendar file, in order to import 16 | class events to other calendars (e.g. Google Calendar). 17 | 18 | **Pull Requests Welcome!** 19 | 20 | ## Quick Start 21 | 22 | [![DEMO](https://asciinema.org/a/HNivm2Ax5PpuUx6e5LwMwxffA.png)](https://asciinema.org/a/HNivm2Ax5PpuUx6e5LwMwxffA) 23 | 24 | ## Installation 25 | 26 | ### PyPI 27 | 28 | Install `NUAAiCal` python package: 29 | 30 | ```bash 31 | $ pip install NUAAiCal 32 | ``` 33 | 34 | > If there has a problem caused by permission, please try `pip install NUAAiCal --user` instead. 35 | 36 | ### Source 37 | 38 | Built it from source code: 39 | 40 | ```bash 41 | $ git clone https://github.com/Triple-Z/NUAA-iCal-Python.git 42 | $ cd NUAA-iCal-Python 43 | $ python setup.py install 44 | ``` 45 | 46 | ## Start Application 47 | 48 | ```bash 49 | $ nuaaical 50 | ``` 51 | 52 | The `.ics` file path will be shown in the output, you can import it to Google 53 | Calendar etc. 54 | 55 | ## Troubleshoot 56 | 57 | ### Command Not Found 58 | 59 | > nuaaical: command not found 60 | 61 | Maybe your system `PATH` environment variable lacks of the value of `~/ 62 | .local/bin` . Try the command following, then run `nuaaical` again: 63 | 64 | ```bash 65 | $ export PATH=${HOME}/.local/bin:$PATH 66 | ``` 67 | 68 | ## TODO 69 | 70 | - [x] Get course table data 71 | - [x] Generate iCal file 72 | - [x] Input Variables 73 | - [x] Pack 74 | - [x] Calendar Diff 75 | - [ ] GUI 76 | - [ ] Export to Google Calendar 77 | - [ ] WSGI server 78 | 79 | ## Copyright 80 | 81 | This project is licensed by [The MIT License](/LICENSE.md). 82 | 83 | Copyright © 2018 [TripleZ](https://github.com/Triple-Z) 84 | 85 | 86 | ## License 87 | [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2FNUAA-Open-Source%2FNUAA-iCal-Python.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2FNUAA-Open-Source%2FNUAA-iCal-Python?ref=badge_large) 88 | -------------------------------------------------------------------------------- /NUAAiCal/AddToGCal.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import httplib2 3 | import os 4 | 5 | from apiclient import discovery 6 | from oauth2client import client 7 | from oauth2client import tools 8 | from oauth2client.file import Storage 9 | 10 | import datetime 11 | 12 | try: 13 | import argparse 14 | flags = argparse.ArgumentParser(parents=[tools.argparser]).parse_args() 15 | except ImportError: 16 | flags = None 17 | 18 | # If modifying these scopes, delete your previously saved credentials 19 | # at ~/.credentials/calendar-python-quickstart.json 20 | SCOPES = 'https://www.googleapis.com/auth/calendar.readonly' 21 | CLIENT_SECRET_FILE = 'client_secret.json' 22 | APPLICATION_NAME = 'Google Calendar API Python Quickstart' 23 | 24 | 25 | def get_credentials(): 26 | """Gets valid user credentials from storage. 27 | 28 | If nothing has been stored, or if the stored credentials are invalid, 29 | the OAuth2 flow is completed to obtain the new credentials. 30 | 31 | Returns: 32 | Credentials, the obtained credential. 33 | """ 34 | home_dir = os.path.expanduser('~') 35 | credential_dir = os.path.join(home_dir, '.credentials') 36 | if not os.path.exists(credential_dir): 37 | os.makedirs(credential_dir) 38 | credential_path = os.path.join(credential_dir, 39 | 'calendar-python-quickstart.json') 40 | 41 | store = Storage(credential_path) 42 | credentials = store.get() 43 | if not credentials or credentials.invalid: 44 | flow = client.flow_from_clientsecrets(CLIENT_SECRET_FILE, SCOPES) 45 | flow.user_agent = APPLICATION_NAME 46 | if flags: 47 | credentials = tools.run_flow(flow, store, flags) 48 | else: # Needed only for compatibility with Python 2.6 49 | credentials = tools.run(flow, store) 50 | print('Storing credentials to ' + credential_path) 51 | return credentials 52 | 53 | 54 | def main(): 55 | """Shows basic usage of the Google Calendar API. 56 | 57 | Creates a Google Calendar API service object and outputs a list of the next 58 | 10 events on the user's calendar. 59 | """ 60 | credentials = get_credentials() 61 | http = credentials.authorize(httplib2.Http()) 62 | service = discovery.build('calendar', 'v3', http=http) 63 | 64 | now = datetime.datetime.utcnow().isoformat() + 'Z' # 'Z' indicates UTC time 65 | print('Getting the upcoming 10 events') 66 | eventsResult = service.events().list( 67 | calendarId='primary', timeMin=now, maxResults=10, singleEvents=True, 68 | orderBy='startTime').execute() 69 | events = eventsResult.get('items', []) 70 | 71 | if not events: 72 | print('No upcoming events found.') 73 | for event in events: 74 | start = event['start'].get('dateTime', event['start'].get('date')) 75 | print(start, event['summary']) 76 | 77 | 78 | if __name__ == '__main__': 79 | main() 80 | -------------------------------------------------------------------------------- /NUAAiCal/GenerateICS.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from __future__ import print_function 4 | from __future__ import unicode_literals 5 | 6 | from icalendar import Calendar, Event, vText 7 | from datetime import datetime, timedelta 8 | from pytz import timezone 9 | import tempfile 10 | import hashlib 11 | import os 12 | from sys import getsizeof 13 | 14 | def create_ics(lessons, semester_start_date): 15 | 16 | cal = Calendar() 17 | cal.add('prodid', '-//TripleZ//NUAA-iCal-Python//CN') 18 | cal.add('version', '2.0') 19 | cal.add('X-WR-TIMEZONE', 'Asia/Shanghai') 20 | 21 | for lesson in lessons: 22 | for week in lesson.weeks: 23 | 24 | event = Event() 25 | event.add('summary', lesson.name) 26 | 27 | # Lesson start time 28 | lesson_start_hour = { 29 | '1': 8, 30 | '3': 10, 31 | '5': 14, 32 | '7': 16, 33 | '9': 18, 34 | }.get(lesson.unit) 35 | lesson_start_minute = { 36 | '1': 0, 37 | '3': 15, 38 | '5': 0, 39 | '7': 15, 40 | '9': 45, 41 | }.get(lesson.unit) 42 | 43 | lesson_start_time = semester_start_date \ 44 | + timedelta(weeks=week-1, days=int(lesson.day_of_week)-1, 45 | hours=lesson_start_hour-semester_start_date.hour, 46 | minutes=lesson_start_minute-semester_start_date.minute, 47 | seconds=-semester_start_date.second, 48 | milliseconds=-semester_start_date.microsecond) 49 | 50 | lesson_end_time = lesson_start_time + timedelta(minutes=105) 51 | 52 | event.add('dtstart', lesson_start_time) 53 | event.add('dtend', lesson_end_time) 54 | # event.add('dtstamp', datetime.now(tz=timezone('Asia/Shanghai'))) 55 | event.add('location', lesson.room_number.rstrip() + '@' + 56 | lesson.school_distinct.rstrip()) 57 | try: 58 | event.add('description', "教师:" + lesson.teacher_name.rstrip() + 59 | "\n" + \ 60 | "当前周次:%d" % week + \ 61 | "\n上课周次:%d-%d" % (lesson.weeks[0], lesson.weeks[-1]) + 62 | "\n课程序号:" + lesson.lesson_order_number.rstrip() + \ 63 | "\n\nPowered by NUAA-iCal-Python") 64 | except UnicodeDecodeError: 65 | details = "教师:".decode('utf-8') + lesson.teacher_name.rstrip()\ 66 | + "\n".decode('utf-8') + \ 67 | "当前周次:".decode('utf-8') + week + \ 68 | "\n上课周次:".decode('utf-8') + weeks[0] + "-".decode('utf-8') + week[-1] + \ 69 | "\n课程序号:".decode('utf-8') +\ 70 | lesson.lesson_order_number.rstrip() + "\n\nPowered by NUAA-iCal-Python".decode('utf-8') 71 | event.add('description', details) 72 | 73 | cal.add_component(event) 74 | 75 | return cal 76 | 77 | 78 | def export_ics(cal, xn, xq, xh): 79 | filename = 'NUAAiCal-Data/NUAA-curriculum-' + xn + '-' + xq + '-' + xh + '.ics' 80 | 81 | if os.path.exists('NUAAiCal-Data'): 82 | # print('Directory exists.') 83 | if os.path.isfile(filename): 84 | # File exists, check whether need to be updated. 85 | 86 | tem = open('.temp', 'w+b') 87 | tem_path = os.path.abspath(tem.name) 88 | tem.write(cal.to_ical()) 89 | tem_filename = tem.name 90 | tem.read() # fix a py2.7 bug... issue#2 91 | tem.close() 92 | # print(getsizeof(tem.read())) 93 | is_update = not is_same(tem_path, filename) 94 | # print("Temp file name is %s, in %s" % (tem_filename, os.path.abspath(tem_filename))) 95 | os.remove(tem_path) 96 | 97 | if is_update: 98 | print('有更新的课程!') 99 | f = open(os.path.join(filename), 'wb') 100 | f.write(cal.to_ical()) 101 | f.close() 102 | print("更新的日历文件已导出到 \"" + os.path.abspath(filename) + "\"。") 103 | else: 104 | print('没有需要更新的课程!') 105 | print("原有的日历文件位置为 \"" + os.path.abspath(filename) + "\"。") 106 | 107 | else: 108 | f = open(os.path.join(filename), 'wb') 109 | f.write(cal.to_ical()) 110 | f.close() 111 | print("日历文件已导出到 \"" + os.path.abspath(filename) + "\"。") 112 | else: 113 | os.mkdir('NUAAiCal-Data') 114 | 115 | f = open(os.path.join(filename), 'wb') 116 | f.write(cal.to_ical()) 117 | f.close() 118 | # print('ICS file has successfully exported to \"' + filename + '\".') 119 | print("日历文件已导出到 \"" + os.path.abspath(filename) + "\"。") 120 | 121 | return True 122 | 123 | 124 | def is_same(file1, file2): 125 | hash1 = hashlib.md5() 126 | with open(file1, 'rb') as f1: 127 | f1_data = f1.read() 128 | # print(getsizeof(f1_data)) 129 | hash1.update(f1_data) 130 | md5_1 = hash1.hexdigest() 131 | # print(f1_data) 132 | # print(file1.name) 133 | # print(md5_1) 134 | 135 | hash2 = hashlib.md5() 136 | with open(file2, 'rb') as f2: 137 | f2_data = f2.read() 138 | # print(getsizeof(f2_data)) 139 | hash2.update(f2_data) 140 | md5_2 = hash2.hexdigest() 141 | # print(f2_data) 142 | # print(f2.name) 143 | # print(md5_2) 144 | 145 | # print(f1_data == f2_data) 146 | 147 | if md5_1 == md5_2: 148 | return True 149 | else: 150 | return False 151 | -------------------------------------------------------------------------------- /NUAAiCal/main.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from __future__ import unicode_literals 4 | 5 | import getopt 6 | import sys 7 | from builtins import input 8 | 9 | from lxml import etree 10 | from zeep import Client 11 | 12 | from NUAAiCal.settings import DEBUG, VERSION 13 | from NUAAiCal.FindFirstDayofSemester import get_semester_start_date 14 | from NUAAiCal.GenerateICS import create_ics, export_ics 15 | from NUAAiCal.Lesson import Lesson 16 | 17 | 18 | def usage(): 19 | print('''Usage: 20 | -v, --version Check NUAAiCal version 21 | -h, --help Display this help text 22 | ''') 23 | 24 | 25 | def main(): 26 | shortopts = "hv" 27 | longopts = ['help', 'version'] 28 | try: 29 | options, args = getopt.getopt(sys.argv[1:], shortopts, longopts) 30 | for opt, arg in options: 31 | if opt in ('-v', '--version'): 32 | print('NUAAiCal ' + VERSION) 33 | exit(0) 34 | elif opt in ('-h', '--help'): 35 | usage() 36 | exit(0) 37 | 38 | except getopt.GetoptError: 39 | pass 40 | 41 | client = Client( 42 | 'http://ded.nuaa.edu.cn/NetEa/Services/WebService.asmx?WSDL') 43 | 44 | print("======== NUAA iCal Python ========") 45 | print("Repo: https://github.com/Triple-Z/NUAA-iCal-Python") 46 | print("Please help me STAR this project if it is useful~") 47 | print("Pull requests (PR) welcome!") 48 | 49 | if DEBUG: 50 | xn = '2017-2018' 51 | xq = '2' 52 | xh = '161540121' 53 | # semester_start_date = datetime(2018, 2, 26, 0, 0, 0) 54 | print("==================================================") 55 | else: 56 | print("\n输入提示:") 57 | print('学年: 2017-2018') 58 | print('学期: 1 (上学期) / 2 (下学期)') 59 | print('学号: 你的南京航空航天大学学号') 60 | print("-------- 请填写以下信息 --------") 61 | 62 | try: 63 | xn = input("学年: ") 64 | except UnicodeEncodeError: 65 | # fucking python 2.7 unicode 66 | xn = input("学年: ".encode('utf-8')) 67 | 68 | if '-' in xn: 69 | 70 | try: 71 | years = xn.split('-') 72 | start_year = int(years[0]) 73 | end_year = int(years[1]) 74 | except ValueError: 75 | print("学年输入错误: " + xn) 76 | else: 77 | 78 | if not DEBUG: 79 | try: 80 | xq = input('学期: ') 81 | except UnicodeEncodeError: 82 | xq = input('学期: '.encode('utf-8')) 83 | 84 | if not (xq == '1' or xq == '2'): 85 | print("学期输入错误! 提示:1为上学期,2为下学期!") 86 | exit() 87 | 88 | try: 89 | xh = input('学号: ') 90 | except UnicodeEncodeError: 91 | xh = input('学号: '.encode('utf-8')) 92 | 93 | print("==================================================") 94 | 95 | semester_start_date = get_semester_start_date(years, xq, client) 96 | 97 | if not semester_start_date: 98 | print("未能获得学年 " + xn + " 的校历信息") 99 | exit() 100 | 101 | request_data = { 102 | 'xn': xn, 103 | 'xq': xq, 104 | 'xh': xh, 105 | } 106 | 107 | with client.options(raw_response=True): 108 | response = client.service.GetCourseTableByXh(**request_data) 109 | root = etree.fromstring(response.content) 110 | body = root[0] 111 | GetCourseTableByXhResponse = body[0] 112 | GetCourseTableByXhResult = GetCourseTableByXhResponse[0] 113 | try: 114 | NewDataSet = list(GetCourseTableByXhResult[1][1]) 115 | except IndexError: 116 | print(xn + '学年第' + xq + '学期没有课程!') 117 | else: 118 | # print(len(NewDataSet)) 119 | 120 | lessons = list() 121 | 122 | # Add lessons 123 | for ds in NewDataSet: 124 | # Explain XML 125 | year = ds.find('xn').text 126 | semester = ds.find('xq').text 127 | student_number = ds.find('xh').text 128 | lesson_order_number = ds.find('kcxh').text 129 | lesson_number = ds.find('kch').text 130 | name = ds.find('kcm').text 131 | teacher_number = ds.find('jsh').text 132 | teacher_name = ds.find('jsm').text 133 | school_distinct = ds.find('xiaoqu').text 134 | day_of_week = ds.find('week').text 135 | unit = ds.find('unit').text 136 | lsjs = ds.find('lsjs').text # what is this??? 137 | room_number = ds.find('roomid').text 138 | weeks = list(map(int, ds.find('weeks').text.split(','))) 139 | 140 | new_lesson = Lesson(year, semester, student_number, 141 | lesson_order_number, 142 | lesson_number, name, teacher_number, 143 | teacher_name, 144 | school_distinct, day_of_week, unit, 145 | lsjs, 146 | room_number, weeks) 147 | 148 | lessons.append(new_lesson) 149 | 150 | # Print all lessons in cli 151 | # for lesson in lessons: 152 | # lesson._print() 153 | 154 | cal = create_ics(lessons, semester_start_date) 155 | export_ics(cal, xn, xq, xh) 156 | return True 157 | else: 158 | print("学年输入错误: " + xn) 159 | --------------------------------------------------------------------------------