├── .gitattributes ├── .github ├── FUNDING.yml └── workflows │ ├── lint.yml │ └── test.yml ├── .gitignore ├── .nojekyll ├── Dockerfile ├── LICENSE ├── README.md ├── action.yml ├── archive.py ├── assets └── img │ └── zulip.svg ├── default_settings.py ├── default_streams.yaml ├── entrypoint.sh ├── github.py ├── hosting.md ├── instructions.md ├── lib ├── __init__.py ├── common.py ├── date_helper.py ├── files.py ├── html.py ├── populate.py ├── sitemap.py ├── url.py ├── website.py └── zulip_data.py ├── requirements.txt ├── style.css └── tests └── testCommon.py /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: zulip 2 | patreon: zulip 3 | open_collective: zulip 4 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Linting 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | black: 7 | runs-on: ubuntu-latest 8 | name: Black linting 9 | steps: 10 | - uses: actions/checkout@v3 11 | - uses: psf/black@stable 12 | with: 13 | options: "--check" 14 | src: "." 15 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | name: Test 13 | steps: 14 | - uses: actions/checkout@v3 15 | - name: Setup Python 3.10 16 | uses: actions/setup-python@v4 17 | with: 18 | python-version: "3.10" 19 | - name: Install dependencies 20 | run: | 21 | pip install -r requirements.txt 22 | pip install pytest 23 | - name: Running Test-Suite on Linux 24 | run: | 25 | pytest tests/testCommon.py 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # configuration, caches, output 2 | settings.py 3 | streams.yaml 4 | _json 5 | archive 6 | zuliprc 7 | 8 | # Byte-compiled / optimized / DLL files 9 | __pycache__/ 10 | *.py[cod] 11 | *$py.class 12 | 13 | # C extensions 14 | *.so 15 | 16 | # Distribution / packaging 17 | .Python 18 | build/ 19 | develop-eggs/ 20 | dist/ 21 | downloads/ 22 | eggs/ 23 | .eggs/ 24 | lib64/ 25 | parts/ 26 | sdist/ 27 | var/ 28 | wheels/ 29 | *.egg-info/ 30 | .installed.cfg 31 | *.egg 32 | MANIFEST 33 | 34 | # PyInstaller 35 | # Usually these files are written by a python script from a template 36 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 37 | *.manifest 38 | *.spec 39 | 40 | # Installer logs 41 | pip-log.txt 42 | pip-delete-this-directory.txt 43 | 44 | # Unit test / coverage reports 45 | htmlcov/ 46 | .tox/ 47 | .coverage 48 | .coverage.* 49 | .cache 50 | nosetests.xml 51 | coverage.xml 52 | *.cover 53 | .hypothesis/ 54 | .pytest_cache/ 55 | 56 | # Translations 57 | *.mo 58 | *.pot 59 | 60 | # Django stuff: 61 | *.log 62 | local_settings.py 63 | db.sqlite3 64 | 65 | # Flask stuff: 66 | instance/ 67 | .webassets-cache 68 | 69 | # Scrapy stuff: 70 | .scrapy 71 | 72 | # Sphinx documentation 73 | docs/_build/ 74 | 75 | # PyBuilder 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # pyenv 82 | .python-version 83 | 84 | # celery beat schedule file 85 | celerybeat-schedule 86 | 87 | # SageMath parsed files 88 | *.sage.py 89 | 90 | # Environments 91 | .env 92 | .venv 93 | env/ 94 | venv/ 95 | ENV/ 96 | env.bak/ 97 | venv.bak/ 98 | bin/ 99 | include/ 100 | lib/python3.6/ 101 | pip-selfcheck.json 102 | share/ 103 | 104 | # Spyder project settings 105 | .spyderproject 106 | .spyproject 107 | 108 | # Rope project settings 109 | .ropeproject 110 | 111 | # mkdocs documentation 112 | /site 113 | 114 | # mypy 115 | .mypy_cache/ 116 | 117 | # editors 118 | *.swp 119 | -------------------------------------------------------------------------------- /.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-archive/663518053b8f19b58c36ee61a5ac425cd2604ba4/.nojekyll -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.11-alpine 2 | 3 | RUN mkdir -p /zulip-archive && apk update && apk add git curl 4 | 5 | COPY . /zulip-archive-action/ 6 | 7 | ENTRYPOINT ["sh", "/zulip-archive-action/entrypoint.sh"] 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Robert Y. Lewis 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Zulip HTML archive 2 | 3 | [![code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) 4 | 5 | Generates an HTML archive of a configured set of streams within a 6 | [Zulip](https://zulip.com) organization. It is common to archive all [public](https://zulip.com/help/stream-permissions) or [web-public](https://zulip.com/help/public-access-option) streams. 7 | 8 | Example: [Lean Prover 9 | archive](https://leanprover-community.github.io/archive/). 10 | 11 | `zulip-archive` works by downloading Zulip message history via the 12 | API, storing it in JSON files, maintaining its local archive with 13 | incremental updates, and turning those JSON files into the HTML 14 | archive. 15 | 16 | This archive tool is often used in addition to enabling the [public access option](https://zulip.com/help/public-access-option) for your organization, which lets administrators configure selected streams to be web-public. Web-public streams can be viewed by anyone on the Internet without creating an account in your organization. The public access option does not yet support search engine indexing, which makes this archive tool a good option if it's important for your organization's chat history to appear in search results. It is easy to configure `zulip-archive` to automatically archive all web-public streams in your organization. 17 | 18 | ### Contents 19 | * [Running zulip-archive as a GitHub action](#running-zulip-archive-as-a-github-action) 20 | * [Running zulip-archive without GitHub actions](#running-zulip-archive-without-github-actions) 21 | * [Why archive](#why-archive) 22 | * [Contributing and future plans](#contributing-and-future-plans) 23 | 24 | ## Running zulip-archive as a GitHub action 25 | 26 | Running `zulip-archive` as a GitHub action is easiest way to get up and running. The action will periodically sync a GitHub repository with the latest messages, and publish the archive website using GitHub pages. Follow the steps below to set up a `zulip-archive` GitHub action in a few minutes. 27 | 28 | ### Step 1 - Create a repository for running the action 29 | 30 | It's best to use a dedicated repository for running the action. You can create a new repository at https://github.com/new/. 31 | 32 | ### Step 2 - Generate credentials 33 | 34 | The GitHub action requires a Zulip API key in order to run. The key is used for fetching messages in public streams in your Zulip organization. It is strongly recommended that you [create a bot](https://zulip.com/help/add-a-bot-or-integration) and use its zuliprc, rather than using your personal zuliprc. 35 | 36 | ### Step 3 - Store credentials as secrets in the repository 37 | 38 | The credentials for your bot need to be stored in the repository as secrets, so that the action can access them during run time. You can create secrets in your repository at `https://github.com///settings/secrets`, where `` is your GitHub username, and `` is the name of the repository you are using. 39 | 40 | You will need to create the following secret. Use the credentials generated in the above step as the value of each secret. 41 | 42 | |Secret name | Value | 43 | |--------------|------------------------------------------------------| 44 | |zuliprc | The file content of the zuliprc obtained from step 2 | 45 | 46 | ### Step 4 - Enable GitHub Pages or set up base URL 47 | 48 | Go to `https://github.com///settings/pages`, select `main` (or a branch of your choosing), and `/` as the folder. Save the changes. The base URL of the generated site will be resolved to GitHub Pages, i.e., `https://.github.io/` or the configured custom domain name. 49 | 50 | Alternatively, you can configure the `base_url` option to populate the base URL. This option could be useful in situation when you are not using GitHub Pages. 51 | 52 | ### Step 5 - Configure the streams you want to index 53 | 54 | You will need to configure which streams will be indexed by `zulip-archive` by creating a `streams.yaml` file in the repository you are using for the GitHub action. As a starting point, you can make a copy of the default configuration file: `cp default_streams.yaml streams.yaml` 55 | 56 | To index all the [web-public streams](https://zulip.com/help/public-access-option) in your organization, set the following as the content of your `streams.yaml` file. 57 | 58 | ```yaml 59 | included: 60 | - 'web-public:*' 61 | ``` 62 | 63 | To index all the [public streams](https://zulip.com/help/stream-permissions), set the following as the content of your `streams.yaml` file. Note that public streams include all web-public streams. 64 | 65 | ```yaml 66 | included: 67 | - '*' 68 | ``` 69 | 70 | You can exclude specific public streams by placing them under the `excluded` key. 71 | 72 | ```yaml 73 | included: 74 | - '*' 75 | 76 | excluded: 77 | - general 78 | - development help 79 | ``` 80 | 81 | Alternatively, you can specify only the streams that you want to index. 82 | 83 | ```yaml 84 | included: 85 | - python 86 | - data structures 87 | - javascript 88 | ``` 89 | 90 | ### Step 6 - Enable the zulip-archive action 91 | 92 | Enable the action by creating a file called `.github/workflows/main.yaml`: 93 | 94 | #### Sample `main.yaml` file 95 | 96 | ```yaml 97 | on: 98 | schedule: 99 | - cron: '*/20 * * * *' 100 | 101 | jobs: 102 | publish_archive_job: 103 | runs-on: ubuntu-latest 104 | name: A job to publish zulip-archive in GitHub pages 105 | steps: 106 | - name: Checkout 107 | uses: actions/checkout@v3 108 | - name: Run archive 109 | id: archive 110 | uses: zulip/zulip-archive@master 111 | with: 112 | zuliprc: ${{ secrets.ZULIPRC }} 113 | # Using the GitHub Token that is provided automatically by GitHub Actions 114 | # (no setup needed). 115 | github_token: ${{ secrets.GITHUB_TOKEN }} 116 | delete_history: true 117 | archive_branch: main 118 | ``` 119 | 120 | #### Configure run frequency 121 | 122 | The above file tells GitHub to run the `zulip-archive` action every 20 minutes. You can [adjust](https://en.wikipedia.org/wiki/Cron) the `cron` key to modify the schedule as you feel appropriate. 123 | 124 | If you Zulip organization history is very large (not the case for most users), it is recommended that you initially increase the time between runs to an hour or longer (e.g., `'0 * * * *'`). This is is because the initial archive run that fetches the messages for the first time will take a long time, and you don't want the second cron job to start before the first run is completed. After the initial run, you can shorten the cron job period as desired. 125 | 126 | #### Configure `delete_history` option 127 | 128 | If you are running frequent updates with a busy Zulip organization, 129 | the Git repository that you use to run the action will grow very 130 | quickly. In this situation, it is recommended that you set the `delete_history` option to 131 | `true`. This will overwrite the Git _history_ in the repository, but 132 | keep all the _content_. If you are using the repository for more than 133 | just the Zulip archive (not recommended), you may want to set the `delete_history` flag to `false`, but be 134 | warned that the repository size may explode. 135 | 136 | ### Step 7 - Verify that everything works 137 | 138 | Finally, verify that everything is working as expected. You can track the status of the action by visiting `https://github.com///actions`. Once the initial run is completed, you should be able to visit the archive by opening the link provided at the end of the action run log. The link will generally be of the form `.github.io/`, or `/` if you have configured your own personal domain to point to GitHub pages. 139 | 140 | If you configure `base_url` option, you can track the status of the action by visiting the URL instead. 141 | 142 | ## Running zulip-archive without GitHub actions 143 | 144 | For most users, running `zulip-archive` as GitHub actions should be good enough. If you want to run `zulip-archive` in your own server or do something else, see the [instructions](instructions.md) docs. The [hosting docs](hosting.md) also offer a few suggestions for good ways to host the output of this tool. 145 | 146 | ## Why archive? 147 | 148 | The best place to participate actively in a Zulip community is an app 149 | that tracks unread messages, formats messages properly, and is 150 | designed for efficient interaction. However, there are several use 151 | cases where this HTML archive tool is a valuable complement to the 152 | Zulip apps: 153 | 154 | * A public HTML archive can be indexed by search engines and doesn't 155 | require authentication to access. For open source projects and 156 | other open communities, this provides a convenient search and 157 | browsing experience for users who may not want to sign up an account 158 | just to find previous answers to common questions. 159 | 160 | * It's common to set up Zulip instances for one-time events such as 161 | conferences, retreats, or hackathons. Once the event ends, you may 162 | want to shut down the Zulip instance for operational convenience, 163 | but still want an archive of the communications. 164 | 165 | * You may also decide to shut down a Zulip instance, whether to move 166 | to another communication tool, to deduplicate instances, or because 167 | your organization is shutting down. You can always [export your 168 | Zulip data](https://zulip.com/help/export-your-organization), 169 | but the other tool may not be able to import it. In such a case, 170 | you can use this archive tool to keep the old conversations 171 | accessible. (Contrast this to scenarios where your provider locks 172 | you in to a solution, even when folks are dissatisfied with the 173 | tool, because they own the data.) 174 | 175 | * You may also want to publish your conversations outside of Zulip for 176 | branding reasons or to integrate with other data. You can modify 177 | the tools here as needed for that. You own your own data. 178 | 179 | 180 | ## Contributing and future plans 181 | 182 | Feedback, issues, and pull requests are encouraged! Our goal is for 183 | this project to support the needs of any community looking for an HTML 184 | archive of their Zulip organization's history, through just 185 | configuration changes. So please report even minor inconveniences, 186 | either via a GitHub issue or by posting in the 187 | [#integrations](https://chat.zulip.org/#narrow/stream/127-integrations/) stream 188 | in the [Zulip development community](https://zulip.com/development-community/). 189 | 190 | This project is licensed under the MIT license. 191 | 192 | Author: [Robert Y. Lewis](https://robertylewis.com/) ([@robertylewis](https://github.com/robertylewis)) 193 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | # action.yml 2 | name: 'Zulip Archive' 3 | description: 'Publish Zulip archive in GitHub pages' 4 | inputs: 5 | zulip_organization_url: 6 | description: 'URL of Zulip organization' 7 | required: true 8 | zulip_bot_email: 9 | description: 'Email of the Zulip bot' 10 | required: true 11 | zulip_bot_key: 12 | description: 'API key of the Zulip bot' 13 | required: true 14 | github_personal_access_token: 15 | description: 'GitHub personal access token (deprecated)' 16 | required: false 17 | deprecationMessage: 'Please use `github_token` instead' 18 | github_token: 19 | description: 'GitHub Token/GitHub Personal Access Token' 20 | required: true 21 | delete_history: 22 | description: 'If enabled, will delete the archive history while keeping the most recent version' 23 | required: false 24 | default: false 25 | archive_branch: 26 | description: 'Branch where to commit archive files (should coincide with GH Pages branch)' 27 | # legacy 28 | required: false 29 | default: 'master' 30 | zuliprc: 31 | description: 'zuliprc of the Zulip bot' 32 | required: true 33 | site_url: 34 | description: 'Base URL for the site. If not configured, this action will try to resolve the base URL as GH pages.' 35 | required: false 36 | runs: 37 | using: 'docker' 38 | image: 'Dockerfile' 39 | args: 40 | - ${{ inputs.zulip_organization_url }} 41 | - ${{ inputs.zulip_bot_email }} 42 | - ${{ inputs.zulip_bot_key }} 43 | - ${{ inputs.github_token }} 44 | - ${{ inputs.delete_history }} 45 | - ${{ inputs.archive_branch }} 46 | - ${{ inputs.github_personal_access_token }} 47 | -------------------------------------------------------------------------------- /archive.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """ 4 | This is the main program for the Zulip archive system. For help: 5 | 6 | python archive.py -h 7 | 8 | Note that this actual file mostly does the following: 9 | 10 | parse command line arguments 11 | check some settings from settings.py 12 | complain if you haven't made certain directories 13 | 14 | The actual work is done in two main libraries: 15 | 16 | lib/html.py 17 | lib/populate.py 18 | """ 19 | 20 | 21 | # The workflow (timing for the leanprover Zulip chat, on my slow laptop): 22 | # - populate_all() builds a json file in `settings.json_directory` for each topic, 23 | # containing message data and an index json file mapping streams to their topics. 24 | # This uses the Zulip API and takes ~10 minutes to crawl the whole chat. 25 | # - populate_incremental() assumes there is already a json cache and collects only new messages. 26 | # - build_website() builds the webstie 27 | # - See hosting.md for suggestions on hosting. 28 | # 29 | 30 | import sys 31 | 32 | if sys.version_info < (3, 6): 33 | version_error = " Python version must be 3.6 or higher\n\ 34 | Your current version of python is {}.{}\n\ 35 | Please try again with python3.".format( 36 | sys.version_info.major, sys.version_info.minor 37 | ) 38 | raise Exception(version_error) 39 | import argparse 40 | import configparser 41 | import os 42 | import zulip 43 | 44 | from lib.common import stream_validator, exit_immediately 45 | 46 | # Most of the heavy lifting is done by the following modules: 47 | 48 | from lib.populate import populate_all, populate_incremental 49 | 50 | from lib.website import build_website 51 | 52 | from lib.sitemap import build_sitemap 53 | 54 | try: 55 | import settings 56 | except ModuleNotFoundError: 57 | # TODO: Add better instructions. 58 | exit_immediately( 59 | """ 60 | We can't find settings.py. 61 | 62 | Please copy default_settings.py to settings.py 63 | and then edit the settings.py file to fit your use case. 64 | 65 | For testing, you can often leave the default settings, 66 | but you will still want to review them first. 67 | """ 68 | ) 69 | 70 | NO_JSON_DIR_ERROR_WRITE = """ 71 | We cannot find a place to write JSON files. 72 | 73 | Please run the below command: 74 | 75 | mkdir {}""" 76 | 77 | NO_JSON_DIR_ERROR_READ = """ 78 | We cannot find a place to read JSON files. 79 | 80 | Please run the below command: 81 | 82 | mkdir {} 83 | 84 | And then fetch the JSON: 85 | 86 | python archive.py -t""" 87 | 88 | NO_HTML_DIR_ERROR = """ 89 | We cannot find a place to write HTML files. 90 | 91 | Please run the below command: 92 | 93 | mkdir {}""" 94 | 95 | 96 | def get_json_directory(for_writing): 97 | json_dir = settings.json_directory 98 | 99 | if not json_dir.exists(): 100 | # I use posix paths here, since even on Windows folks will 101 | # probably be using some kinda Unix-y shell to run mkdir. 102 | if for_writing: 103 | error_msg = NO_JSON_DIR_ERROR_WRITE.format(json_dir.as_posix()) 104 | else: 105 | error_msg = NO_JSON_DIR_ERROR_READ.format(json_dir.as_posix()) 106 | 107 | exit_immediately(error_msg) 108 | 109 | if not json_dir.is_dir(): 110 | exit_immediately(str(json_dir) + " needs to be a directory") 111 | 112 | return settings.json_directory 113 | 114 | 115 | def get_html_directory(): 116 | html_dir = settings.html_directory 117 | 118 | if not html_dir.exists(): 119 | error_msg = NO_HTML_DIR_ERROR.format(html_dir.as_posix()) 120 | 121 | exit_immediately(error_msg) 122 | 123 | if not html_dir.is_dir(): 124 | exit_immediately(str(html_dir) + " needs to be a directory") 125 | 126 | return settings.html_directory 127 | 128 | 129 | def get_client_info(): 130 | config_file = "./zuliprc" 131 | client = zulip.Client(config_file=config_file) 132 | 133 | # It would be convenient if the Zulip client object 134 | # had a `site` field, but instead I just re-read the file 135 | # directly to get it. 136 | config = configparser.RawConfigParser() 137 | config.read(config_file) 138 | zulip_url = config.get("api", "site") 139 | 140 | return client, zulip_url 141 | 142 | 143 | def run(): 144 | parser = argparse.ArgumentParser( 145 | description="Build an html archive of the Zulip chat." 146 | ) 147 | parser.add_argument( 148 | "-b", action="store_true", default=False, help="Build .md files" 149 | ) 150 | parser.add_argument( 151 | "--no-sitemap", 152 | action="store_true", 153 | default=False, 154 | help="Don't build sitemap files", 155 | ) 156 | parser.add_argument( 157 | "-t", action="store_true", default=False, help="Make a clean json archive" 158 | ) 159 | parser.add_argument( 160 | "-i", 161 | action="store_true", 162 | default=False, 163 | help="Incrementally update the json archive", 164 | ) 165 | 166 | results = parser.parse_args() 167 | 168 | if results.t and results.i: 169 | print("Cannot perform both a total and incremental update. Use -t or -i.") 170 | exit(1) 171 | 172 | if not (results.t or results.i or results.b): 173 | print("\nERROR!\n\nYou have not specified any work to do.\n") 174 | parser.print_help() 175 | exit(1) 176 | 177 | json_root = get_json_directory(for_writing=results.t) 178 | 179 | # The directory where this archive.py is located 180 | repo_root = os.path.dirname(os.path.realpath(__file__)) 181 | 182 | if results.b: 183 | md_root = get_html_directory() 184 | 185 | if results.t or results.i: 186 | is_valid_stream_name = stream_validator(settings) 187 | 188 | client, zulip_url = get_client_info() 189 | 190 | if results.t: 191 | populate_all( 192 | client, 193 | json_root, 194 | is_valid_stream_name, 195 | ) 196 | 197 | elif results.i: 198 | populate_incremental( 199 | client, 200 | json_root, 201 | is_valid_stream_name, 202 | ) 203 | 204 | if results.b: 205 | build_website( 206 | json_root, 207 | md_root, 208 | settings.site_url, 209 | settings.html_root, 210 | settings.title, 211 | zulip_url, 212 | settings.zulip_icon_url, 213 | repo_root, 214 | settings.page_head_html, 215 | settings.page_footer_html, 216 | ) 217 | if not results.no_sitemap: 218 | build_sitemap(settings.site_url, md_root.as_posix(), md_root.as_posix()) 219 | 220 | 221 | if __name__ == "__main__": 222 | run() 223 | -------------------------------------------------------------------------------- /assets/img/zulip.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | -------------------------------------------------------------------------------- /default_settings.py: -------------------------------------------------------------------------------- 1 | # Welcome to default_settings.py! You will want to modify these values 2 | # for your own needs and then copy them to settings.py. Copying them 3 | # in the same directory is the mostly likely choice here: 4 | # 5 | # cp default_settings settings.py 6 | # settings.py 7 | # 8 | # If you prefer to keep the settings elsewhere, just make sure they 9 | # are in your Python path. 10 | 11 | import os 12 | import yaml 13 | from pathlib import Path 14 | 15 | """ 16 | You generally want to start in debug mode to test out the archive, 17 | and then set PROD_ARCHIVE to turn on production settings here. In 18 | production you usually change two things--the site_url and your 19 | html_directory. 20 | """ 21 | 22 | if os.getenv("PROD_ARCHIVE"): 23 | DEBUG = False 24 | else: 25 | DEBUG = True 26 | 27 | """ 28 | Set the site url. The default below is good for local testing, but you will 29 | definitely need to set your own value for prod. 30 | """ 31 | 32 | if DEBUG: 33 | site_url = "http://127.0.0.1:4000" 34 | else: 35 | site_url = os.getenv("SITE_URL") 36 | if not site_url: 37 | raise Exception("You need to configure site_url for prod") 38 | 39 | """ 40 | Set the zulip icon url. Folks can press the icon to see a 41 | message in the actual Zulip instance. 42 | """ 43 | 44 | if DEBUG: 45 | zulip_icon_url = "http://127.0.0.1:4000/assets/img/zulip.svg" 46 | else: 47 | # Set this according to how you serve your prod assets. 48 | zulip_icon_url = os.getenv("ZULIP_ICON_URL", None) 49 | 50 | 51 | """ 52 | Set the HTML title of your Zulip archive here. 53 | """ 54 | title = "Zulip Chat Archive" # Modify me! 55 | 56 | """ 57 | Set the path prefix of your URLs for your website. 58 | 59 | For example, you might want your main page to have 60 | the path of archive/index.html 61 | """ 62 | html_root = os.getenv("HTML_ROOT", "archive") # Modify me! 63 | 64 | """ 65 | When we get content from your Zulip instance, we first create 66 | JSON files that include all of the content data from the Zulip 67 | instance. Having the data in JSON makes it easy to incrementally 68 | update your data as new messages come in. 69 | 70 | You will want to put this in a permanent location outside of 71 | your repo. Here we assume a sibling directory named zulip_json, but 72 | you may prefer another directory structure. 73 | """ 74 | 75 | json_directory = Path(os.getenv("JSON_DIRECTORY", "../zulip_json")) 76 | 77 | """ 78 | We write HTML to here. 79 | """ 80 | if DEBUG: 81 | html_directory = Path("./archive") # Modify me! 82 | else: 83 | try: 84 | html_directory = Path(os.getenv("HTML_DIRECTORY", None)) 85 | except TypeError: 86 | raise Exception( 87 | """ 88 | You need to set html_directory for prod, and it 89 | should be a different location than DEBUG mode, 90 | since files will likely have different urls in 91 | anchor tags. 92 | """ 93 | ) 94 | 95 | 96 | """ 97 | This is where you modify the section of every page. 98 | """ 99 | page_head_html = ( 100 | '\nZulip Chat Archive\n' 101 | ) 102 | 103 | """ 104 | This is where you modify the