├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── gmailfilterrecipes
├── __init__.py
├── client
│ ├── __init__.py
│ ├── index.py
│ └── static
│ │ ├── coffee
│ │ └── recipes.coffee
│ │ └── html
│ │ └── index.html
├── jsonschemas.py
├── tests
│ ├── __init__.py
│ ├── available-filters.yml
│ ├── data
│ │ ├── filters.xml
│ │ └── user_recipe_set.yml
│ └── tests.py
└── xmlgeneration.py
├── gmailfilterxml
├── __init__.py
├── api.py
├── tests
│ ├── __init__.py
│ ├── many-filters.xml
│ ├── single-filter.xml
│ └── tests.py
└── xmlschemas.py
├── recipesets
└── default.yml
├── requirements.txt
├── run_server.py
├── settings.ini
└── utils
├── __init__.py
└── unittest.py
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | .idea/
3 |
4 | *.js
5 | *.js.map
6 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: python
2 | python:
3 | - "2.7"
4 | install: "pip install -r requirements.txt"
5 | script:
6 | - "python -m unittest gmailfilterxml.tests gmailfilterrecipes.tests"
7 | - "python -m doctest README.md"
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 Dimagi
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 | # Gmail Filters
2 |
3 | Python library and simple web app for creating GMail filters.
4 |
5 | See [GMail help](https://support.google.com/mail/answer/6579) for info on importing filters into GMail.
6 |
7 | ## Install
8 |
9 | To install requirements:
10 |
11 | ```bash
12 | pip install -r requirements.txt
13 | npm install -g coffee-script
14 | ```
15 |
16 | ## Compile Coffee
17 |
18 | ```bash
19 | coffee --compile gmailfilterrecipes/client/static/coffee/*.coffee
20 | ```
21 |
22 |
23 | ## Run
24 |
25 | Just run
26 |
27 | ```bash
28 | python run_server.py
29 | ```
30 |
31 | and the web frontend should be running at http://localhost:8080/.
32 |
33 | ## Configure your recipeset
34 |
35 | Each organization or team within an organization will have a different set of filters that are relevant to them;
36 | each member of that team may then want to further tweak the base set to conform to their particular needs. For example,
37 | everyone may want a basic filter labeling emails that contain their name, but each person's name is different, so each person's
38 | filter must be tweaked accordingly.
39 |
40 | In this framework, the shared filter descriptions are codified as "recipe sets", which are then displayed to the individual through
41 | a web interface, so that they may customize it to themself by simply filling out a web form.
42 |
43 | An example recipe set (which is used by the dev team at Dimagi) is included in this repo at `recipesets/default.yml`.
44 | To use your own instead, simply change the `gmailfilterrecipes.filters_yml` option in `settings.ini` to point to your own.
45 |
46 | A recipeset yaml file has two toplevel components, `options` and `recipes`: `options` let you specify filter options that can be shared
47 | across all your recipes, which are then defined under `recipes`. A recipe looks somethin like this:
48 |
49 | ```yaml
50 | -
51 | label: Mails directly to me
52 | custom_options:
53 | -
54 | key: names
55 | label: "List your email addresses"
56 | type: list
57 | match:
58 | to: "{{ names|join(' OR ') }}"
59 | filters:
60 | - label: =^.^=
61 | shouldNeverSpam: true
62 | shouldAlwaysMarkAsImportant: true
63 | ```
64 |
65 | Each recipe specifies a row in the web form that will be shown to the individual. In this case, that row would be labelled
66 | "Mails directly to me", they would require the individual to specify a list of email addresses (could also be just a single one),
67 | and would create a filter that matches on those emails, applies a gmail label named "=^.^=", keeps it from going to spam,
68 | and marks it as important.
69 |
70 | ## Library Usage
71 |
72 | If you aren't interested in the front end, you can use `gmailfilterxml`
73 | as a library for reading and writing gmail filter xml files.
74 |
75 | To create a simple gmail filter:
76 |
77 | ```python
78 | >>> from gmailfilterxml import GmailFilterSet, GmailFilter
79 | >>> import datetime
80 | >>>
81 | >>>
82 | >>> filter_set = GmailFilterSet(
83 | ... author_name='Danny Roberts',
84 | ... author_email='droberts@dimagi.com',
85 | ... updated_timestamp=datetime.datetime(2014, 9, 19, 17, 40, 28),
86 | ... filters=[
87 | ... GmailFilter(
88 | ... id='1286460749536',
89 | ... from_='noreply@github.com',
90 | ... label='github',
91 | ... shouldArchive=True,
92 | ... )
93 | ... ]
94 | ... )
95 | >>> print filter_set.to_xml(pretty=True),
96 |
97 |
98 | Mail Filters
99 | tag:mail.google.com,2008:filters:1286460749536
100 | 2014-09-19T17:40:28Z
101 |
102 | Danny Roberts
103 | droberts@dimagi.com
104 |
105 |
106 |
107 | Mail Filter
108 | tag:mail.google.com,2008:filter:1286460749536
109 | 2014-09-19T17:40:28Z
110 |
111 |
112 |
113 |
114 |
115 |
116 | >>>
117 | ```
118 |
--------------------------------------------------------------------------------
/gmailfilterrecipes/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimagi/gmail-filters/bea5a547a35c786b155bcddfa40843680ad8bbe0/gmailfilterrecipes/__init__.py
--------------------------------------------------------------------------------
/gmailfilterrecipes/client/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimagi/gmail-filters/bea5a547a35c786b155bcddfa40843680ad8bbe0/gmailfilterrecipes/client/__init__.py
--------------------------------------------------------------------------------
/gmailfilterrecipes/client/index.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | import json
3 | import bottle
4 | import yaml
5 | from gmailfilterrecipes.jsonschemas import UserRecipeSet, RecipeSet
6 | from gmailfilterrecipes.xmlgeneration import generate_gmail_fitler_set
7 |
8 | app = bottle.default_app()
9 |
10 | app.config.load_config('settings.ini')
11 |
12 | @bottle.route('/')
13 | def index():
14 | return bottle.static_file('index.html',
15 | root='gmailfilterrecipes/client/static/html/')
16 |
17 |
18 | @bottle.route('/filters.json')
19 | def filters_json():
20 | filters_yml = app.config.get('gmailfilterrecipes.filters_yml')
21 | with open(filters_yml) as f:
22 | filters = yaml.load(f)
23 | user_recipe_set = UserRecipeSet.from_recipe_set(RecipeSet.wrap(filters))
24 | return json.dumps(user_recipe_set.to_json())
25 |
26 |
27 | @bottle.post('/filters.xml')
28 | def filters_xml():
29 | bottle.response.headers.update({
30 | 'Content-Type': 'application/xml; charset="utf-8"',
31 | 'Content-Disposition': 'attachment; filename="gmailFilters.xml"',
32 | })
33 |
34 | recipe_set_json = (
35 | bottle.request.json
36 | or json.loads(bottle.request.POST.get('recipeSet'))
37 | )
38 | user_recipe_set = UserRecipeSet.wrap(recipe_set_json)
39 | gmail_filter_set = generate_gmail_fitler_set(user_recipe_set)
40 |
41 | return gmail_filter_set.to_xml(pretty=True)
42 |
43 |
44 | @bottle.route('/static/')
45 | def static(path):
46 | return bottle.static_file(path, root='gmailfilterrecipes/client/static')
47 |
--------------------------------------------------------------------------------
/gmailfilterrecipes/client/static/coffee/recipes.coffee:
--------------------------------------------------------------------------------
1 | recipesApp = angular.module('recipesApp', [])
2 |
3 | recipesApp.controller 'RecipesController', ($scope, $http, $window) ->
4 | $scope.recipeSetJson = ''
5 | $scope.show = false
6 |
7 | $http.get('/filters.json').success (data) ->
8 | $scope.recipeSet = data
9 | $scope.recipeSetJson = angular.toJson data, true
10 |
11 | $scope.filtersXml = ''
12 | $scope.refreshFiltersXml = ->
13 | $http.post('/filters.xml', $scope.recipeSet).success (data) ->
14 | $scope.filtersXml = data
15 | $scope.downloadFiltersXml = ->
16 | $scope.filtersXml = ''
17 | $window.post('/filters.xml?', $scope.recipeSet)
18 | $scope.updateForm = ->
19 | $scope.recipeSet = JSON.parse $scope.recipeSetJson
20 | $scope.updateRecipe = ->
21 | $scope.recipeSetJson = angular.toJson $scope.recipeSet, true
22 | $scope.showRecipe = ->
23 | $scope.show = true
24 | $scope.selectAll = (value) ->
25 | # value should be true or false
26 | for recipe in $scope.recipeSet.recipes
27 | recipe.selected = value
--------------------------------------------------------------------------------
/gmailfilterrecipes/client/static/html/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |