├── .cico.pipeline ├── CHANGES.txt ├── github2fedmsg ├── templates │ ├── widget.mak │ ├── index.mak │ ├── __init__.py │ └── master.mak ├── static │ ├── favicon.ico │ ├── footerbg.png │ ├── headerbg.png │ ├── middlebg.png │ ├── pyramid.png │ ├── transparent.gif │ ├── github2fedmsg.png │ ├── pyramid-small.png │ ├── bootstrap-3.1.1-fedora │ │ ├── fonts │ │ │ ├── glyphicons-halflings-regular.eot │ │ │ ├── glyphicons-halflings-regular.ttf │ │ │ └── glyphicons-halflings-regular.woff │ │ └── css │ │ │ ├── bootstrap-theme.min.css │ │ │ └── bootstrap-theme.css │ ├── ie6.css │ ├── messenger-1.4.1 │ │ ├── LICENSE │ │ ├── js │ │ │ ├── messenger-theme-flat.js │ │ │ ├── messenger-theme-future.js │ │ │ └── messenger.min.js │ │ └── css │ │ │ ├── messenger.css │ │ │ ├── messenger-theme-block.css │ │ │ ├── messenger-theme-ice.css │ │ │ ├── messenger-spinner.css │ │ │ ├── messenger-theme-air.css │ │ │ ├── messenger-theme-flat.css │ │ │ └── messenger-theme-future.css │ └── github2fedmsg.css ├── widgets │ ├── templates │ │ ├── __init__.py │ │ └── profile.mak │ ├── __init__.py │ ├── static │ │ └── profile.js │ └── users.py ├── scripts │ ├── __init__.py │ └── initializedb.py ├── events.py ├── tests.py ├── views │ ├── __init__.py │ ├── auth.py │ └── webhooks.py ├── custom_openid.py ├── traversal.py ├── models │ ├── jsonifiable.py │ └── __init__.py ├── githubutils.py └── __init__.py ├── .gitignore ├── MANIFEST.in ├── tox.ini ├── fedora-messaging.toml.example ├── secret.ini.example ├── TODO.md ├── README.rst ├── development.ini ├── setup.py ├── tools └── fake-github-post.py └── CHANGELOG.rst /.cico.pipeline: -------------------------------------------------------------------------------- 1 | fedoraInfraTox { } 2 | -------------------------------------------------------------------------------- /CHANGES.txt: -------------------------------------------------------------------------------- 1 | 0.0 2 | --- 3 | 4 | - Initial version 5 | -------------------------------------------------------------------------------- /github2fedmsg/templates/widget.mak: -------------------------------------------------------------------------------- 1 | <%inherit file="master.mak"/> 2 | ${widget.display() | n} 3 | -------------------------------------------------------------------------------- /github2fedmsg/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-infra/github2fedmsg/develop/github2fedmsg/static/favicon.ico -------------------------------------------------------------------------------- /github2fedmsg/static/footerbg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-infra/github2fedmsg/develop/github2fedmsg/static/footerbg.png -------------------------------------------------------------------------------- /github2fedmsg/static/headerbg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-infra/github2fedmsg/develop/github2fedmsg/static/headerbg.png -------------------------------------------------------------------------------- /github2fedmsg/static/middlebg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-infra/github2fedmsg/develop/github2fedmsg/static/middlebg.png -------------------------------------------------------------------------------- /github2fedmsg/static/pyramid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-infra/github2fedmsg/develop/github2fedmsg/static/pyramid.png -------------------------------------------------------------------------------- /github2fedmsg/static/transparent.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-infra/github2fedmsg/develop/github2fedmsg/static/transparent.gif -------------------------------------------------------------------------------- /github2fedmsg/static/github2fedmsg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-infra/github2fedmsg/develop/github2fedmsg/static/github2fedmsg.png -------------------------------------------------------------------------------- /github2fedmsg/static/pyramid-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-infra/github2fedmsg/develop/github2fedmsg/static/pyramid-small.png -------------------------------------------------------------------------------- /github2fedmsg/static/bootstrap-3.1.1-fedora/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-infra/github2fedmsg/develop/github2fedmsg/static/bootstrap-3.1.1-fedora/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /github2fedmsg/static/bootstrap-3.1.1-fedora/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-infra/github2fedmsg/develop/github2fedmsg/static/bootstrap-3.1.1-fedora/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /github2fedmsg/static/bootstrap-3.1.1-fedora/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-infra/github2fedmsg/develop/github2fedmsg/static/bootstrap-3.1.1-fedora/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.swp 3 | ez_setup 4 | tw2* 5 | *.db* 6 | data 7 | build 8 | dist 9 | doc/build 10 | *.egg* 11 | README.pdf 12 | *.pid 13 | *.log 14 | github2fedmsg/githubsecrets.* 15 | web198.ini 16 | testlog.txt 17 | secret.ini 18 | .tox 19 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *.txt development.ini alembic.ini secret.ini.example *.cfg *.rst LICENSE 2 | prune secret.ini 3 | graft github2fedmsg/static 4 | graft github2fedmsg/templates 5 | graft github2fedmsg/widgets/templates 6 | graft github2fedmsg/widgets/static 7 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py27 3 | skip_missing_interpreters = True 4 | 5 | [testenv] 6 | commands = 7 | python -m unittest -v {posargs:github2fedmsg.tests} 8 | passenv = HOME 9 | 10 | [testenv:lint] 11 | basepython = python3 12 | commands = 13 | python -m flake8 github2fedmsg 14 | deps = 15 | flake8>3.0 16 | -------------------------------------------------------------------------------- /fedora-messaging.toml.example: -------------------------------------------------------------------------------- 1 | # Example configuraton for Fedora Messaging 2 | 3 | # Broker address 4 | amqp_url = "amqp://" 5 | 6 | # Authentication is TLS-based 7 | [tls] 8 | ca_cert = "/etc/pki/tls/certs/ca-bundle.crt" 9 | keyfile = "/my/client/key.pem" 10 | certfile = "/my/client/cert.pem" 11 | 12 | [client_properties] 13 | app = "github2fedmsg" 14 | -------------------------------------------------------------------------------- /secret.ini.example: -------------------------------------------------------------------------------- 1 | [github2fedmsg] 2 | 3 | # This is the "application" key that is used to negotiate oauth on behalf of 4 | # other users: https://github.com/settings/applications/new 5 | velruse.github.consumer_secret = create-your-own 6 | 7 | # This is our "own" oauth bearer token that we use to make authenticated github 8 | # queries: https://github.com/settings/tokens/new 9 | github.secret_oauth_access_token = create-your-own 10 | -------------------------------------------------------------------------------- /github2fedmsg/templates/index.mak: -------------------------------------------------------------------------------- 1 | <%inherit file="master.mak"/> 2 | 3 |
4 |

github2fedmsg 5 | 6 |

7 |

Have stuff on github? We'll put it on the fedmsg bus. 8 |

9 | %if not request.user: 10 | 11 | 12 | Sign in with FAS 13 | 14 | %endif 15 |
16 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | - [X] clean out the model to only what we need 2 | - [X] Add FAS <-> Github account linking. 3 | - [X] set up irc notifications to #fedora-apps for development 4 | - [X] Remove pygithub3, replace with our own lib. 5 | - [X] Remove pep8bot branding 6 | - [X] Style with robyduck's latest bootstrap fedora 7 | - [X] Add fedmsg publication code, test it. 8 | - [X] Set up a cloud node for shared dev/testing 9 | - [X] Write fedmsg.meta processors 10 | - [X] Package it up; package review. 11 | - [X] Take another pass through the UI and clean it up. @ryanlerch is working 12 | on a nicer design; in the meantime we can spruce it up ourselves. 13 | - [X] Staging/ansible 14 | - [X] Production 15 | -------------------------------------------------------------------------------- /github2fedmsg/static/ie6.css: -------------------------------------------------------------------------------- 1 | * html img, 2 | * html .png{position:relative;behavior:expression((this.runtimeStyle.behavior="none")&&(this.pngSet?this.pngSet=true:(this.nodeName == "IMG" && this.src.toLowerCase().indexOf('.png')>-1?(this.runtimeStyle.backgroundImage = "none", 3 | this.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this.src + "',sizingMethod='image')", 4 | this.src = "static/transparent.gif"):(this.origBg = this.origBg? this.origBg :this.currentStyle.backgroundImage.toString().replace('url("','').replace('")',''), 5 | this.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this.origBg + "',sizingMethod='crop')", 6 | this.runtimeStyle.backgroundImage = "none")),this.pngSet=true) 7 | );} 8 | #wrap{display:table;height:100%} 9 | -------------------------------------------------------------------------------- /github2fedmsg/templates/__init__.py: -------------------------------------------------------------------------------- 1 | # This file is a part of github2fedmsg, a pubsubhubbub to zeromq bridge. 2 | # Copyright (C) 2014, Red Hat, Inc. 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Affero General Public License as 6 | # published by the Free Software Foundation, either version 3 of the 7 | # License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Affero General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Affero General Public License 15 | # along with this program. If not, see . 16 | 17 | -------------------------------------------------------------------------------- /github2fedmsg/widgets/templates/__init__.py: -------------------------------------------------------------------------------- 1 | # This file is a part of github2fedmsg, a pubsubhubbub to zeromq bridge. 2 | # Copyright (C) 2014, Red Hat, Inc. 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Affero General Public License as 6 | # published by the Free Software Foundation, either version 3 of the 7 | # License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Affero General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Affero General Public License 15 | # along with this program. If not, see . 16 | 17 | -------------------------------------------------------------------------------- /github2fedmsg/scripts/__init__.py: -------------------------------------------------------------------------------- 1 | # This file is a part of github2fedmsg, a pubsubhubbub to zeromq bridge. 2 | # Copyright (C) 2014, Red Hat, Inc. 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Affero General Public License as 6 | # published by the Free Software Foundation, either version 3 of the 7 | # License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Affero General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Affero General Public License 15 | # along with this program. If not, see . 16 | 17 | # package 18 | -------------------------------------------------------------------------------- /github2fedmsg/widgets/__init__.py: -------------------------------------------------------------------------------- 1 | # This file is a part of github2fedmsg, a pubsubhubbub to zeromq bridge. 2 | # Copyright (C) 2014, Red Hat, Inc. 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Affero General Public License as 6 | # published by the Free Software Foundation, either version 3 of the 7 | # License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Affero General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Affero General Public License 15 | # along with this program. If not, see . 16 | 17 | from .users import ( 18 | UserProfile, 19 | ) 20 | -------------------------------------------------------------------------------- /github2fedmsg/widgets/static/profile.js: -------------------------------------------------------------------------------- 1 | function subscribe(link) { 2 | $.ajax(link, { 3 | success: function(json, stat, xhr) { 4 | var name, sel; 5 | name = json.repo.name; 6 | name = name.replace(/\./g, '\\.'); 7 | sel = $("#"+json.github_username+'-'+name); 8 | sel.toggleClass('btn-success'); 9 | sel.toggleClass('btn-default'); 10 | 11 | if (json.repo.enabled) { 12 | sel.html("On"); 13 | } else { 14 | sel.html("Off"); 15 | } 16 | }, 17 | error: function(json, stat, xhr) { 18 | Messenger({theme: 'flat'}).post({ 19 | message: "Sorry. There was an error on the server.", 20 | type: "error", 21 | }); 22 | console.log('error'); 23 | console.log(json); 24 | }, 25 | }); 26 | } 27 | -------------------------------------------------------------------------------- /github2fedmsg/static/messenger-1.4.1/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 HubSpot, Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | 9 | -------------------------------------------------------------------------------- /github2fedmsg/static/github2fedmsg.css: -------------------------------------------------------------------------------- 1 | 2 | .profile-header h1 { 3 | 4 | padding-left: 25px; 5 | } 6 | 7 | .profile-header form { 8 | display: inline-block; 9 | } 10 | 11 | .profile-buttons { 12 | margin-top: -20px; 13 | margin-right: 20px; 14 | } 15 | 16 | .vspace { 17 | height: 40px; 18 | } 19 | 20 | .content { 21 | margin-left: auto; 22 | margin-right: auto; 23 | width: 100%; 24 | /*width: 0px;*/ 25 | } 26 | 27 | .logo { 28 | margin-top: 20px; 29 | margin-bottom: 20px; 30 | margin-left: auto; 31 | margin-right: auto; 32 | display: inline-block; 33 | } 34 | 35 | form.navbar-form { 36 | margin-left: 50px; 37 | padding-top: 2px; 38 | } 39 | 40 | .masthead { 41 | margin-top: 140px; 42 | font-size: 120%; 43 | } 44 | .masthead h1 { 45 | font-size: 64pt; 46 | } 47 | .masthead .lead { 48 | font-size: 28pt; 49 | } 50 | 51 | 52 | /* Sticky footer styles */ 53 | html, 54 | body { 55 | height: 100%; 56 | } 57 | 58 | .max-width { 59 | min-width: 100%; 60 | } 61 | 62 | #wrap { 63 | min-height: 100%; 64 | height: auto; 65 | margin: 0 auto -45px; 66 | padding: 0 0 60px; 67 | } 68 | 69 | #footer { 70 | height: 40px; 71 | background-color: #f5f5f5; 72 | border-top: 1px dashed black; 73 | text-align: center; 74 | } 75 | 76 | #footer { 77 | padding: 10px; 78 | } 79 | -------------------------------------------------------------------------------- /github2fedmsg/static/messenger-1.4.1/js/messenger-theme-flat.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var $, FlatMessage, spinner_template, 3 | __hasProp = {}.hasOwnProperty, 4 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; 5 | 6 | $ = jQuery; 7 | 8 | spinner_template = '
\n \n \n \n \n \n \n
'; 9 | 10 | FlatMessage = (function(_super) { 11 | 12 | __extends(FlatMessage, _super); 13 | 14 | function FlatMessage() { 15 | return FlatMessage.__super__.constructor.apply(this, arguments); 16 | } 17 | 18 | FlatMessage.prototype.template = function(opts) { 19 | var $message; 20 | $message = FlatMessage.__super__.template.apply(this, arguments); 21 | $message.append($(spinner_template)); 22 | return $message; 23 | }; 24 | 25 | return FlatMessage; 26 | 27 | })(window.Messenger.Message); 28 | 29 | window.Messenger.themes.flat = { 30 | Message: FlatMessage 31 | }; 32 | 33 | }).call(this); 34 | -------------------------------------------------------------------------------- /github2fedmsg/static/messenger-1.4.1/js/messenger-theme-future.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var $, FutureMessage, spinner_template, 3 | __hasProp = {}.hasOwnProperty, 4 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; 5 | 6 | $ = jQuery; 7 | 8 | spinner_template = '
\n \n \n \n \n \n \n
'; 9 | 10 | FutureMessage = (function(_super) { 11 | 12 | __extends(FutureMessage, _super); 13 | 14 | function FutureMessage() { 15 | return FutureMessage.__super__.constructor.apply(this, arguments); 16 | } 17 | 18 | FutureMessage.prototype.template = function(opts) { 19 | var $message; 20 | $message = FutureMessage.__super__.template.apply(this, arguments); 21 | $message.append($(spinner_template)); 22 | return $message; 23 | }; 24 | 25 | return FutureMessage; 26 | 27 | })(window.Messenger.Message); 28 | 29 | window.Messenger.themes.future = { 30 | Message: FutureMessage 31 | }; 32 | 33 | }).call(this); 34 | -------------------------------------------------------------------------------- /github2fedmsg/events.py: -------------------------------------------------------------------------------- 1 | # This file is a part of github2fedmsg, a pubsubhubbub to zeromq bridge. 2 | # Copyright (C) 2014, Red Hat, Inc. 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Affero General Public License as 6 | # published by the Free Software Foundation, either version 3 of the 7 | # License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Affero General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Affero General Public License 15 | # along with this program. If not, see . 16 | 17 | from pyramid.threadlocal import get_current_request 18 | from pyramid.events import subscriber 19 | from pyramid.events import BeforeRender 20 | from pyramid.security import authenticated_userid 21 | 22 | import tw2.core 23 | 24 | 25 | def when_ready(func): 26 | """ 27 | Takes a js_function and returns a js_callback that will run 28 | when the document is ready. 29 | """ 30 | return tw2.core.js_callback( 31 | '$(document).ready(function(){' + str(func) + '});' 32 | ) 33 | 34 | 35 | @subscriber(BeforeRender) 36 | def inject_globals(event): 37 | request = get_current_request() 38 | 39 | # Expose these as global attrs for our templates 40 | event['identity'] = authenticated_userid(request) 41 | 42 | when_ready( 43 | "$('.dropdown-toggle').dropdown();" 44 | ) 45 | -------------------------------------------------------------------------------- /github2fedmsg/scripts/initializedb.py: -------------------------------------------------------------------------------- 1 | # This file is a part of github2fedmsg, a pubsubhubbub to zeromq bridge. 2 | # Copyright (C) 2014, Red Hat, Inc. 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Affero General Public License as 6 | # published by the Free Software Foundation, either version 3 of the 7 | # License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Affero General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Affero General Public License 15 | # along with this program. If not, see . 16 | 17 | import os 18 | import sys 19 | import transaction 20 | 21 | from sqlalchemy import engine_from_config 22 | 23 | from pyramid.paster import ( 24 | get_appsettings, 25 | setup_logging, 26 | ) 27 | 28 | from ..models import ( 29 | DBSession, 30 | Base, 31 | ) 32 | 33 | def usage(argv): 34 | cmd = os.path.basename(argv[0]) 35 | print('usage: %s \n' 36 | '(example: "%s development.ini")' % (cmd, cmd)) 37 | sys.exit(1) 38 | 39 | def main(argv=sys.argv): 40 | if len(argv) != 2: 41 | usage(argv) 42 | 43 | config_uri = argv[1] 44 | setup_logging(config_uri) 45 | settings = get_appsettings(config_uri, name="github2fedmsg") 46 | engine = engine_from_config(settings, 'sqlalchemy.') 47 | DBSession.configure(bind=engine) 48 | Base.metadata.create_all(engine) 49 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | github2fedmsg 2 | ------------- 3 | 4 | A bot broadcasts every action made on your repo hosted on GitHub on the 5 | `fedora-messaging `_ message bus. 6 | 7 | It is a web application that monitors GitHub repositories you subscribe it to. 8 | When new actions (commits, pull-request, tickets) are made, it broadcasts a 9 | message on the `fedora-messaging`_ message bus. 10 | 11 | You can see all the current messages with `datagrepper's "github" category 12 | `_. 13 | 14 | It is written in Python on the Pyramid framework, and uses `velruse 15 | `_ to talk with GitHub. It adds a webhook callback 16 | back to itself on repositories you ask it to monitor. When one of those 17 | callbacks fire, github2fedmsg republishes the message it receives to the 18 | `fedora-messaging`_ bus. 19 | 20 | Hacking 21 | ------- 22 | 23 | If you run into trouble with these instructions, feel free to open a ticket 24 | or get in touch with me directly. 25 | 26 | Fork and clone the following two repositories: 27 | 28 | - http://github.com/fedora-infra/github2fedmsg 29 | 30 | Using `virtualenvwrapper `_:: 31 | 32 | $ cd github2fedmsg 33 | $ mkvirtualenv github2fedmsg 34 | $ python setup.py develop 35 | $ pip install waitress 36 | 37 | Go off and `register your development application with GitHub 38 | `_. Save the oauth tokens and add 39 | the secret one to a new file you create called ``secret.ini``. Use the example 40 | ``secret.ini.example`` file. 41 | 42 | 43 | Create the database:: 44 | 45 | $ initialize_github2fedmsg_db development.ini 46 | 47 | 48 | Now, start the webapp:: 49 | 50 | $ workon github2fedmsg 51 | $ pserve development.ini --reload 52 | -------------------------------------------------------------------------------- /development.ini: -------------------------------------------------------------------------------- 1 | [app:github2fedmsg] 2 | use = egg:github2fedmsg 3 | 4 | pyramid.reload_templates = true 5 | pyramid.default_locale_name = en 6 | pyramid.includes = 7 | pyramid_tm 8 | 9 | sqlalchemy.url = sqlite:///%(here)s/github2fedmsg.db 10 | 11 | mako.directories = github2fedmsg:templates 12 | 13 | # github2fedmsg developer key. 14 | velruse.github.consumer_key = ee676d3349157a49fbbd 15 | velruse.github.scope = read:org,admin:repo_hook 16 | 17 | # For talking with FAS 18 | velruse.openid.identifier = https://id.fedoraproject.org/ 19 | velruse.openid.realm = http://127.0.0.1:6543 20 | 21 | # By default, github cannot actually reach you here. 22 | github.callback = http://127.0.0.1:6543/webhook 23 | github.secret = changeme! 24 | 25 | session.secret = changeme2 26 | authnsecret = changeme3 27 | 28 | [pipeline:main] 29 | pipeline = 30 | egg:WebError#evalerror 31 | tw2 32 | github2fedmsg 33 | 34 | [filter:tw2] 35 | use = egg:tw2.core#middleware 36 | 37 | [server:main] 38 | use = egg:waitress#main 39 | host = 127.0.0.1 40 | port = 6543 41 | 42 | # Begin logging configuration 43 | 44 | [loggers] 45 | keys = root, github2fedmsg, sqlalchemy 46 | 47 | [handlers] 48 | keys = console 49 | 50 | [formatters] 51 | keys = generic 52 | 53 | [logger_root] 54 | level = INFO 55 | handlers = console 56 | 57 | [logger_github2fedmsg] 58 | level = DEBUG 59 | handlers = 60 | qualname = github2fedmsg 61 | 62 | [logger_sqlalchemy] 63 | level = WARN 64 | #level = INFO 65 | handlers = 66 | qualname = sqlalchemy.engine 67 | # "level = INFO" logs SQL queries. 68 | # "level = DEBUG" logs SQL queries and results. 69 | # "level = WARN" logs neither. (Recommended for production systems.) 70 | 71 | [handler_console] 72 | class = StreamHandler 73 | args = (sys.stderr,) 74 | level = NOTSET 75 | formatter = generic 76 | 77 | [formatter_generic] 78 | format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s 79 | 80 | # End logging configuration 81 | -------------------------------------------------------------------------------- /github2fedmsg/tests.py: -------------------------------------------------------------------------------- 1 | # This file is a part of github2fedmsg, a pubsubhubbub to zeromq bridge. 2 | # Copyright (C) 2014, Red Hat, Inc. 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Affero General Public License as 6 | # published by the Free Software Foundation, either version 3 of the 7 | # License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Affero General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Affero General Public License 15 | # along with this program. If not, see . 16 | 17 | import unittest 18 | import transaction 19 | 20 | from pyramid import testing 21 | from sqlalchemy import create_engine 22 | 23 | from .models import DBSession, Base, Repo 24 | from .views import home, widget_view 25 | 26 | class TestMyView(unittest.TestCase): 27 | def setUp(self): 28 | self.config = testing.setUp() 29 | engine = create_engine('sqlite://') 30 | DBSession.configure(bind=engine) 31 | Base.metadata.create_all(engine) 32 | with transaction.manager: 33 | repo = Repo(name='testrepo', description="Test Repo", language="cobol") 34 | DBSession.add(repo) 35 | 36 | def tearDown(self): 37 | DBSession.remove() 38 | testing.tearDown() 39 | 40 | def test_home(self): 41 | request = testing.DummyRequest() 42 | request.user = None 43 | result = home(request) 44 | self.assertEqual(result, {}) 45 | 46 | def test_widget_view(self): 47 | request = testing.DummyRequest() 48 | request.user = None 49 | result = widget_view(request) 50 | self.assertEqual(result, {'widget': None}) 51 | -------------------------------------------------------------------------------- /github2fedmsg/views/__init__.py: -------------------------------------------------------------------------------- 1 | # This file is a part of github2fedmsg, a pubsubhubbub to zeromq bridge. 2 | # Copyright (C) 2014, Red Hat, Inc. 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Affero General Public License as 6 | # published by the Free Software Foundation, either version 3 of the 7 | # License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Affero General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Affero General Public License 15 | # along with this program. If not, see . 16 | 17 | from pyramid.view import view_config 18 | from pyramid.security import authenticated_userid 19 | from pyramid.httpexceptions import HTTPFound, HTTPUnauthorized, HTTPForbidden 20 | 21 | import github2fedmsg.models as m 22 | 23 | 24 | @view_config(route_name='home', renderer='index.mak') 25 | def home(request): 26 | if request.user: 27 | return HTTPFound(location=request.user.username) 28 | return {} 29 | 30 | 31 | @view_config(name='sync', context=m.User, renderer='json') 32 | def sync_user(request): 33 | # TODO -- someday, learn how to do the __acls__ thing.. :/ 34 | username = request.context.username 35 | userid = authenticated_userid(request) 36 | if userid != username: 37 | raise HTTPUnauthorized() 38 | 39 | config_key = 'github.secret_oauth_access_token' 40 | value = request.registry.settings[config_key] 41 | oauth_creds = dict(access_token=value) 42 | 43 | import transaction 44 | request.context.sync_repos(oauth_creds) 45 | transaction.commit() 46 | home = request.route_url('home') 47 | raise HTTPFound(home + username) 48 | 49 | 50 | @view_config(context="tw2.core.widgets.WidgetMeta", 51 | renderer='widget.mak') 52 | def widget_view(request): 53 | return dict(widget=request.context) 54 | -------------------------------------------------------------------------------- /github2fedmsg/templates/master.mak: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | github2fedmsg - rebroadcast GitHub events to the fedmsg bus 5 | 6 | 8 | 10 | 12 | 13 | 15 | 17 | 19 | 21 | 22 | 23 |
24 | ${self.body()} 25 |
26 | 27 | 37 | 38 | % if request.registry.settings.get('fedmenu.url'): 39 | 40 | 47 | % endif 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /github2fedmsg/custom_openid.py: -------------------------------------------------------------------------------- 1 | # This file is a part of github2fedmsg, a pubsubhubbub to zeromq bridge. 2 | # Copyright (C) 2014, Red Hat, Inc. 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Affero General Public License as 6 | # published by the Free Software Foundation, either version 3 of the 7 | # License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Affero General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Affero General Public License 15 | # along with this program. If not, see . 16 | 17 | import velruse.api 18 | import velruse.providers.openid as vr 19 | 20 | from pyramid.security import NO_PERMISSION_REQUIRED 21 | 22 | 23 | def add_openid_login(config, realm, identity_provider): 24 | provider = SingleOpenIDConsumer( 25 | 'openid', 'openid', 26 | realm=realm, 27 | identity_provider=identity_provider, 28 | storage=None, 29 | ) 30 | login_path='/login/openid' 31 | callback_path='/login/openid/callback' 32 | config.add_route(provider.login_route, login_path) 33 | config.add_view(provider, attr='login', route_name=provider.login_route, 34 | permission=NO_PERMISSION_REQUIRED) 35 | config.add_route(provider.callback_route, callback_path, 36 | use_global_views=True, 37 | factory=provider.callback) 38 | velruse.api.register_provider(config, 'openid', provider) 39 | 40 | 41 | class SingleOpenIDConsumer(vr.OpenIDConsumer): 42 | def __init__(self, 43 | name, 44 | _type, 45 | realm=None, 46 | identity_provider=None, 47 | storage=None, 48 | context=vr.OpenIDAuthenticationComplete): 49 | super(SingleOpenIDConsumer, self).__init__( 50 | name, _type, realm, storage, context) 51 | self.identity_provider = identity_provider 52 | 53 | def _lookup_identifier(self, request, url): 54 | return self.identity_provider 55 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # This file is a part of github2fedmsg, a pubsubhubbub to zeromq bridge. 2 | # Copyright (C) 2014, Red Hat, Inc. 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Affero General Public License as 6 | # published by the Free Software Foundation, either version 3 of the 7 | # License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Affero General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Affero General Public License 15 | # along with this program. If not, see . 16 | 17 | import os 18 | 19 | from setuptools import setup, find_packages 20 | 21 | here = os.path.abspath(os.path.dirname(__file__)) 22 | README = open(os.path.join(here, 'README.rst')).read() 23 | CHANGES = open(os.path.join(here, 'CHANGES.txt')).read() 24 | 25 | requires = [ 26 | 'fedora-messaging', 27 | 'pyramid', 28 | 'SQLAlchemy', 29 | 'transaction', 30 | 'pyramid_tm', 31 | 'pyramid_mako', 32 | 'zope.sqlalchemy', 33 | 'weberror', 34 | 'velruse', 35 | 'tw2.core', 36 | ] 37 | 38 | setup(name='github2fedmsg', 39 | version='0.3.6', 40 | description='Pubsubhubbub app that rebroadcasts GitHub events over fedora-messaging', 41 | long_description=README + '\n\n' + CHANGES, 42 | classifiers=[ 43 | "Programming Language :: Python", 44 | "Framework :: Pylons", 45 | "Topic :: Internet :: WWW/HTTP", 46 | "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", 47 | ], 48 | author='Ralph Bean', 49 | author_email='rbean@redhat.com', 50 | url='https://github.com/fedora-infra/github2fedmsg', 51 | keywords='web wsgi bfg pylons pyramid', 52 | packages=find_packages(), 53 | include_package_data=True, 54 | zip_safe=False, 55 | test_suite='github2fedmsg', 56 | install_requires=requires, 57 | entry_points="""\ 58 | [paste.app_factory] 59 | main = github2fedmsg:main 60 | [console_scripts] 61 | initialize_github2fedmsg_db = github2fedmsg.scripts.initializedb:main 62 | """, 63 | ) 64 | 65 | -------------------------------------------------------------------------------- /github2fedmsg/widgets/users.py: -------------------------------------------------------------------------------- 1 | # This file is a part of github2fedmsg, a pubsubhubbub to zeromq bridge. 2 | # Copyright (C) 2014, Red Hat, Inc. 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Affero General Public License as 6 | # published by the Free Software Foundation, either version 3 of the 7 | # License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Affero General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Affero General Public License 15 | # along with this program. If not, see . 16 | 17 | import tw2.core as twc 18 | import github2fedmsg.models 19 | import pyramid.threadlocal 20 | from sqlalchemy import and_ 21 | 22 | 23 | class UserProfile(twc.Widget): 24 | template = "mako:github2fedmsg.widgets.templates.profile" 25 | user = twc.Param("An instance of the User SQLAlchemy model.") 26 | resources = [ 27 | twc.JSLink(filename="static/profile.js"), 28 | ] 29 | 30 | show_buttons = twc.Param("show my buttons?", default=False) 31 | 32 | def prepare(self): 33 | """ Query github for some information before display """ 34 | 35 | oauth_creds = dict(access_token=self.user.oauth_access_token) 36 | 37 | # Try to refresh list of repos only if the user has none. 38 | if self.user.github_username and \ 39 | self.user.oauth_access_token and \ 40 | not self.user.all_repos: 41 | self.user.sync_repos(oauth_creds) 42 | 43 | 44 | def make_button(self, repo): 45 | # TODO -- Can we use resource_url here? 46 | username = repo.user.username 47 | github_username = repo.user.github_username 48 | home = self.request.route_url('home') 49 | link = home + 'api/%s/%s/%s/toggle' % (username, github_username, repo.name) 50 | click = 'onclick="subscribe(\'%s\')"' % link 51 | 52 | if repo.enabled: 53 | cls, text = "btn-success", "On" 54 | else: 55 | cls, text = "btn-default", "Off" 56 | 57 | return "" % ( 58 | github_username, repo.name, cls, click, text) 59 | 60 | @property 61 | def request(self): 62 | return pyramid.threadlocal.get_current_request() 63 | -------------------------------------------------------------------------------- /github2fedmsg/traversal.py: -------------------------------------------------------------------------------- 1 | # This file is a part of github2fedmsg, a pubsubhubbub to zeromq bridge. 2 | # Copyright (C) 2014, Red Hat, Inc. 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Affero General Public License as 6 | # published by the Free Software Foundation, either version 3 of the 7 | # License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Affero General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Affero General Public License 15 | # along with this program. If not, see . 16 | 17 | from hashlib import md5 18 | import tw2.core as twc 19 | 20 | import github2fedmsg.models 21 | import github2fedmsg.widgets 22 | from pyramid.security import authenticated_userid 23 | 24 | 25 | def make_root(request): 26 | return RootApp(request) 27 | 28 | 29 | class RootApp(dict): 30 | __name__ = None 31 | __parent__ = None 32 | 33 | def __init__(self, request): 34 | dict.__init__(self) 35 | self.request = request 36 | self.static = dict( 37 | api=ApiApp(), 38 | ) 39 | 40 | def __getitem__(self, key): 41 | if key in self.static: 42 | return self.static[key] 43 | 44 | query = github2fedmsg.models.User.query.filter_by(username=key) 45 | if query.count() != 1: 46 | raise KeyError("No such user") 47 | 48 | user = query.one() 49 | 50 | # TODO -- use __acl__ machinery some day 51 | userid = authenticated_userid(self.request) 52 | # TODO -- check if this is an org that I own 53 | show_buttons = (userid == user.username) 54 | return UserApp(user=user, show_buttons=show_buttons) 55 | 56 | 57 | class ApiApp(object): 58 | def __getitem__(self, key): 59 | query = github2fedmsg.models.User.query.filter_by(username=key) 60 | if query.count() != 1: 61 | raise KeyError("No such user") 62 | return query.one() 63 | 64 | 65 | class UserApp(github2fedmsg.widgets.UserProfile): 66 | __name__ = None 67 | __parent__ = RootApp 68 | 69 | @classmethod 70 | def __getitem__(self, key): 71 | for repo in self.user.repos: 72 | if repo.name == key: 73 | return repo 74 | 75 | raise KeyError 76 | 77 | 78 | class APISuccess(object): 79 | def __init__(self, data): 80 | self.data = data 81 | -------------------------------------------------------------------------------- /github2fedmsg/widgets/templates/profile.mak: -------------------------------------------------------------------------------- 1 | ## -*- coding: utf-8 -*- 2 |
3 |
4 | 5 |
6 | 7 | 10 |
11 |
12 | 13 |

14 | ${w.user.username} 15 | ${w.user.full_name} 16 | %if w.user.github_username: 17 | (${w.user.github_username} on github with ${len(w.user.all_repos)} repos) 18 | %else: 19 | (github account not linked) 20 | %endif 21 | 22 |

23 |
24 |
25 |
26 | 27 | 28 | 29 | Sign out 30 | 31 | % if w.show_buttons: 32 | %if w.user.oauth_access_token: 33 | 34 | 35 | Refresh from Github 36 | 37 | 38 | 39 | Forget Github Authz 40 | 41 | %else: 42 | 43 | 44 | Link with Github 45 | 46 | %endif 47 | % endif 48 | 49 |
50 | 51 |
 
52 | 53 |
54 | 55 | 56 | 57 | 58 | 59 | 60 | % if w.show_buttons and w.user.oauth_access_token: 61 | 62 | % endif 63 | 64 | % for repo in list(w.user.all_repos): 65 | 66 | 67 | 68 | 69 | % if w.show_buttons and w.user.oauth_access_token: 70 | 71 | % endif 72 | 73 | % endfor 74 |
NameDescriptionLanguageEnabled?
${repo.user.github_username}/${repo.name}${repo.description}${repo.language}${w.make_button(repo) | n}
75 |
76 |
77 |
78 | -------------------------------------------------------------------------------- /github2fedmsg/models/jsonifiable.py: -------------------------------------------------------------------------------- 1 | # This file is a part of github2fedmsg, a pubsubhubbub to zeromq bridge. 2 | # Copyright (C) 2014, Red Hat, Inc. 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Affero General Public License as 6 | # published by the Free Software Foundation, either version 3 of the 7 | # License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Affero General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Affero General Public License 15 | # along with this program. If not, see . 16 | 17 | import datetime 18 | import time 19 | 20 | from sqlalchemy.orm import ( 21 | class_mapper, 22 | ) 23 | from sqlalchemy.orm.properties import RelationshipProperty 24 | 25 | 26 | class JSONifiable(object): 27 | """ A mixin for sqlalchemy models providing a .__json__ method. """ 28 | 29 | def __json__(self, seen=None): 30 | """ Returns a dict representation of the object. 31 | 32 | Recursively evaluates .__json__() on its relationships. 33 | """ 34 | 35 | if not seen: 36 | seen = [] 37 | 38 | properties = list(class_mapper(type(self)).iterate_properties) 39 | relationships = [ 40 | p.key for p in properties if type(p) is RelationshipProperty 41 | ] 42 | attrs = [ 43 | p.key for p in properties if p.key not in relationships 44 | ] 45 | 46 | d = dict([(attr, getattr(self, attr)) for attr in attrs]) 47 | 48 | for attr in relationships: 49 | d[attr] = self._expand(getattr(self, attr), seen) 50 | 51 | if hasattr(self, 'avatar'): 52 | d['avatar'] = self.avatar 53 | 54 | # Serialize datetime objects 55 | for k, v in d.items(): 56 | if isinstance(v, datetime.datetime): 57 | d[k] = time.mktime(v.timetuple()) 58 | 59 | return d 60 | 61 | def _expand(self, relation, seen): 62 | """ Return the __json__() or id of a sqlalchemy relationship. """ 63 | 64 | if hasattr(relation, 'all'): 65 | relation = relation.all() 66 | 67 | if hasattr(relation, '__iter__'): 68 | return [self._expand(item, seen) for item in relation] 69 | 70 | if not relation: 71 | return [] 72 | 73 | if type(relation) not in seen: 74 | return relation.__json__(seen + [type(self)]) 75 | else: 76 | return self._primary(relation) 77 | 78 | def _primary(self, obj): 79 | """ This is an ugly hack, and can be done much more nicely by 80 | introspecting primary keys... 81 | """ 82 | 83 | if hasattr(obj, 'id'): 84 | return obj.id 85 | else: 86 | return obj.username 87 | -------------------------------------------------------------------------------- /github2fedmsg/githubutils.py: -------------------------------------------------------------------------------- 1 | # This file is a part of github2fedmsg, a pubsubhubbub to zeromq bridge. 2 | # Copyright (C) 2014, Red Hat, Inc. 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Affero General Public License as 6 | # published by the Free Software Foundation, either version 3 of the 7 | # License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Affero General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Affero General Public License 15 | # along with this program. If not, see . 16 | 17 | """ Tools for querying github. 18 | 19 | I tried using pygithub3, but it really sucks. 20 | """ 21 | 22 | import requests 23 | 24 | 25 | def _link_field_to_dict(field): 26 | """ Utility for ripping apart github's Link header field. 27 | It's kind of ugly. 28 | """ 29 | 30 | if not field: 31 | return dict() 32 | 33 | return dict([ 34 | ( 35 | part.split('; ')[1][5:-1], 36 | part.split('; ')[0][1:-1], 37 | ) for part in field.split(', ') 38 | ]) 39 | 40 | 41 | def get_repos(username, auth): 42 | """ username should be a string 43 | auth should be a tuple of username and password. 44 | 45 | item can be one of "repos" or "orgs" 46 | """ 47 | 48 | tmpl = "https://api.github.com/users/{username}/repos?per_page=100" 49 | url = tmpl.format(username=username) 50 | return _getter(url, auth) 51 | 52 | def get_orgs(username, auth): 53 | tmpl = "https://api.github.com/users/{username}/orgs?per_page=100" 54 | url = tmpl.format(username=username) 55 | return _getter(url, auth) 56 | 57 | 58 | def _getter(url, auth): 59 | """ Pagination utility. Obnoxious. """ 60 | 61 | results = [] 62 | link = dict(next=url) 63 | while 'next' in link: 64 | 65 | if isinstance(auth, tuple): 66 | # Is it a (username, password) tuple? 67 | kwargs = dict(auth=auth) 68 | elif isinstance(auth, dict): 69 | # Or, is it an oauth bearer token? 70 | kwargs = dict(params=auth) 71 | else: 72 | raise TypeError("No clue how to handle github auth obj: %r" % auth) 73 | 74 | response = requests.get(link['next'], **kwargs) 75 | 76 | # And.. if we didn't get good results, just bail. 77 | if response.status_code != 200: 78 | raise IOError( 79 | "Non-200 status code %r; %r; %r" % ( 80 | response.status_code, url, response.json)) 81 | 82 | if callable(response.json): 83 | # Newer python-requests 84 | results += response.json() 85 | else: 86 | # Older python-requests 87 | results += response.json 88 | 89 | link = _link_field_to_dict(response.headers.get('link', None)) 90 | 91 | return results 92 | 93 | if __name__ == '__main__': 94 | # Little test. 95 | import getpass 96 | username = raw_input("GitHub Username: ") 97 | password = getpass.getpass() 98 | 99 | results = get_repos(username, (username, password)) 100 | print len(results), "repos found." 101 | -------------------------------------------------------------------------------- /github2fedmsg/static/messenger-1.4.1/css/messenger.css: -------------------------------------------------------------------------------- 1 | /* line 4, ../../src/sass/messenger.sass */ 2 | ul.messenger { 3 | margin: 0; 4 | padding: 0; 5 | } 6 | /* line 8, ../../src/sass/messenger.sass */ 7 | ul.messenger > li { 8 | list-style: none; 9 | margin: 0; 10 | padding: 0; 11 | } 12 | /* line 14, ../../src/sass/messenger.sass */ 13 | ul.messenger.messenger-empty { 14 | display: none; 15 | } 16 | /* line 17, ../../src/sass/messenger.sass */ 17 | ul.messenger .messenger-message { 18 | overflow: hidden; 19 | *zoom: 1; 20 | } 21 | /* line 20, ../../src/sass/messenger.sass */ 22 | ul.messenger .messenger-message.messenger-hidden { 23 | display: none; 24 | } 25 | /* line 23, ../../src/sass/messenger.sass */ 26 | ul.messenger .messenger-message .messenger-phrase, ul.messenger .messenger-message .messenger-actions a { 27 | padding-right: 5px; 28 | } 29 | /* line 26, ../../src/sass/messenger.sass */ 30 | ul.messenger .messenger-message .messenger-actions { 31 | float: right; 32 | } 33 | /* line 29, ../../src/sass/messenger.sass */ 34 | ul.messenger .messenger-message .messenger-actions a { 35 | cursor: pointer; 36 | text-decoration: underline; 37 | } 38 | /* line 33, ../../src/sass/messenger.sass */ 39 | ul.messenger .messenger-message ul, ul.messenger .messenger-message ol { 40 | margin: 10px 18px 0; 41 | } 42 | /* line 36, ../../src/sass/messenger.sass */ 43 | ul.messenger.messenger-fixed { 44 | position: fixed; 45 | z-index: 10000; 46 | } 47 | /* line 40, ../../src/sass/messenger.sass */ 48 | ul.messenger.messenger-fixed .messenger-message { 49 | min-width: 0; 50 | -webkit-box-sizing: border-box; 51 | -moz-box-sizing: border-box; 52 | box-sizing: border-box; 53 | } 54 | /* line 45, ../../src/sass/messenger.sass */ 55 | ul.messenger.messenger-fixed .message .messenger-actions { 56 | float: left; 57 | } 58 | /* line 48, ../../src/sass/messenger.sass */ 59 | ul.messenger.messenger-fixed.messenger-on-top { 60 | top: 20px; 61 | } 62 | /* line 51, ../../src/sass/messenger.sass */ 63 | ul.messenger.messenger-fixed.messenger-on-bottom { 64 | bottom: 20px; 65 | } 66 | /* line 54, ../../src/sass/messenger.sass */ 67 | ul.messenger.messenger-fixed.messenger-on-top, ul.messenger.messenger-fixed.messenger-on-bottom { 68 | left: 50%; 69 | width: 800px; 70 | margin-left: -400px; 71 | } 72 | @media (max-width: 960px) { 73 | /* line 54, ../../src/sass/messenger.sass */ 74 | ul.messenger.messenger-fixed.messenger-on-top, ul.messenger.messenger-fixed.messenger-on-bottom { 75 | left: 10%; 76 | width: 80%; 77 | margin-left: 0px; 78 | } 79 | } 80 | /* line 64, ../../src/sass/messenger.sass */ 81 | ul.messenger.messenger-fixed.messenger-on-top.messenger-on-right, ul.messenger.messenger-fixed.messenger-on-bottom.messenger-on-right { 82 | right: 20px; 83 | left: auto; 84 | } 85 | /* line 68, ../../src/sass/messenger.sass */ 86 | ul.messenger.messenger-fixed.messenger-on-top.messenger-on-left, ul.messenger.messenger-fixed.messenger-on-bottom.messenger-on-left { 87 | left: 20px; 88 | margin-left: 0px; 89 | } 90 | /* line 72, ../../src/sass/messenger.sass */ 91 | ul.messenger.messenger-fixed.messenger-on-right, ul.messenger.messenger-fixed.messenger-on-left { 92 | width: 350px; 93 | } 94 | /* line 75, ../../src/sass/messenger.sass */ 95 | ul.messenger.messenger-fixed.messenger-on-right .messenger-actions, ul.messenger.messenger-fixed.messenger-on-left .messenger-actions { 96 | float: left; 97 | } 98 | /* line 78, ../../src/sass/messenger.sass */ 99 | ul.messenger .messenger-spinner { 100 | display: none; 101 | } 102 | -------------------------------------------------------------------------------- /github2fedmsg/__init__.py: -------------------------------------------------------------------------------- 1 | # This file is a part of github2fedmsg, a pubsubhubbub to zeromq bridge. 2 | # Copyright (C) 2014, Red Hat, Inc. 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Affero General Public License as 6 | # published by the Free Software Foundation, either version 3 of the 7 | # License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Affero General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Affero General Public License 15 | # along with this program. If not, see . 16 | 17 | from pyramid.config import Configurator 18 | from pyramid.session import UnencryptedCookieSessionFactoryConfig 19 | from pyramid.authentication import AuthTktAuthenticationPolicy 20 | #from pyramid.authorization import ACLAuthorizationPolicy 21 | from pyramid.security import authenticated_userid 22 | from sqlalchemy import engine_from_config 23 | 24 | import pyramid_mako 25 | 26 | import ConfigParser 27 | import os 28 | 29 | import github2fedmsg.models 30 | import github2fedmsg.traversal 31 | import github2fedmsg.custom_openid 32 | 33 | 34 | def get_user(request): 35 | """ A utility property hanging on 'request' """ 36 | username = authenticated_userid(request) 37 | query = github2fedmsg.models.User.query.filter_by(username=username) 38 | if username and query.count() > 0: 39 | return query.one() 40 | 41 | 42 | def main(global_config, **settings): 43 | """ This function returns a Pyramid WSGI application. 44 | """ 45 | 46 | # Load secret stuff from secret.ini. 47 | try: 48 | default_path = os.path.abspath("secret.ini") 49 | secret_path = settings.get('secret_config_path', default_path) 50 | # TODO: There is a better way to log this message than print. 51 | print "Reading secrets from %r" % secret_path 52 | parser = ConfigParser.ConfigParser() 53 | parser.read(secret_path) 54 | secret_config = dict(parser.items("github2fedmsg")) 55 | settings.update(secret_config) 56 | except Exception as e: 57 | # TODO: There is a better way to log this message than print. 58 | print 'Failed to load secret.ini. Reason: %r' % str(e) 59 | 60 | crappy_session_factory = UnencryptedCookieSessionFactoryConfig(settings['session.secret']) 61 | authn_policy = AuthTktAuthenticationPolicy(secret=settings['authnsecret'], hashalg='sha256') 62 | 63 | engine = engine_from_config(settings, 'sqlalchemy.') 64 | github2fedmsg.models.DBSession.configure(bind=engine) 65 | 66 | config = Configurator( 67 | settings=settings, 68 | root_factory=github2fedmsg.traversal.make_root, 69 | session_factory=crappy_session_factory, 70 | authentication_policy=authn_policy, 71 | #authorization_policy=authz_policy, 72 | ) 73 | 74 | # Make it so we can do "request.user" in templates. 75 | if hasattr(config, "add_request_method"): 76 | # Pyramid >= 1.4 77 | config.add_request_method(get_user, 'user', reify=True) 78 | else: 79 | config.set_request_property(get_user, 'user', reify=True) 80 | 81 | config.include('pyramid_mako') 82 | config.add_mako_renderer('.mak') 83 | 84 | config.include('velruse.providers.github') 85 | config.add_github_login_from_settings() 86 | 87 | config.include('velruse.providers.openid') 88 | github2fedmsg.custom_openid.add_openid_login( 89 | config, 90 | realm=settings.get('velruse.openid.realm'), 91 | identity_provider=settings.get('velruse.openid.identifier'), 92 | ) 93 | 94 | config.add_static_view('static', 'static', cache_max_age=3600) 95 | config.add_route('home', '/') 96 | config.add_route('logout', '/logout') 97 | config.add_route('webhook', '/webhook') 98 | config.add_route('forget_github_token', '/forget_github_token') 99 | config.scan() 100 | return config.make_wsgi_app() 101 | -------------------------------------------------------------------------------- /github2fedmsg/static/messenger-1.4.1/css/messenger-theme-block.css: -------------------------------------------------------------------------------- 1 | /* line 4, ../../src/sass/messenger-theme-block.sass */ 2 | ul.messenger.messenger-theme-block.messenger-fixed { 3 | width: 100%; 4 | top: 0; 5 | left: 0; 6 | margin-left: 0px; 7 | } 8 | /* line 10, ../../src/sass/messenger-theme-block.sass */ 9 | ul.messenger.messenger-theme-block.messenger-fixed.messenger-on-bottom { 10 | top: auto; 11 | bottom: 0; 12 | } 13 | /* line 14, ../../src/sass/messenger-theme-block.sass */ 14 | ul.messenger.messenger-theme-block.messenger-fixed.messenger-on-top { 15 | top: 0px; 16 | bottom: auto; 17 | } 18 | /* line 18, ../../src/sass/messenger-theme-block.sass */ 19 | ul.messenger.messenger-theme-block.messenger-fixed.messenger-on-top, ul.messenger.messenger-theme-block.messenger-fixed.messenger-on-bottom { 20 | left: 0px; 21 | right: 0px; 22 | } 23 | /* line 22, ../../src/sass/messenger-theme-block.sass */ 24 | ul.messenger.messenger-theme-block.messenger-fixed.messenger-on-top.messenger-on-right, ul.messenger.messenger-theme-block.messenger-fixed.messenger-on-top.messenger-on-left, ul.messenger.messenger-theme-block.messenger-fixed.messenger-on-bottom.messenger-on-right, ul.messenger.messenger-theme-block.messenger-fixed.messenger-on-bottom.messenger-on-left { 25 | width: 350px; 26 | } 27 | /* line 25, ../../src/sass/messenger-theme-block.sass */ 28 | ul.messenger.messenger-theme-block.messenger-fixed.messenger-on-top.messenger-on-left, ul.messenger.messenger-theme-block.messenger-fixed.messenger-on-bottom.messenger-on-left { 29 | right: auto; 30 | } 31 | /* line 28, ../../src/sass/messenger-theme-block.sass */ 32 | ul.messenger.messenger-theme-block.messenger-fixed.messenger-on-top.messenger-on-right, ul.messenger.messenger-theme-block.messenger-fixed.messenger-on-bottom.messenger-on-right { 33 | left: auto; 34 | } 35 | /* line 31, ../../src/sass/messenger-theme-block.sass */ 36 | ul.messenger.messenger-theme-block.messenger-fixed .messenger-message-slot { 37 | max-width: none; 38 | } 39 | /* line 34, ../../src/sass/messenger-theme-block.sass */ 40 | ul.messenger.messenger-theme-block.messenger-fixed .messenger-message { 41 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); 42 | border-width: 1px; 43 | border-style: solid; 44 | text-align: center; 45 | padding: 1em; 46 | } 47 | /* line 41, ../../src/sass/messenger-theme-block.sass */ 48 | ul.messenger.messenger-theme-block.messenger-fixed .messenger-message.alert-warning { 49 | color: #c09853; 50 | background-color: #fcf8e3; 51 | border-color: #fbeed5; 52 | } 53 | /* line 46, ../../src/sass/messenger-theme-block.sass */ 54 | ul.messenger.messenger-theme-block.messenger-fixed .messenger-message.alert-error, ul.messenger.messenger-theme-block.messenger-fixed .messenger-message.alert-danger { 55 | color: #b94a48; 56 | background-color: #f2dede; 57 | border-color: #eed3d7; 58 | } 59 | /* line 51, ../../src/sass/messenger-theme-block.sass */ 60 | ul.messenger.messenger-theme-block.messenger-fixed .messenger-message.alert-success { 61 | color: #468847; 62 | background-color: #dff0d8; 63 | border-color: #d6e9c6; 64 | } 65 | /* line 56, ../../src/sass/messenger-theme-block.sass */ 66 | ul.messenger.messenger-theme-block.messenger-fixed .messenger-message.alert-info { 67 | color: #3a87ad; 68 | background-color: #d9edf7; 69 | border-color: #bce8f1; 70 | } 71 | /* line 61, ../../src/sass/messenger-theme-block.sass */ 72 | ul.messenger.messenger-theme-block.messenger-fixed .messenger-message .messenger-close { 73 | filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=50); 74 | opacity: 0.5; 75 | padding: 0; 76 | cursor: pointer; 77 | color: inherit; 78 | background: transparent; 79 | border: 0; 80 | -webkit-appearance: none; 81 | float: right; 82 | position: relative; 83 | top: -13px; 84 | left: 11px; 85 | font-size: 22px; 86 | } 87 | /* line 75, ../../src/sass/messenger-theme-block.sass */ 88 | ul.messenger.messenger-theme-block.messenger-fixed .messenger-message .messenger-close:hover { 89 | filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=80); 90 | opacity: 0.8; 91 | } 92 | /* line 78, ../../src/sass/messenger-theme-block.sass */ 93 | ul.messenger.messenger-theme-block.messenger-fixed .messenger-message .messenger-close:active { 94 | filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=100); 95 | opacity: 1; 96 | } 97 | -------------------------------------------------------------------------------- /github2fedmsg/views/auth.py: -------------------------------------------------------------------------------- 1 | # This file is a part of github2fedmsg, a pubsubhubbub to zeromq bridge. 2 | # Copyright (C) 2014, Red Hat, Inc. 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Affero General Public License as 6 | # published by the Free Software Foundation, either version 3 of the 7 | # License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Affero General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Affero General Public License 15 | # along with this program. If not, see . 16 | 17 | from pyramid.view import view_config 18 | from pyramid.httpexceptions import HTTPFound, HTTPForbidden 19 | from pyramid.security import ( 20 | authenticated_userid, 21 | remember, 22 | forget, 23 | ) 24 | 25 | import github2fedmsg.models as m 26 | 27 | 28 | @view_config(context='velruse.AuthenticationComplete') 29 | def login_complete_view(request): 30 | """ This handles *both* login with FAS and login with github.. """ 31 | 32 | # Github gives us: 33 | #{'accounts': [{'domain': 'github.com', 34 | # 'userid': 331338, 35 | # 'username': u'ralphbean'}], 36 | # 'displayName': u'Ralph Bean', 37 | # 'emails': [{'value': u'rbean@redhat.com'}], 38 | # 'preferredUsername': u'ralphbean'} 39 | 40 | # FAS gives us: 41 | #{'accounts': [{'domain': 'openid.net', 42 | # 'username': 'http://ralph.id.fedoraproject.org/'}], 43 | # 'displayName': u'Ralph Bean', 44 | # 'emails': [u'rbean@redhat.com'], 45 | # 'name': {'formatted': u'Ralph Bean'}, 46 | # 'preferredUsername': u'ralph'} 47 | 48 | ctx = request.context 49 | accounts = ctx.profile['accounts'] 50 | home = request.route_url('home') 51 | 52 | if accounts and accounts[0]['domain'] == 'github.com': 53 | if not request.user: 54 | # Bad scene. Someone is trying to link with github.com while not 55 | # yet being signed in through FAS. We will just deny this. 56 | return HTTPForbidden() 57 | token = ctx.credentials['oauthAccessToken'] 58 | request.user.github_username = ctx.profile['preferredUsername'] 59 | request.user.oauth_access_token = token 60 | request.session['token'] = token 61 | return HTTPFound(location=home + request.user.username) 62 | 63 | try: 64 | username = ctx.profile['preferredUsername'] 65 | except KeyError: 66 | username = accounts[0]["username"].split("/")[-2] 67 | full_name = ctx.profile.get('displayName', username) 68 | emails = ctx.profile.get('emails', []) 69 | 70 | if emails: 71 | if isinstance(emails[0], dict): 72 | emails = [item['value'] for item in emails if item['value']] 73 | 74 | emails = ','.join(emails) 75 | 76 | query = m.User.query.filter_by(username=username) 77 | if query.count() == 0: 78 | m.DBSession.add(m.User( 79 | username=username, 80 | full_name=full_name, 81 | emails=emails, 82 | )) 83 | 84 | user = query.one() 85 | headers = remember(request, username) 86 | request.session['token'] = user.oauth_access_token 87 | return HTTPFound(location=home + user.username, headers=headers) 88 | 89 | 90 | @view_config(context='velruse.AuthenticationDenied', renderer='json') 91 | def login_denied_view(request): 92 | # TODO -- fancy flash and redirect 93 | return {'result': 'denied'} 94 | 95 | 96 | @view_config(route_name='logout') 97 | def logout(request): 98 | headers = forget(request) 99 | home = request.route_url('home') 100 | return HTTPFound(location=home, headers=headers) 101 | 102 | 103 | @view_config(route_name='forget_github_token') 104 | def forget_github_token(request): 105 | if 'token' in request.session: 106 | request.session['token'] 107 | 108 | username = request.user.username 109 | home = request.route_url('home') 110 | 111 | import transaction 112 | #request.user.github_username = None 113 | request.user.oauth_access_token = None 114 | transaction.commit() 115 | 116 | return HTTPFound(location=home + username) 117 | -------------------------------------------------------------------------------- /github2fedmsg/static/messenger-1.4.1/css/messenger-theme-ice.css: -------------------------------------------------------------------------------- 1 | @import url("//fonts.googleapis.com/css?family=Raleway:400"); 2 | /* line 12, ../../src/sass/messenger-theme-ice.sass */ 3 | ul.messenger-theme-ice { 4 | -moz-user-select: none; 5 | -webkit-user-select: none; 6 | -o-user-select: none; 7 | user-select: none; 8 | font-family: "Raleway", sans-serif; 9 | } 10 | /* line 16, ../../src/sass/messenger-theme-ice.sass */ 11 | ul.messenger-theme-ice .messenger-message { 12 | -webkit-border-radius: 5px; 13 | -moz-border-radius: 5px; 14 | -ms-border-radius: 5px; 15 | -o-border-radius: 5px; 16 | border-radius: 5px; 17 | -webkit-box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.14), 0 4px #aaaaaa, 0 5px rgba(0, 0, 0, 0.05); 18 | -moz-box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.14), 0 4px #aaaaaa, 0 5px rgba(0, 0, 0, 0.05); 19 | box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.14), 0 4px #aaaaaa, 0 5px rgba(0, 0, 0, 0.05); 20 | border: 0px; 21 | background-color: #f6f6f6; 22 | position: relative; 23 | margin-bottom: 1.5em; 24 | font-size: 13px; 25 | color: #666666; 26 | font-weight: 500; 27 | padding: 12px 22px; 28 | } 29 | /* line 28, ../../src/sass/messenger-theme-ice.sass */ 30 | ul.messenger-theme-ice .messenger-message .messenger-close { 31 | position: absolute; 32 | top: 0px; 33 | right: 0px; 34 | color: #888888; 35 | opacity: 1; 36 | font-weight: bold; 37 | display: block; 38 | font-size: 20px; 39 | line-height: 20px; 40 | padding: 8px 10px 7px 7px; 41 | cursor: pointer; 42 | background: transparent; 43 | border: 0; 44 | -webkit-appearance: none; 45 | } 46 | /* line 44, ../../src/sass/messenger-theme-ice.sass */ 47 | ul.messenger-theme-ice .messenger-message .messenger-close:hover { 48 | color: #444444; 49 | } 50 | /* line 47, ../../src/sass/messenger-theme-ice.sass */ 51 | ul.messenger-theme-ice .messenger-message .messenger-close:active { 52 | color: #222222; 53 | } 54 | /* line 50, ../../src/sass/messenger-theme-ice.sass */ 55 | ul.messenger-theme-ice .messenger-message .messenger-actions { 56 | float: none; 57 | margin-top: 10px; 58 | } 59 | /* line 54, ../../src/sass/messenger-theme-ice.sass */ 60 | ul.messenger-theme-ice .messenger-message .messenger-actions a { 61 | -webkit-box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.1), inset 0px 1px rgba(255, 255, 255, 0.05); 62 | -moz-box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.1), inset 0px 1px rgba(255, 255, 255, 0.05); 63 | box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.1), inset 0px 1px rgba(255, 255, 255, 0.05); 64 | -webkit-border-radius: 4px; 65 | -moz-border-radius: 4px; 66 | -ms-border-radius: 4px; 67 | -o-border-radius: 4px; 68 | border-radius: 4px; 69 | position: relative; 70 | text-decoration: none; 71 | display: inline-block; 72 | padding: 10px; 73 | color: #888888; 74 | margin-right: 10px; 75 | padding: 3px 10px 5px; 76 | text-transform: capitalize; 77 | } 78 | /* line 66, ../../src/sass/messenger-theme-ice.sass */ 79 | ul.messenger-theme-ice .messenger-message .messenger-actions a:hover, ul.messenger-theme-ice .messenger-message .messenger-actions a:active { 80 | -webkit-box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.1), inset 0px 1px rgba(255, 255, 255, 0.15), 0 2px #aaaaaa; 81 | -moz-box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.1), inset 0px 1px rgba(255, 255, 255, 0.15), 0 2px #aaaaaa; 82 | box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.1), inset 0px 1px rgba(255, 255, 255, 0.15), 0 2px #aaaaaa; 83 | color: #444444; 84 | } 85 | /* line 70, ../../src/sass/messenger-theme-ice.sass */ 86 | ul.messenger-theme-ice .messenger-message .messenger-actions a:active { 87 | -webkit-box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.1), inset 0px 1px rgba(255, 255, 255, 0.15), 0 1px #aaaaaa; 88 | -moz-box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.1), inset 0px 1px rgba(255, 255, 255, 0.15), 0 1px #aaaaaa; 89 | box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.1), inset 0px 1px rgba(255, 255, 255, 0.15), 0 1px #aaaaaa; 90 | top: 1px; 91 | } 92 | /* line 74, ../../src/sass/messenger-theme-ice.sass */ 93 | ul.messenger-theme-ice .messenger-message .messenger-actions .messenger-phrase { 94 | display: none; 95 | } 96 | /* line 77, ../../src/sass/messenger-theme-ice.sass */ 97 | ul.messenger-theme-ice .messenger-message .messenger-message-inner:before { 98 | display: block; 99 | z-index: 20; 100 | font-weight: bold; 101 | margin-bottom: 2px; 102 | } 103 | /* line 84, ../../src/sass/messenger-theme-ice.sass */ 104 | ul.messenger-theme-ice .messenger-message.alert-success .messenger-message-inner:before { 105 | content: "Success"; 106 | } 107 | /* line 88, ../../src/sass/messenger-theme-ice.sass */ 108 | ul.messenger-theme-ice .messenger-message.alert-error .messenger-message-inner:before { 109 | content: "Error"; 110 | } 111 | /* line 92, ../../src/sass/messenger-theme-ice.sass */ 112 | ul.messenger-theme-ice .messenger-message.alert-info .messenger-message-inner:before { 113 | content: "Information"; 114 | } 115 | -------------------------------------------------------------------------------- /tools/fake-github-post.py: -------------------------------------------------------------------------------- 1 | # This file is a part of github2fedmsg, a pubsubhubbub to zeromq bridge. 2 | # Copyright (C) 2014, Red Hat, Inc. 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Affero General Public License as 6 | # published by the Free Software Foundation, either version 3 of the 7 | # License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Affero General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Affero General Public License 15 | # along with this program. If not, see . 16 | 17 | import json 18 | import hmac 19 | import hashlib 20 | import urllib 21 | import requests 22 | 23 | payload = { 24 | "after": "3ef21292dd6448d4731f49864844d458ad3801a5", 25 | "before": "541d26405960ef934a398d7ee413bf7855cbe220", 26 | "commits": [ 27 | { "added": [], 28 | "author": { "email": "rbean@redhat.com", 29 | "name": "Ralph Bean", 30 | "username": "ralphbean" }, 31 | "committer": { "email": "rbean@redhat.com", 32 | "name": "Ralph Bean", 33 | "username": "ralphbean" }, 34 | "distinct": True, 35 | "id": "81eacd09c4d6526bea1d14e3fbc17ef0fd1d9192", 36 | "message": "Authentication policy.", 37 | "modified": [ "statatat/__init__.py", 38 | "statatat/views.py" ], 39 | "removed": [], 40 | "timestamp": "2012-08-25T06:30:21-07:00", 41 | "url": "https://github.com/ralphbean/statatat/commit/81eacd09c4d6526bea1d14e3fbc17ef0fd1d9192" }, 42 | { "added": [], 43 | "author": { "email": "rbean@redhat.com", 44 | "name": "Ralph Bean", 45 | "username": "ralphbean" }, 46 | "committer": { "email": "rbean@redhat.com", 47 | "name": "Ralph Bean", 48 | "username": "ralphbean" }, 49 | "distinct": True, 50 | "id": "ffedad58abec3cbf6546daedfb4249db5b994203", 51 | "message": "Use BeforeRender like a pro.", 52 | "modified": [ "statatat/views.py" ], 53 | "removed": [], 54 | "timestamp": "2012-08-25T06:36:24-07:00", 55 | "url": "https://github.com/ralphbean/statatat/commit/ffedad58abec3cbf6546daedfb4249db5b994203" }, 56 | { "added": [], 57 | "author": { "email": "rbean@redhat.com", 58 | "name": "Ralph Bean", 59 | "username": "ralphbean" }, 60 | "committer": { "email": "rbean@redhat.com", 61 | "name": "Ralph Bean", 62 | "username": "ralphbean" }, 63 | "distinct": True, 64 | "id": "3ef21292dd6448d4731f49864844d458ad3801a5", 65 | "message": "Keep \"identity\" in the globals like TG2.", 66 | "modified": [ "statatat/views.py" ], 67 | "removed": [], 68 | "timestamp": "2012-08-25T06:37:21-07:00", 69 | "url": "https://github.com/ralphbean/statatat/commit/3ef21292dd6448d4731f49864844d458ad3801a5" 70 | } 71 | ], 72 | "compare": "https://github.com/ralphbean/statatat/compare/541d26405960...3ef21292dd64", 73 | "created": False, 74 | "deleted": False, 75 | "forced": False, 76 | "head_commit": { "added": [], 77 | "author": { "email": "rbean@redhat.com", 78 | "name": "Ralph Bean", 79 | "username": "ralphbean" }, 80 | "committer": { "email": "rbean@redhat.com", 81 | "name": "Ralph Bean", 82 | "username": "ralphbean" }, 83 | "distinct": True, 84 | "id": "3ef21292dd6448d4731f49864844d458ad3801a5", 85 | "message": "Keep \"identity\" in the globals like TG2.", 86 | "modified": [ "statatat/views.py" ], 87 | "removed": [], 88 | "timestamp": "2012-08-25T06:37:21-07:00", 89 | "url": "https://github.com/ralphbean/statatat/commit/3ef21292dd6448d4731f49864844d458ad3801a5" }, 90 | "pusher": { "name": "none" }, 91 | "ref": "refs/heads/master", 92 | "repository": { "created_at": "2012-07-05T21:22:12-07:00", 93 | "description": "Embeddable realtime dev widgets.", 94 | "fork": False, 95 | "forks": 0, 96 | "has_downloads": True, 97 | "has_issues": True, 98 | "has_wiki": False, 99 | "language": "Python", 100 | "master_branch": "develop", 101 | "name": "statatat", 102 | "open_issues": 3, 103 | "owner": { "email": "ralph.bean@gmail.com", 104 | "name": "ralphbean" }, 105 | "private": False, 106 | "pushed_at": "2012-09-16T20:03:33-07:00", 107 | "size": 272, 108 | "stargazers": 3, 109 | "url": "https://github.com/ralphbean/statatat", 110 | "watchers": 3 } } 111 | 112 | blob = {'payload': json.dumps(payload)} 113 | 114 | github_secret = 'changeme!' 115 | body = urllib.urlencode(blob) 116 | hex = hmac.new(github_secret, body, hashlib.sha1).hexdigest() 117 | headers = { 118 | 'X-Hub-Signature': 'sha1=%s' % hex, 119 | 'X-GitHub-Event': 'this is going to be something...', 120 | } 121 | 122 | response = requests.post( 123 | "http://localhost:6543/webhook", 124 | data=blob, 125 | headers=headers, 126 | ) 127 | 128 | print response.status_code 129 | print response.text 130 | -------------------------------------------------------------------------------- /github2fedmsg/static/messenger-1.4.1/css/messenger-spinner.css: -------------------------------------------------------------------------------- 1 | @-webkit-keyframes ui-spinner-rotate-right { 2 | /* line 64, ../../src/sass/messenger-spinner.scss */ 3 | 0% { 4 | -webkit-transform: rotate(0deg); 5 | } 6 | 7 | /* line 65, ../../src/sass/messenger-spinner.scss */ 8 | 25% { 9 | -webkit-transform: rotate(180deg); 10 | } 11 | 12 | /* line 66, ../../src/sass/messenger-spinner.scss */ 13 | 50% { 14 | -webkit-transform: rotate(180deg); 15 | } 16 | 17 | /* line 67, ../../src/sass/messenger-spinner.scss */ 18 | 75% { 19 | -webkit-transform: rotate(360deg); 20 | } 21 | 22 | /* line 68, ../../src/sass/messenger-spinner.scss */ 23 | 100% { 24 | -webkit-transform: rotate(360deg); 25 | } 26 | } 27 | 28 | @-webkit-keyframes ui-spinner-rotate-left { 29 | /* line 72, ../../src/sass/messenger-spinner.scss */ 30 | 0% { 31 | -webkit-transform: rotate(0deg); 32 | } 33 | 34 | /* line 73, ../../src/sass/messenger-spinner.scss */ 35 | 25% { 36 | -webkit-transform: rotate(0deg); 37 | } 38 | 39 | /* line 74, ../../src/sass/messenger-spinner.scss */ 40 | 50% { 41 | -webkit-transform: rotate(180deg); 42 | } 43 | 44 | /* line 75, ../../src/sass/messenger-spinner.scss */ 45 | 75% { 46 | -webkit-transform: rotate(180deg); 47 | } 48 | 49 | /* line 76, ../../src/sass/messenger-spinner.scss */ 50 | 100% { 51 | -webkit-transform: rotate(360deg); 52 | } 53 | } 54 | 55 | @-moz-keyframes ui-spinner-rotate-right { 56 | /* line 80, ../../src/sass/messenger-spinner.scss */ 57 | 0% { 58 | -moz-transform: rotate(0deg); 59 | } 60 | 61 | /* line 81, ../../src/sass/messenger-spinner.scss */ 62 | 25% { 63 | -moz-transform: rotate(180deg); 64 | } 65 | 66 | /* line 82, ../../src/sass/messenger-spinner.scss */ 67 | 50% { 68 | -moz-transform: rotate(180deg); 69 | } 70 | 71 | /* line 83, ../../src/sass/messenger-spinner.scss */ 72 | 75% { 73 | -moz-transform: rotate(360deg); 74 | } 75 | 76 | /* line 84, ../../src/sass/messenger-spinner.scss */ 77 | 100% { 78 | -moz-transform: rotate(360deg); 79 | } 80 | } 81 | 82 | @-moz-keyframes ui-spinner-rotate-left { 83 | /* line 88, ../../src/sass/messenger-spinner.scss */ 84 | 0% { 85 | -moz-transform: rotate(0deg); 86 | } 87 | 88 | /* line 89, ../../src/sass/messenger-spinner.scss */ 89 | 25% { 90 | -moz-transform: rotate(0deg); 91 | } 92 | 93 | /* line 90, ../../src/sass/messenger-spinner.scss */ 94 | 50% { 95 | -moz-transform: rotate(180deg); 96 | } 97 | 98 | /* line 91, ../../src/sass/messenger-spinner.scss */ 99 | 75% { 100 | -moz-transform: rotate(180deg); 101 | } 102 | 103 | /* line 92, ../../src/sass/messenger-spinner.scss */ 104 | 100% { 105 | -moz-transform: rotate(360deg); 106 | } 107 | } 108 | 109 | @keyframes ui-spinner-rotate-right { 110 | /* line 96, ../../src/sass/messenger-spinner.scss */ 111 | 0% { 112 | transform: rotate(0deg); 113 | } 114 | 115 | /* line 97, ../../src/sass/messenger-spinner.scss */ 116 | 25% { 117 | transform: rotate(180deg); 118 | } 119 | 120 | /* line 98, ../../src/sass/messenger-spinner.scss */ 121 | 50% { 122 | transform: rotate(180deg); 123 | } 124 | 125 | /* line 99, ../../src/sass/messenger-spinner.scss */ 126 | 75% { 127 | transform: rotate(360deg); 128 | } 129 | 130 | /* line 100, ../../src/sass/messenger-spinner.scss */ 131 | 100% { 132 | transform: rotate(360deg); 133 | } 134 | } 135 | 136 | @keyframes ui-spinner-rotate-left { 137 | /* line 104, ../../src/sass/messenger-spinner.scss */ 138 | 0% { 139 | transform: rotate(0deg); 140 | } 141 | 142 | /* line 105, ../../src/sass/messenger-spinner.scss */ 143 | 25% { 144 | transform: rotate(0deg); 145 | } 146 | 147 | /* line 106, ../../src/sass/messenger-spinner.scss */ 148 | 50% { 149 | transform: rotate(180deg); 150 | } 151 | 152 | /* line 107, ../../src/sass/messenger-spinner.scss */ 153 | 75% { 154 | transform: rotate(180deg); 155 | } 156 | 157 | /* line 108, ../../src/sass/messenger-spinner.scss */ 158 | 100% { 159 | transform: rotate(360deg); 160 | } 161 | } 162 | 163 | /* line 116, ../../src/sass/messenger-spinner.scss */ 164 | .messenger-spinner { 165 | position: relative; 166 | border-radius: 100%; 167 | } 168 | /* line 120, ../../src/sass/messenger-spinner.scss */ 169 | ul.messenger.messenger-spinner-active .messenger-spinner .messenger-spinner { 170 | display: block; 171 | } 172 | /* line 124, ../../src/sass/messenger-spinner.scss */ 173 | .messenger-spinner .messenger-spinner-side { 174 | width: 50%; 175 | height: 100%; 176 | overflow: hidden; 177 | position: absolute; 178 | } 179 | /* line 130, ../../src/sass/messenger-spinner.scss */ 180 | .messenger-spinner .messenger-spinner-side .messenger-spinner-fill { 181 | border-radius: 999px; 182 | position: absolute; 183 | width: 100%; 184 | height: 100%; 185 | -webkit-animation-iteration-count: infinite; 186 | -moz-animation-iteration-count: infinite; 187 | -ms-animation-iteration-count: infinite; 188 | -o-animation-iteration-count: infinite; 189 | animation-iteration-count: infinite; 190 | -webkit-animation-timing-function: linear; 191 | -moz-animation-timing-function: linear; 192 | -ms-animation-timing-function: linear; 193 | -o-animation-timing-function: linear; 194 | animation-timing-function: linear; 195 | } 196 | /* line 140, ../../src/sass/messenger-spinner.scss */ 197 | .messenger-spinner .messenger-spinner-side-left { 198 | left: 0; 199 | } 200 | /* line 143, ../../src/sass/messenger-spinner.scss */ 201 | .messenger-spinner .messenger-spinner-side-left .messenger-spinner-fill { 202 | left: 100%; 203 | border-top-left-radius: 0; 204 | border-bottom-left-radius: 0; 205 | -webkit-animation-name: ui-spinner-rotate-left; 206 | -moz-animation-name: ui-spinner-rotate-left; 207 | -ms-animation-name: ui-spinner-rotate-left; 208 | -o-animation-name: ui-spinner-rotate-left; 209 | animation-name: ui-spinner-rotate-left; 210 | -webkit-transform-origin: 0 50%; 211 | -moz-transform-origin: 0 50%; 212 | -ms-transform-origin: 0 50%; 213 | -o-transform-origin: 0 50%; 214 | transform-origin: 0 50%; 215 | } 216 | /* line 152, ../../src/sass/messenger-spinner.scss */ 217 | .messenger-spinner .messenger-spinner-side-right { 218 | left: 50%; 219 | } 220 | /* line 155, ../../src/sass/messenger-spinner.scss */ 221 | .messenger-spinner .messenger-spinner-side-right .messenger-spinner-fill { 222 | left: -100%; 223 | border-top-right-radius: 0; 224 | border-bottom-right-radius: 0; 225 | -webkit-animation-name: ui-spinner-rotate-right; 226 | -moz-animation-name: ui-spinner-rotate-right; 227 | -ms-animation-name: ui-spinner-rotate-right; 228 | -o-animation-name: ui-spinner-rotate-right; 229 | animation-name: ui-spinner-rotate-right; 230 | -webkit-transform-origin: 100% 50%; 231 | -moz-transform-origin: 100% 50%; 232 | -ms-transform-origin: 100% 50%; 233 | -o-transform-origin: 100% 50%; 234 | transform-origin: 100% 50%; 235 | } 236 | -------------------------------------------------------------------------------- /github2fedmsg/models/__init__.py: -------------------------------------------------------------------------------- 1 | # This file is a part of github2fedmsg, a pubsubhubbub to zeromq bridge. 2 | # Copyright (C) 2014, Red Hat, Inc. 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Affero General Public License as 6 | # published by the Free Software Foundation, either version 3 of the 7 | # License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Affero General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Affero General Public License 15 | # along with this program. If not, see . 16 | 17 | from sqlalchemy import ( 18 | Table, 19 | Column, 20 | Integer, 21 | DateTime, 22 | Boolean, 23 | Unicode, 24 | ForeignKey, 25 | and_, 26 | ) 27 | 28 | from sqlalchemy.ext.declarative import declarative_base 29 | 30 | from sqlalchemy.orm import ( 31 | scoped_session, 32 | sessionmaker, 33 | relation, 34 | backref, 35 | ) 36 | 37 | import pyramid.threadlocal 38 | import github2fedmsg.traversal 39 | import datetime 40 | from hashlib import sha256 41 | from .jsonifiable import JSONifiable 42 | 43 | from zope.sqlalchemy import ZopeTransactionExtension 44 | 45 | DBSession = scoped_session(sessionmaker(extension=ZopeTransactionExtension())) 46 | Base = declarative_base(cls=JSONifiable) 47 | Base.query = DBSession.query_property() 48 | 49 | import github2fedmsg.githubutils as gh 50 | 51 | org_to_user_mapping = Table( 52 | 'org_to_user_mapping', Base.metadata, 53 | Column('org_id', Unicode, ForeignKey('users.github_username'), primary_key=True), 54 | Column('usr_id', Unicode, ForeignKey('users.github_username'), primary_key=True), 55 | ) 56 | 57 | import logging 58 | log = logging.getLogger("github2fedmsg.models") 59 | 60 | 61 | class User(Base): 62 | __tablename__ = 'users' 63 | username = Column(Unicode, primary_key=True) 64 | github_username = Column(Unicode, unique=True) 65 | emails = Column(Unicode, nullable=False) 66 | full_name = Column(Unicode, nullable=False) 67 | oauth_access_token = Column(Unicode) 68 | created_on = Column(DateTime, default=datetime.datetime.now) 69 | repos = relation('Repo', backref=('user')) 70 | 71 | @property 72 | def openid_url(self): 73 | return "http://%s.id.fedoraproject.org/" % self.username 74 | 75 | @property 76 | def all_repos(self): 77 | return sum( 78 | [self.repos] + [org.repos for org in self.organizations], 79 | []) 80 | 81 | def sync_repos(self, gh_auth): 82 | """ Ask github about what repos I have and cache that. """ 83 | gh_repos = gh.get_repos(self.github_username, gh_auth) 84 | 85 | # TODO -- fix this. this is inefficient 86 | for repo in gh_repos: 87 | 88 | if Repo.query.filter(and_( 89 | Repo.name==repo['name'], 90 | Repo.username==self.github_username 91 | )).count() < 1: 92 | github2fedmsg.models.DBSession.add(github2fedmsg.models.Repo( 93 | user=self, 94 | name=unicode(repo['name']), 95 | description=unicode(repo['description']), 96 | language=unicode(repo['language']), 97 | )) 98 | 99 | # Refresh my list of organizations. 100 | if not self.users: 101 | # Then I am a real User. 102 | gh_orgs = gh.get_orgs(self.github_username, gh_auth) 103 | for o in gh_orgs: 104 | query = User.query.filter(User.github_username==o['login']) 105 | if query.count() < 1: 106 | log.debug("Adding new org %r" % o['login']) 107 | organization = User( 108 | username="github_org_" + o['login'], 109 | github_username=o['login'], 110 | full_name='', 111 | emails='') 112 | DBSession.add(organization) 113 | else: 114 | log.debug("Found prexisting org %r" % o['login']) 115 | organization = query.one() 116 | 117 | if self not in organization.users: 118 | log.debug("Adding %r to %r" % ( 119 | self.github_username, organization.github_username)) 120 | organization.users.append(self) 121 | DBSession.flush() 122 | else: 123 | log.debug("Already in %r" % organization.github_username) 124 | else: 125 | # I am an organization. Do not recurse. 126 | pass 127 | 128 | # Follow up with all my orgs too (they are also "User"s) 129 | for organization in self.organizations: 130 | organization.sync_repos(gh_auth) 131 | 132 | @property 133 | def total_enabled_repos(self): 134 | return sum([1 for repo in self.repos if repo.enabled]) 135 | 136 | @property 137 | def percent_enabled_repos(self): 138 | # Avoid division by zero 139 | if not len(self.repos): 140 | return 0 141 | 142 | return 100.0 * self.total_enabled_repos / len(self.repos) 143 | 144 | @property 145 | def avatar(self): 146 | digest = sha256(self.openid_url).hexdigest() 147 | return "https://seccdn.libravatar.org/avatar/%s?d=retro" % digest 148 | 149 | @property 150 | def created_on_fmt(self): 151 | return str(self.created_on) 152 | 153 | def __getitem__(self, key): 154 | already_visited = getattr(self, '_visited', False) 155 | if not already_visited and key == self.github_username: 156 | self._visited = True 157 | return self 158 | 159 | for r in self.repos: 160 | if r.name == key: 161 | return r 162 | 163 | for o in self.organizations: 164 | try: 165 | return o[key] 166 | except KeyError: 167 | pass 168 | 169 | raise KeyError( 170 | "No such repo %r associated with %s" % (key, self.username)) 171 | 172 | def repo_by_name(self, repo_name): 173 | return self[repo_name] 174 | 175 | User.__mapper__.add_property('organizations', relation( 176 | User, 177 | primaryjoin=User.github_username == org_to_user_mapping.c.org_id, 178 | secondaryjoin=org_to_user_mapping.c.usr_id == User.github_username, 179 | secondary=org_to_user_mapping, 180 | doc="List of this users organizations", 181 | backref=backref('users', doc="List of this organizations users") 182 | )) 183 | 184 | 185 | class Repo(Base): 186 | __tablename__ = 'repos' 187 | id = Column(Integer, primary_key=True) 188 | name = Column(Unicode, nullable=False) 189 | description = Column(Unicode, nullable=False) 190 | language = Column(Unicode, nullable=False) 191 | username = Column(Unicode, ForeignKey('users.github_username')) 192 | 193 | enabled = Column(Boolean, default=False) 194 | -------------------------------------------------------------------------------- /CHANGELOG.rst: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | 0.3.6 5 | ----- 6 | 7 | - Convert github.watch messages to github.star messages. `8ead823b6 `_ 8 | - Merge pull request #16 from fedora-infra/feature/watch-to-star `288a5b85c `_ 9 | 10 | 0.3.5 11 | ----- 12 | 13 | - Change in default consumer key. `132d8cbeb `_ 14 | - Pull in messenger-1.4.1. `d46bd97bf `_ 15 | - Use messenger.js to show errors. `a2029df93 `_ 16 | - Typofix `c67f97e0e `_ 17 | - Include messenger LICENSE. `cb98f6c7b `_ 18 | - Merge pull request #15 from fedora-infra/feature/name-name `18fc7b0c4 `_ 19 | - Merge pull request #14 from fedora-infra/feature/error-messages `7d389e0ac `_ 20 | 21 | 0.3.4 22 | ----- 23 | 24 | - Careful with the repo owner `3c083026a `_ 25 | - Merge pull request #11 from fedora-infra/feature/payload-changes `9f2de8581 `_ 26 | - Update TODO.md `8a6c50bf4 `_ 27 | - Might as well be even more careful here. `1b64c77a5 `_ 28 | 29 | 0.3.3 30 | ----- 31 | 32 | - A slash makes all the difference. `e56562181 `_ 33 | - Preserve target_url `fee1c01ca `_ 34 | - Add a ```` tag. `a66d502b3 <https://github.com/fedora-infra/github2fedmsg/commit/a66d502b32acaa2c12b63de885d98cf220f0392e>`_ 35 | - Include repo owner in passive actions `ba67db9fa <https://github.com/fedora-infra/github2fedmsg/commit/ba67db9faefec61f449192c786f3670875e733ac>`_ 36 | - Merge pull request #8 from fedora-infra/feature/include-owners `2220c655d <https://github.com/fedora-infra/github2fedmsg/commit/2220c655d145b870b0b16f16f641a60aeb2174b4>`_ 37 | - Fix an 'unbound session' error. `4d6f4af9d <https://github.com/fedora-infra/github2fedmsg/commit/4d6f4af9df0b17cf7c8840884091dd0169aac49a>`_ 38 | - Merge pull request #9 from fedora-infra/feature/fix-unbound-session-error `163e7afc1 <https://github.com/fedora-infra/github2fedmsg/commit/163e7afc19dd8a3d49d32503d3b85f910547e5e2>`_ 39 | - Mark resources as traversed. `4347c0176 <https://github.com/fedora-infra/github2fedmsg/commit/4347c01761b256728ea88503fdf1dcec01d6f6c7>`_ 40 | - Merge pull request #10 from fedora-infra/feature/fix-for-puiterwijk `5457d673d <https://github.com/fedora-infra/github2fedmsg/commit/5457d673d58294a8e2479567d1458aa7435d4ca2>`_ 41 | - 0.3.2 `3c97c7acb <https://github.com/fedora-infra/github2fedmsg/commit/3c97c7acb1e0d16c08aa9857722f20f8853bb0ea>`_ 42 | 43 | 0.3.1 44 | ----- 45 | 46 | - Adapt this to handle webhook payloads as well as pubsubhubbub. `b821dbf99 <https://github.com/fedora-infra/github2fedmsg/commit/b821dbf99bda1e1ed3897db00336274c36f05c93>`_ 47 | 48 | 0.3.0 49 | ----- 50 | 51 | - Fix refresh link redirection for @pypingou. `f9d0aad9e <https://github.com/fedora-infra/github2fedmsg/commit/f9d0aad9e976618e7dff452d415a9af1d1aa3f6c>`_ 52 | - Remove some debugging. `5cd637945 <https://github.com/fedora-infra/github2fedmsg/commit/5cd637945c63e093428b974ef6ce06ec8004fbfa>`_ 53 | - Construct a nice url when new repos are added. `8b22a8931 <https://github.com/fedora-infra/github2fedmsg/commit/8b22a89318f368aebb17c002bead96056b83c6e0>`_ 54 | - Remove unused utilities. `a98642c10 <https://github.com/fedora-infra/github2fedmsg/commit/a98642c10564af330922a4a1cf1ae555d07f7c9e>`_ 55 | - Reduce oauth scope. `f093b633b <https://github.com/fedora-infra/github2fedmsg/commit/f093b633b7384719e2bbbc4ae37bae651da5838c>`_ 56 | - Modern requests works fine here. `9ceb3110b <https://github.com/fedora-infra/github2fedmsg/commit/9ceb3110b893f2e57d01a593883bf019d1754718>`_ 57 | - That reduced oauth scope doesn't actually work. `b9cc0892d <https://github.com/fedora-infra/github2fedmsg/commit/b9cc0892d0b6c2a161ca518f2846858613c44b78>`_ 58 | - Update consumer key. `1789b722f <https://github.com/fedora-infra/github2fedmsg/commit/1789b722f11a7416bc06ee88d4fa6f1dd160d268>`_ 59 | - Break toggling out into its own util function. `ad60e5c23 <https://github.com/fedora-infra/github2fedmsg/commit/ad60e5c231c74ee8aff6f70328952823948f0510>`_ 60 | - Port from pubsubhubbub to webhooks. `08cc079cd <https://github.com/fedora-infra/github2fedmsg/commit/08cc079cda5551136c245ac17459930220063b9d>`_ 61 | - These scopes work now. /cc @puiterwijk `fad7394e7 <https://github.com/fedora-infra/github2fedmsg/commit/fad7394e70583497cb3ca02676fb60ea7dc79429>`_ 62 | - Just reorganize some of these views.. `1641c5b82 <https://github.com/fedora-infra/github2fedmsg/commit/1641c5b827af6022286afc309370a565cb51b988>`_ 63 | 64 | 0.2.7 65 | ----- 66 | 67 | - Get relative urls right for serving behind a proxy. `6d60f5170 <https://github.com/fedora-infra/github2fedmsg/commit/6d60f5170c2e2a6d3d852412a2e1743fa1405b8c>`_ 68 | - Add new vars to development.ini `935292e2d <https://github.com/fedora-infra/github2fedmsg/commit/935292e2d3a3113d8646afa15c4bef2dcb369f5a>`_ 69 | 70 | 0.2.6 71 | ----- 72 | 73 | - Remove currently unused alembic stuff. `655844396 <https://github.com/fedora-infra/github2fedmsg/commit/6558443960bf4a2e8f656d0821729d5712a7d1e6>`_ 74 | 75 | 0.2.5 76 | ----- 77 | 78 | - Include templates and alembic stuff. `92147d6dc <https://github.com/fedora-infra/github2fedmsg/commit/92147d6dc4f057ceedc7e021f0b265d091ae3939>`_ 79 | 80 | 0.2.4 81 | ----- 82 | 83 | - Update to the lastest bootstrap-fedora. `62cc2def2 <https://github.com/fedora-infra/github2fedmsg/commit/62cc2def29e92abebd37b7bfaf3dc09691e24057>`_ 84 | 85 | 0.2.3 86 | ----- 87 | 88 | - Add jquery back in. `8985732f1 <https://github.com/fedora-infra/github2fedmsg/commit/8985732f1e22a565dfd3ce9964896e9e4f86657e>`_ 89 | - This is gone. `b0e2e309f <https://github.com/fedora-infra/github2fedmsg/commit/b0e2e309f7eb9d00250e9cb164c3a4a3da141877>`_ 90 | - Add agpl header notice to each .py file. `52063ac07 <https://github.com/fedora-infra/github2fedmsg/commit/52063ac07ad83a1ddceeb1c12a9ec93ebc6c65f1>`_ 91 | - Grammar/style fixes in the README. `ba1a8ead4 <https://github.com/fedora-infra/github2fedmsg/commit/ba1a8ead4736a2e9607a886a0a973721b1017387>`_ 92 | - Merge pull request #7 from fedora-infra/feature/review-items `f891d6c4a <https://github.com/fedora-infra/github2fedmsg/commit/f891d6c4a851c2ea381307b1811a3d2d7e21362e>`_ 93 | 94 | 0.2.2 95 | ----- 96 | 97 | - Include license fulltext. `29e06e62d <https://github.com/fedora-infra/github2fedmsg/commit/29e06e62de6d92ff8e6eb5eafccf5548113282da>`_ 98 | - Include tw2.core. `b9483c25e <https://github.com/fedora-infra/github2fedmsg/commit/b9483c25e845cd0656a59cfa8409f6f5fb360304>`_ 99 | - We don't really require these things. `5259948c3 <https://github.com/fedora-infra/github2fedmsg/commit/5259948c36b1ca43008734c1f486f55c3d42af05>`_ 100 | 101 | 0.2.1 102 | ----- 103 | 104 | - Fix inclusion of resources in the dist. `e43d151d5 <https://github.com/fedora-infra/github2fedmsg/commit/e43d151d51620240e1f16befaa999314f31e1da3>`_ 105 | 106 | 0.2 107 | --- 108 | 109 | - Prune secret.ini. `ec496f86b <https://github.com/fedora-infra/github2fedmsg/commit/ec496f86b6415c6cb988b7c62baa3868efd8908a>`_ 110 | -------------------------------------------------------------------------------- /github2fedmsg/static/bootstrap-3.1.1-fedora/css/bootstrap-theme.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.1.1 (http://getbootstrap.com) 3 | * Copyright 2011-2014 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */ 6 | 7 | .btn-default,.btn-primary,.btn-success,.btn-info,.btn-warning,.btn-danger{text-shadow:0 -1px 0 rgba(0,0,0,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075)}.btn-default:active,.btn-primary:active,.btn-success:active,.btn-info:active,.btn-warning:active,.btn-danger:active,.btn-default.active,.btn-primary.active,.btn-success.active,.btn-info.active,.btn-warning.active,.btn-danger.active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn:active,.btn.active{background-image:none}.btn-default{background-image:-webkit-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:linear-gradient(to bottom,#fff 0,#e0e0e0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#dbdbdb;text-shadow:0 1px 0 #fff;border-color:#ccc}.btn-default:hover,.btn-default:focus{background-color:#e0e0e0;background-position:0 -15px}.btn-default:active,.btn-default.active{background-color:#e0e0e0;border-color:#dbdbdb}.btn-primary{background-image:-webkit-linear-gradient(top,#428bca 0,#2d6ca2 100%);background-image:linear-gradient(to bottom,#428bca 0,#2d6ca2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff2d6ca2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#2b669a}.btn-primary:hover,.btn-primary:focus{background-color:#2d6ca2;background-position:0 -15px}.btn-primary:active,.btn-primary.active{background-color:#2d6ca2;border-color:#2b669a}.btn-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:linear-gradient(to bottom,#5cb85c 0,#419641 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#3e8f3e}.btn-success:hover,.btn-success:focus{background-color:#419641;background-position:0 -15px}.btn-success:active,.btn-success.active{background-color:#419641;border-color:#3e8f3e}.btn-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:linear-gradient(to bottom,#5bc0de 0,#2aabd2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#28a4c9}.btn-info:hover,.btn-info:focus{background-color:#2aabd2;background-position:0 -15px}.btn-info:active,.btn-info.active{background-color:#2aabd2;border-color:#28a4c9}.btn-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:linear-gradient(to bottom,#f0ad4e 0,#eb9316 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#e38d13}.btn-warning:hover,.btn-warning:focus{background-color:#eb9316;background-position:0 -15px}.btn-warning:active,.btn-warning.active{background-color:#eb9316;border-color:#e38d13}.btn-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:linear-gradient(to bottom,#d9534f 0,#c12e2a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#b92c28}.btn-danger:hover,.btn-danger:focus{background-color:#c12e2a;background-position:0 -15px}.btn-danger:active,.btn-danger.active{background-color:#c12e2a;border-color:#b92c28}.thumbnail,.img-thumbnail{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-color:#e8e8e8}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{background-image:-webkit-linear-gradient(top,#428bca 0,#357ebd 100%);background-image:linear-gradient(to bottom,#428bca 0,#357ebd 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0);background-color:#357ebd}.navbar-default{background-image:-webkit-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:linear-gradient(to bottom,#fff 0,#f8f8f8 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075)}.navbar-default .navbar-nav>.active>a{background-image:-webkit-linear-gradient(top,#ebebeb 0,#f3f3f3 100%);background-image:linear-gradient(to bottom,#ebebeb 0,#f3f3f3 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff3f3f3', GradientType=0);-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.075);box-shadow:inset 0 3px 9px rgba(0,0,0,.075)}.navbar-brand,.navbar-nav>li>a{text-shadow:0 1px 0 rgba(255,255,255,.25)}.navbar-inverse{background-image:-webkit-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:linear-gradient(to bottom,#3c3c3c 0,#222 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.navbar-inverse .navbar-nav>.active>a{background-image:-webkit-linear-gradient(top,#222 0,#282828 100%);background-image:linear-gradient(to bottom,#222 0,#282828 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222', endColorstr='#ff282828', GradientType=0);-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.25);box-shadow:inset 0 3px 9px rgba(0,0,0,.25)}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{text-shadow:0 -1px 0 rgba(0,0,0,.25)}.navbar-static-top,.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}.alert{text-shadow:0 1px 0 rgba(255,255,255,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05)}.alert-success{background-image:-webkit-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:linear-gradient(to bottom,#dff0d8 0,#c8e5bc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);border-color:#b2dba1}.alert-info{background-image:-webkit-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:linear-gradient(to bottom,#d9edf7 0,#b9def0 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);border-color:#9acfea}.alert-warning{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:linear-gradient(to bottom,#fcf8e3 0,#f8efc0 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);border-color:#f5e79e}.alert-danger{background-image:-webkit-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:linear-gradient(to bottom,#f2dede 0,#e7c3c3 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);border-color:#dca7a7}.progress{background-image:-webkit-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:linear-gradient(to bottom,#ebebeb 0,#f5f5f5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0)}.progress-bar{background-image:-webkit-linear-gradient(top,#428bca 0,#3071a9 100%);background-image:linear-gradient(to bottom,#428bca 0,#3071a9 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0)}.progress-bar-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0)}.progress-bar-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0)}.progress-bar-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0)}.progress-bar-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0)}.list-group{border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{text-shadow:0 -1px 0 #3071a9;background-image:-webkit-linear-gradient(top,#428bca 0,#3278b3 100%);background-image:linear-gradient(to bottom,#428bca 0,#3278b3 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3278b3', GradientType=0);border-color:#3278b3}.panel{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.05);box-shadow:0 1px 2px rgba(0,0,0,.05)}.panel-default>.panel-heading{background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0)}.panel-primary>.panel-heading{background-image:-webkit-linear-gradient(top,#428bca 0,#357ebd 100%);background-image:linear-gradient(to bottom,#428bca 0,#357ebd 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0)}.panel-success>.panel-heading{background-image:-webkit-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:linear-gradient(to bottom,#dff0d8 0,#d0e9c6 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0)}.panel-info>.panel-heading{background-image:-webkit-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:linear-gradient(to bottom,#d9edf7 0,#c4e3f3 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0)}.panel-warning>.panel-heading{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:linear-gradient(to bottom,#fcf8e3 0,#faf2cc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0)}.panel-danger>.panel-heading{background-image:-webkit-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:linear-gradient(to bottom,#f2dede 0,#ebcccc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0)}.well{background-image:-webkit-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:linear-gradient(to bottom,#e8e8e8 0,#f5f5f5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);border-color:#dcdcdc;-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1)} -------------------------------------------------------------------------------- /github2fedmsg/views/webhooks.py: -------------------------------------------------------------------------------- 1 | # This file is a part of github2fedmsg, a pubsubhubbub to zeromq bridge. 2 | # Copyright (C) 2014, Red Hat, Inc. 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Affero General Public License as 6 | # published by the Free Software Foundation, either version 3 of the 7 | # License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Affero General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Affero General Public License 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. 16 | 17 | from pyramid.view import view_config 18 | from pyramid.security import authenticated_userid 19 | from pyramid.httpexceptions import HTTPFound, HTTPUnauthorized, HTTPForbidden 20 | 21 | import github2fedmsg.models as m 22 | 23 | import hashlib 24 | import hmac 25 | import json 26 | import requests 27 | 28 | import logging 29 | from fedora_messaging.api import Message, publish 30 | from fedora_messaging.exceptions import PublishReturned, ConnectionException 31 | 32 | LOGGER = logging.getLogger(__name__) 33 | 34 | # http://developer.github.com/v3/repos/hooks/ 35 | github_pubsubhubub_api_url = "https://api.github.com/hub" 36 | # All events: http://developer.github.com/webhooks/#events 37 | github_events = [ 38 | # "push", # Any git push to a Repository 39 | # "commit_comment", # Any time a Commit is commented on. 40 | # "create", # Any time a Repository, Branch, or Tag is created. 41 | # "delete", # Any time a Branch or Tag is deleted. 42 | # "release", # Any time a Release is published in the Repository. 43 | # "issues", # Any time an Issue is opened or closed. 44 | # "issue_comment", # Any time an Issue is commented on. 45 | # "pull_request", # Any time a Pull Request is opened, closed, or 46 | # # synchronized (updated due to a new push in the 47 | # # branch that the pull request is tracking). 48 | # "pull_request_review_comment", # Any time a Commit is commented on while 49 | # # inside a Pull Request review (the Files 50 | # # Changed tab). 51 | # "gollum", # Any time a Wiki page is updated. 52 | # "watch", # Any time a User watches the Repository. 53 | # "download", # ? 54 | # "fork", # Any time a Repository is forked. 55 | # "fork_apply", # ? 56 | # "member", # Any time a User is added as a collaborator to a 57 | # # non-Organization Repository. 58 | # "public", # Any time a Repository changes from private to public 59 | # "status", # Any time a Repository has a status update from 60 | # # the API. 61 | # "team_add", # Any time a team is added or modified on a 62 | # # Repository. 63 | # "deployment", # Any time a Repository has a new deployment created 64 | # # from the API. 65 | # "deployment_status",# Any time a deployment for the Repository has a 66 | # #status update from the API. 67 | # "page_build", # Any time a Pages site is built or results in a 68 | # # failed build. 69 | "*", # Any time any event is triggered (Wildcard Event). 70 | ] 71 | 72 | 73 | @view_config(route_name='webhook', request_method="POST", renderer='string') 74 | def webhook(request): 75 | """ Handle github webhook. """ 76 | 77 | github_secret = request.registry.settings.get("github.secret") 78 | 79 | hex = hmac.new(github_secret, request.body, hashlib.sha1).hexdigest() 80 | valid_sig = "sha1=%s" % hex 81 | 82 | if 'X-Hub-Signature' not in request.headers: 83 | msg = "No X-Hub-Signature provided" 84 | raise HTTPUnauthorized(msg) 85 | 86 | actual_sig = request.headers['X-Hub-Signature'] 87 | 88 | if actual_sig != valid_sig: 89 | msg = "Invalid X-Hub-Signature" 90 | raise HTTPForbidden(msg) 91 | 92 | if 'payload' in request.params: 93 | # pubsubhubbub 94 | payload = request.params['payload'] 95 | payload = json.loads(payload) 96 | else: 97 | # whatever webhooks 98 | payload = request.json_body 99 | 100 | event_type = request.headers['X-Github-Event'].lower() 101 | 102 | # github sends us a 'ping' when we first subscribe to let us know that 103 | # it worked. 104 | if event_type == 'ping': 105 | event_type = 'webhook' 106 | 107 | # They don't actually tell us which repo is signed up, so, for 108 | # presentation purposes only, we'll rip out the repo name here and 109 | # build a nice human-clickable url. 110 | tokens = payload['hook']['url'].split('/') 111 | owner, repo = tokens[-4], tokens[-3] 112 | payload['compare'] = 'https://github.com/%s/%s' % (owner, repo) 113 | 114 | # Convert github.watch messages to github.star messages. 115 | # See http://da.gd/sUNkj 116 | if event_type == 'watch': 117 | event_type = 'star' 118 | 119 | # Turn just 'issues' into 'issue.reopened' 120 | if event_type == 'issues': 121 | event_type = 'issue.' + payload['action'] 122 | 123 | # Same here 124 | if event_type == 'pull_request': 125 | event_type = 'pull_request.' + payload['action'] 126 | 127 | # Make issues comments match our scheme more nicely 128 | if event_type == 'issue_comment': 129 | event_type = 'issue.comment' 130 | 131 | # Strip out a bunch of redundant information that github sends us 132 | payload = prune_useless_urls(payload) 133 | 134 | # Build a little table of github usernames to fas usernames so 135 | # consumers can have an easy time. 136 | fas_usernames = build_fas_lookup(payload) 137 | payload['fas_usernames'] = fas_usernames 138 | 139 | try: 140 | msg = Message( 141 | topic="github.{}".format(event_type), 142 | body=payload, 143 | ) 144 | publish(msg) 145 | except PublishReturned as e: 146 | LOGGER.warning( 147 | "Fedora Messaging broker rejected message %s: %s", 148 | msg.id, e 149 | ) 150 | except ConnectionException as e: 151 | LOGGER.warning("Error sending message %s: %s", msg.id, e) 152 | 153 | return "OK" 154 | 155 | 156 | def prune_useless_urls(payload): 157 | """ Given *any* github message, strip out unneeded information. """ 158 | 159 | for k, v in payload.items(): 160 | if k == '_links': 161 | payload['html_url'] = v['html']['href'] 162 | del payload[k] 163 | elif isinstance(v, dict): 164 | payload[k] = prune_useless_urls(v) 165 | elif k.endswith('_url') and k not in ['html_url', 'target_url']: 166 | del payload[k] 167 | 168 | return payload 169 | 170 | 171 | def build_fas_lookup(payload): 172 | """ Given *any* github message, build a lookup of github usernames to fas 173 | usernames for it. 174 | 175 | This involves hand coding a bunch of knowledge about github message 176 | formats. 177 | """ 178 | 179 | usernames = set() 180 | 181 | # Trawl through every possible corner we can to find github usernames 182 | if 'commits' in payload: 183 | for commit in payload['commits']: 184 | if 'committer' in commit: 185 | if 'username' in commit['committer']: 186 | usernames.add(commit['committer']['username']) 187 | elif 'name' in commit['committer']: 188 | usernames.add(commit['committer']['name']) 189 | if 'author' in commit: 190 | if 'username' in commit['author']: 191 | usernames.add(commit['author']['username']) 192 | elif 'name' in commit['author']: 193 | usernames.add(commit['author']['name']) 194 | 195 | if 'pusher' in payload: 196 | usernames.add(payload['pusher']['name']) 197 | 198 | if 'sender' in payload: 199 | if 'login' in payload['sender']: 200 | usernames.add(payload['sender']['login']) 201 | 202 | if 'forkee' in payload: 203 | if 'login' in payload['forkee']['owner']: 204 | usernames.add(payload['forkee']['owner']['login']) 205 | 206 | if 'repository' in payload: 207 | if 'login' in payload['repository']['owner']: 208 | usernames.add(payload['repository']['owner']['login']) 209 | 210 | # Take all that, and roll it up into a dict mapping those to FAS 211 | mapping = {} 212 | for github_username in usernames: 213 | if not github_username: 214 | continue 215 | user = m.User.query.filter_by(github_username=github_username).first() 216 | if user: 217 | mapping[github_username] = user.username 218 | 219 | return mapping 220 | 221 | 222 | @view_config(name='toggle', context=m.Repo, renderer='json') 223 | def repo_toggle_enabled(request): 224 | # TODO -- someday, learn how to do the __acls__ thing.. :/ 225 | userid = authenticated_userid(request) 226 | if userid != request.context.user.username: 227 | if userid not in [ 228 | member.username for member in request.context.user.users 229 | ]: 230 | raise HTTPUnauthorized() 231 | 232 | repo = request.context 233 | 234 | # Toggle that attribute on our db model. 235 | repo.enabled = not repo.enabled 236 | 237 | # Now notify github 238 | token = request.user.oauth_access_token 239 | if not token: 240 | raise HTTPForbidden("you need to link your account with github first") 241 | 242 | # We used to do this with pubsubhubbub, but not all of github's oauth 243 | # scopes work there. 244 | # toggle_pubsubhubbub_hooks(request, repo, token) 245 | toggle_webhook_directly(request, repo, token) 246 | 247 | response = { 248 | 'status': 'ok', 249 | 'repo': request.context.__json__(), 250 | 'username': repo.user.username, 251 | 'github_username': repo.user.github_username, 252 | 'enabled': repo.enabled, 253 | } 254 | return response 255 | 256 | 257 | def toggle_pubsubhubbub_hooks(request, repo, token): 258 | data = { 259 | "access_token": token, 260 | "hub.mode": ['unsubscribe', 'subscribe'][repo.enabled], 261 | "hub.callback": request.registry.settings.get("github.callback"), 262 | "hub.secret": request.registry.settings.get("github.secret"), 263 | } 264 | 265 | for event in github_events: 266 | data["hub.topic"] = "https://github.com/%s/%s/events/%s" % ( 267 | repo.user.github_username, repo.name, event) 268 | # Subscribe to events via pubsubhubbub 269 | result = requests.post(github_pubsubhubbub_api_url, data=data) 270 | 271 | if result.status_code < 200 or result.status_code > 299: 272 | d = result.json() 273 | d['status_code'] = result.status_code 274 | raise IOError(d) 275 | 276 | 277 | def toggle_webhook_directly(request, repo, token): 278 | actually_enabled = _get_webhook_status_directly(request, repo, token) 279 | 280 | # Here, repo.enabled represents our database knowledge of the hook state. 281 | # "actually_enabled" represents github's own record keeping. 282 | 283 | if repo.enabled and not actually_enabled: 284 | _enable_webhook_directly(request, repo, token) 285 | elif repo.enabled and actually_enabled: 286 | pass # nothing to do 287 | elif not repo.enabled and actually_enabled: 288 | _disable_webhook_directly(request, repo, actually_enabled, token) 289 | elif not repo.enabled and not actually_enabled: 290 | pass # nothing to do 291 | 292 | 293 | def _get_webhook_status_directly(request, repo, token): 294 | auth = {"access_token": token} 295 | url = "https://api.github.com/repos/%s/%s/hooks" % ( 296 | repo.user.github_username, repo.name) 297 | result = requests.get(url, params=auth) 298 | 299 | if result.status_code < 200 or result.status_code > 299: 300 | d = result.json() 301 | d['status_code'] = result.status_code 302 | raise IOError(d) 303 | 304 | callback = request.registry.settings.get("github.callback") 305 | 306 | hooks = result.json() 307 | for hook in hooks: 308 | if hook['name'] == 'web' and hook['config'].get('url') == callback: 309 | return hook 310 | 311 | return None 312 | 313 | 314 | def _enable_webhook_directly(request, repo, token): 315 | auth = {"access_token": token} 316 | data = { 317 | "name": "web", 318 | "active": True, 319 | "events": github_events, 320 | "config": { 321 | "url": request.registry.settings.get("github.callback"), 322 | "content_type": "json", 323 | "secret": request.registry.settings.get("github.secret"), 324 | }, 325 | } 326 | 327 | headers = {'content-type': 'application/json'} 328 | url = "https://api.github.com/repos/%s/%s/hooks" % ( 329 | repo.user.github_username, repo.name) 330 | result = requests.post( 331 | url, params=auth, data=json.dumps(data), headers=headers) 332 | 333 | if result.status_code < 200 or result.status_code > 299: 334 | d = result.json() 335 | d['status_code'] = result.status_code 336 | raise IOError(d) 337 | 338 | 339 | def _disable_webhook_directly(request, repo, hook, token): 340 | auth = {"access_token": token} 341 | url = "https://api.github.com/repos/%s/%s/hooks/%i" % ( 342 | repo.user.github_username, repo.name, hook['id']) 343 | requests.delete(url, params=auth) 344 | -------------------------------------------------------------------------------- /github2fedmsg/static/messenger-1.4.1/css/messenger-theme-air.css: -------------------------------------------------------------------------------- 1 | @import url("//fonts.googleapis.com/css?family=Raleway:400"); 2 | @-webkit-keyframes ui-spinner-rotate-right { 3 | /* line 64, ../../src/sass/messenger-spinner.scss */ 4 | 0% { 5 | -webkit-transform: rotate(0deg); 6 | } 7 | 8 | /* line 65, ../../src/sass/messenger-spinner.scss */ 9 | 25% { 10 | -webkit-transform: rotate(180deg); 11 | } 12 | 13 | /* line 66, ../../src/sass/messenger-spinner.scss */ 14 | 50% { 15 | -webkit-transform: rotate(180deg); 16 | } 17 | 18 | /* line 67, ../../src/sass/messenger-spinner.scss */ 19 | 75% { 20 | -webkit-transform: rotate(360deg); 21 | } 22 | 23 | /* line 68, ../../src/sass/messenger-spinner.scss */ 24 | 100% { 25 | -webkit-transform: rotate(360deg); 26 | } 27 | } 28 | 29 | @-webkit-keyframes ui-spinner-rotate-left { 30 | /* line 72, ../../src/sass/messenger-spinner.scss */ 31 | 0% { 32 | -webkit-transform: rotate(0deg); 33 | } 34 | 35 | /* line 73, ../../src/sass/messenger-spinner.scss */ 36 | 25% { 37 | -webkit-transform: rotate(0deg); 38 | } 39 | 40 | /* line 74, ../../src/sass/messenger-spinner.scss */ 41 | 50% { 42 | -webkit-transform: rotate(180deg); 43 | } 44 | 45 | /* line 75, ../../src/sass/messenger-spinner.scss */ 46 | 75% { 47 | -webkit-transform: rotate(180deg); 48 | } 49 | 50 | /* line 76, ../../src/sass/messenger-spinner.scss */ 51 | 100% { 52 | -webkit-transform: rotate(360deg); 53 | } 54 | } 55 | 56 | @-moz-keyframes ui-spinner-rotate-right { 57 | /* line 80, ../../src/sass/messenger-spinner.scss */ 58 | 0% { 59 | -moz-transform: rotate(0deg); 60 | } 61 | 62 | /* line 81, ../../src/sass/messenger-spinner.scss */ 63 | 25% { 64 | -moz-transform: rotate(180deg); 65 | } 66 | 67 | /* line 82, ../../src/sass/messenger-spinner.scss */ 68 | 50% { 69 | -moz-transform: rotate(180deg); 70 | } 71 | 72 | /* line 83, ../../src/sass/messenger-spinner.scss */ 73 | 75% { 74 | -moz-transform: rotate(360deg); 75 | } 76 | 77 | /* line 84, ../../src/sass/messenger-spinner.scss */ 78 | 100% { 79 | -moz-transform: rotate(360deg); 80 | } 81 | } 82 | 83 | @-moz-keyframes ui-spinner-rotate-left { 84 | /* line 88, ../../src/sass/messenger-spinner.scss */ 85 | 0% { 86 | -moz-transform: rotate(0deg); 87 | } 88 | 89 | /* line 89, ../../src/sass/messenger-spinner.scss */ 90 | 25% { 91 | -moz-transform: rotate(0deg); 92 | } 93 | 94 | /* line 90, ../../src/sass/messenger-spinner.scss */ 95 | 50% { 96 | -moz-transform: rotate(180deg); 97 | } 98 | 99 | /* line 91, ../../src/sass/messenger-spinner.scss */ 100 | 75% { 101 | -moz-transform: rotate(180deg); 102 | } 103 | 104 | /* line 92, ../../src/sass/messenger-spinner.scss */ 105 | 100% { 106 | -moz-transform: rotate(360deg); 107 | } 108 | } 109 | 110 | @keyframes ui-spinner-rotate-right { 111 | /* line 96, ../../src/sass/messenger-spinner.scss */ 112 | 0% { 113 | transform: rotate(0deg); 114 | } 115 | 116 | /* line 97, ../../src/sass/messenger-spinner.scss */ 117 | 25% { 118 | transform: rotate(180deg); 119 | } 120 | 121 | /* line 98, ../../src/sass/messenger-spinner.scss */ 122 | 50% { 123 | transform: rotate(180deg); 124 | } 125 | 126 | /* line 99, ../../src/sass/messenger-spinner.scss */ 127 | 75% { 128 | transform: rotate(360deg); 129 | } 130 | 131 | /* line 100, ../../src/sass/messenger-spinner.scss */ 132 | 100% { 133 | transform: rotate(360deg); 134 | } 135 | } 136 | 137 | @keyframes ui-spinner-rotate-left { 138 | /* line 104, ../../src/sass/messenger-spinner.scss */ 139 | 0% { 140 | transform: rotate(0deg); 141 | } 142 | 143 | /* line 105, ../../src/sass/messenger-spinner.scss */ 144 | 25% { 145 | transform: rotate(0deg); 146 | } 147 | 148 | /* line 106, ../../src/sass/messenger-spinner.scss */ 149 | 50% { 150 | transform: rotate(180deg); 151 | } 152 | 153 | /* line 107, ../../src/sass/messenger-spinner.scss */ 154 | 75% { 155 | transform: rotate(180deg); 156 | } 157 | 158 | /* line 108, ../../src/sass/messenger-spinner.scss */ 159 | 100% { 160 | transform: rotate(360deg); 161 | } 162 | } 163 | 164 | /* line 116, ../../src/sass/messenger-spinner.scss */ 165 | .messenger-spinner { 166 | position: relative; 167 | border-radius: 100%; 168 | } 169 | /* line 120, ../../src/sass/messenger-spinner.scss */ 170 | ul.messenger.messenger-spinner-active .messenger-spinner .messenger-spinner { 171 | display: block; 172 | } 173 | /* line 124, ../../src/sass/messenger-spinner.scss */ 174 | .messenger-spinner .messenger-spinner-side { 175 | width: 50%; 176 | height: 100%; 177 | overflow: hidden; 178 | position: absolute; 179 | } 180 | /* line 130, ../../src/sass/messenger-spinner.scss */ 181 | .messenger-spinner .messenger-spinner-side .messenger-spinner-fill { 182 | border-radius: 999px; 183 | position: absolute; 184 | width: 100%; 185 | height: 100%; 186 | -webkit-animation-iteration-count: infinite; 187 | -moz-animation-iteration-count: infinite; 188 | -ms-animation-iteration-count: infinite; 189 | -o-animation-iteration-count: infinite; 190 | animation-iteration-count: infinite; 191 | -webkit-animation-timing-function: linear; 192 | -moz-animation-timing-function: linear; 193 | -ms-animation-timing-function: linear; 194 | -o-animation-timing-function: linear; 195 | animation-timing-function: linear; 196 | } 197 | /* line 140, ../../src/sass/messenger-spinner.scss */ 198 | .messenger-spinner .messenger-spinner-side-left { 199 | left: 0; 200 | } 201 | /* line 143, ../../src/sass/messenger-spinner.scss */ 202 | .messenger-spinner .messenger-spinner-side-left .messenger-spinner-fill { 203 | left: 100%; 204 | border-top-left-radius: 0; 205 | border-bottom-left-radius: 0; 206 | -webkit-animation-name: ui-spinner-rotate-left; 207 | -moz-animation-name: ui-spinner-rotate-left; 208 | -ms-animation-name: ui-spinner-rotate-left; 209 | -o-animation-name: ui-spinner-rotate-left; 210 | animation-name: ui-spinner-rotate-left; 211 | -webkit-transform-origin: 0 50%; 212 | -moz-transform-origin: 0 50%; 213 | -ms-transform-origin: 0 50%; 214 | -o-transform-origin: 0 50%; 215 | transform-origin: 0 50%; 216 | } 217 | /* line 152, ../../src/sass/messenger-spinner.scss */ 218 | .messenger-spinner .messenger-spinner-side-right { 219 | left: 50%; 220 | } 221 | /* line 155, ../../src/sass/messenger-spinner.scss */ 222 | .messenger-spinner .messenger-spinner-side-right .messenger-spinner-fill { 223 | left: -100%; 224 | border-top-right-radius: 0; 225 | border-bottom-right-radius: 0; 226 | -webkit-animation-name: ui-spinner-rotate-right; 227 | -moz-animation-name: ui-spinner-rotate-right; 228 | -ms-animation-name: ui-spinner-rotate-right; 229 | -o-animation-name: ui-spinner-rotate-right; 230 | animation-name: ui-spinner-rotate-right; 231 | -webkit-transform-origin: 100% 50%; 232 | -moz-transform-origin: 100% 50%; 233 | -ms-transform-origin: 100% 50%; 234 | -o-transform-origin: 100% 50%; 235 | transform-origin: 100% 50%; 236 | } 237 | 238 | /* line 16, ../../src/sass/messenger-theme-air.sass */ 239 | ul.messenger-theme-air { 240 | -moz-user-select: none; 241 | -webkit-user-select: none; 242 | -o-user-select: none; 243 | user-select: none; 244 | font-family: "Raleway", sans-serif; 245 | } 246 | /* line 20, ../../src/sass/messenger-theme-air.sass */ 247 | ul.messenger-theme-air .messenger-message { 248 | -webkit-transition: background-color 0.4s; 249 | -moz-transition: background-color 0.4s; 250 | -o-transition: background-color 0.4s; 251 | transition: background-color 0.4s; 252 | -webkit-border-radius: 5px; 253 | -moz-border-radius: 5px; 254 | -ms-border-radius: 5px; 255 | -o-border-radius: 5px; 256 | border-radius: 5px; 257 | -webkit-box-shadow: inset 0 0 0 1px white, inset 0 2px white, 0 0 0 1px rgba(0, 0, 0, 0.1), 0 1px rgba(0, 0, 0, 0.2); 258 | -moz-box-shadow: inset 0 0 0 1px white, inset 0 2px white, 0 0 0 1px rgba(0, 0, 0, 0.1), 0 1px rgba(0, 0, 0, 0.2); 259 | box-shadow: inset 0 0 0 1px white, inset 0 2px white, 0 0 0 1px rgba(0, 0, 0, 0.1), 0 1px rgba(0, 0, 0, 0.2); 260 | border: 0px; 261 | background-color: rgba(255, 255, 255, 0.8); 262 | position: relative; 263 | margin-bottom: 1em; 264 | font-size: 13px; 265 | color: #666666; 266 | font-weight: 500; 267 | padding: 10px 30px 11px 46px; 268 | } 269 | /* line 33, ../../src/sass/messenger-theme-air.sass */ 270 | ul.messenger-theme-air .messenger-message:hover { 271 | background-color: white; 272 | } 273 | /* line 36, ../../src/sass/messenger-theme-air.sass */ 274 | ul.messenger-theme-air .messenger-message .messenger-close { 275 | position: absolute; 276 | top: 0px; 277 | right: 0px; 278 | color: #888888; 279 | opacity: 1; 280 | font-weight: bold; 281 | display: block; 282 | font-size: 20px; 283 | line-height: 20px; 284 | padding: 8px 10px 7px 7px; 285 | cursor: pointer; 286 | background: transparent; 287 | border: 0; 288 | -webkit-appearance: none; 289 | } 290 | /* line 52, ../../src/sass/messenger-theme-air.sass */ 291 | ul.messenger-theme-air .messenger-message .messenger-close:hover { 292 | color: #444444; 293 | } 294 | /* line 55, ../../src/sass/messenger-theme-air.sass */ 295 | ul.messenger-theme-air .messenger-message .messenger-close:active { 296 | color: #222222; 297 | } 298 | /* line 58, ../../src/sass/messenger-theme-air.sass */ 299 | ul.messenger-theme-air .messenger-message .messenger-actions { 300 | float: none; 301 | margin-top: 10px; 302 | } 303 | /* line 62, ../../src/sass/messenger-theme-air.sass */ 304 | ul.messenger-theme-air .messenger-message .messenger-actions a { 305 | -webkit-box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.1), inset 0px 1px rgba(255, 255, 255, 0.05); 306 | -moz-box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.1), inset 0px 1px rgba(255, 255, 255, 0.05); 307 | box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.1), inset 0px 1px rgba(255, 255, 255, 0.05); 308 | -webkit-border-radius: 4px; 309 | -moz-border-radius: 4px; 310 | -ms-border-radius: 4px; 311 | -o-border-radius: 4px; 312 | border-radius: 4px; 313 | text-decoration: none; 314 | display: inline-block; 315 | padding: 10px; 316 | color: #888888; 317 | margin-right: 10px; 318 | padding: 3px 10px 5px; 319 | text-transform: capitalize; 320 | } 321 | /* line 73, ../../src/sass/messenger-theme-air.sass */ 322 | ul.messenger-theme-air .messenger-message .messenger-actions a:hover { 323 | -webkit-box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.1), inset 0px 1px rgba(255, 255, 255, 0.15); 324 | -moz-box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.1), inset 0px 1px rgba(255, 255, 255, 0.15); 325 | box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.1), inset 0px 1px rgba(255, 255, 255, 0.15); 326 | color: #444444; 327 | } 328 | /* line 77, ../../src/sass/messenger-theme-air.sass */ 329 | ul.messenger-theme-air .messenger-message .messenger-actions a:active { 330 | -webkit-box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.18), inset 0px 1px rgba(0, 0, 0, 0.05); 331 | -moz-box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.18), inset 0px 1px rgba(0, 0, 0, 0.05); 332 | box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.18), inset 0px 1px rgba(0, 0, 0, 0.05); 333 | background: rgba(0, 0, 0, 0.04); 334 | color: #444444; 335 | } 336 | /* line 82, ../../src/sass/messenger-theme-air.sass */ 337 | ul.messenger-theme-air .messenger-message .messenger-actions .messenger-phrase { 338 | display: none; 339 | } 340 | /* line 85, ../../src/sass/messenger-theme-air.sass */ 341 | ul.messenger-theme-air .messenger-message .messenger-message-inner:before { 342 | -webkit-box-shadow: inset 0px 1px 3px rgba(0, 0, 0, 0.3); 343 | -moz-box-shadow: inset 0px 1px 3px rgba(0, 0, 0, 0.3); 344 | box-shadow: inset 0px 1px 3px rgba(0, 0, 0, 0.3); 345 | -webkit-border-radius: 50%; 346 | -moz-border-radius: 50%; 347 | -ms-border-radius: 50%; 348 | -o-border-radius: 50%; 349 | border-radius: 50%; 350 | position: absolute; 351 | left: 17px; 352 | display: block; 353 | content: " "; 354 | top: 50%; 355 | margin-top: -8px; 356 | height: 13px; 357 | width: 13px; 358 | z-index: 20; 359 | } 360 | /* line 99, ../../src/sass/messenger-theme-air.sass */ 361 | ul.messenger-theme-air .messenger-message.alert-success .messenger-message-inner:before { 362 | background-color: #5fca4a; 363 | } 364 | /* line 32, ../../src/sass/messenger-spinner.scss */ 365 | ul.messenger-theme-air .messenger-message.alert-error.messenger-retry-soon .messenger-spinner { 366 | width: 24px; 367 | height: 24px; 368 | background: transparent; 369 | } 370 | /* line 37, ../../src/sass/messenger-spinner.scss */ 371 | ul.messenger-theme-air .messenger-message.alert-error.messenger-retry-soon .messenger-spinner .messenger-spinner-side .messenger-spinner-fill { 372 | background: #dd6a45; 373 | -webkit-animation-duration: 20s; 374 | -moz-animation-duration: 20s; 375 | -ms-animation-duration: 20s; 376 | -o-animation-duration: 20s; 377 | animation-duration: 20s; 378 | opacity: 1; 379 | } 380 | /* line 45, ../../src/sass/messenger-spinner.scss */ 381 | ul.messenger-theme-air .messenger-message.alert-error.messenger-retry-soon .messenger-spinner:after { 382 | content: ""; 383 | background: white; 384 | position: absolute; 385 | width: 19px; 386 | height: 19px; 387 | border-radius: 50%; 388 | top: 2px; 389 | left: 2px; 390 | display: block; 391 | } 392 | /* line 32, ../../src/sass/messenger-spinner.scss */ 393 | ul.messenger-theme-air .messenger-message.alert-error.messenger-retry-later .messenger-spinner { 394 | width: 24px; 395 | height: 24px; 396 | background: transparent; 397 | } 398 | /* line 37, ../../src/sass/messenger-spinner.scss */ 399 | ul.messenger-theme-air .messenger-message.alert-error.messenger-retry-later .messenger-spinner .messenger-spinner-side .messenger-spinner-fill { 400 | background: #dd6a45; 401 | -webkit-animation-duration: 600s; 402 | -moz-animation-duration: 600s; 403 | -ms-animation-duration: 600s; 404 | -o-animation-duration: 600s; 405 | animation-duration: 600s; 406 | opacity: 1; 407 | } 408 | /* line 45, ../../src/sass/messenger-spinner.scss */ 409 | ul.messenger-theme-air .messenger-message.alert-error.messenger-retry-later .messenger-spinner:after { 410 | content: ""; 411 | background: white; 412 | position: absolute; 413 | width: 19px; 414 | height: 19px; 415 | border-radius: 50%; 416 | top: 2px; 417 | left: 2px; 418 | display: block; 419 | } 420 | /* line 109, ../../src/sass/messenger-theme-air.sass */ 421 | ul.messenger-theme-air .messenger-message.alert-error .messenger-message-inner:before { 422 | background-color: #dd6a45; 423 | } 424 | /* line 113, ../../src/sass/messenger-theme-air.sass */ 425 | ul.messenger-theme-air .messenger-message.alert-info .messenger-message-inner:before { 426 | background-color: #61c4b8; 427 | } 428 | /* line 116, ../../src/sass/messenger-theme-air.sass */ 429 | ul.messenger-theme-air .messenger-spinner { 430 | display: block; 431 | position: absolute; 432 | left: 12px; 433 | top: 50%; 434 | margin-top: -13px; 435 | z-index: 999; 436 | height: 24px; 437 | width: 24px; 438 | z-index: 10; 439 | } 440 | -------------------------------------------------------------------------------- /github2fedmsg/static/messenger-1.4.1/css/messenger-theme-flat.css: -------------------------------------------------------------------------------- 1 | @-webkit-keyframes ui-spinner-rotate-right { 2 | /* line 64, ../../src/sass/messenger-spinner.scss */ 3 | 0% { 4 | -webkit-transform: rotate(0deg); 5 | } 6 | 7 | /* line 65, ../../src/sass/messenger-spinner.scss */ 8 | 25% { 9 | -webkit-transform: rotate(180deg); 10 | } 11 | 12 | /* line 66, ../../src/sass/messenger-spinner.scss */ 13 | 50% { 14 | -webkit-transform: rotate(180deg); 15 | } 16 | 17 | /* line 67, ../../src/sass/messenger-spinner.scss */ 18 | 75% { 19 | -webkit-transform: rotate(360deg); 20 | } 21 | 22 | /* line 68, ../../src/sass/messenger-spinner.scss */ 23 | 100% { 24 | -webkit-transform: rotate(360deg); 25 | } 26 | } 27 | 28 | @-webkit-keyframes ui-spinner-rotate-left { 29 | /* line 72, ../../src/sass/messenger-spinner.scss */ 30 | 0% { 31 | -webkit-transform: rotate(0deg); 32 | } 33 | 34 | /* line 73, ../../src/sass/messenger-spinner.scss */ 35 | 25% { 36 | -webkit-transform: rotate(0deg); 37 | } 38 | 39 | /* line 74, ../../src/sass/messenger-spinner.scss */ 40 | 50% { 41 | -webkit-transform: rotate(180deg); 42 | } 43 | 44 | /* line 75, ../../src/sass/messenger-spinner.scss */ 45 | 75% { 46 | -webkit-transform: rotate(180deg); 47 | } 48 | 49 | /* line 76, ../../src/sass/messenger-spinner.scss */ 50 | 100% { 51 | -webkit-transform: rotate(360deg); 52 | } 53 | } 54 | 55 | @-moz-keyframes ui-spinner-rotate-right { 56 | /* line 80, ../../src/sass/messenger-spinner.scss */ 57 | 0% { 58 | -moz-transform: rotate(0deg); 59 | } 60 | 61 | /* line 81, ../../src/sass/messenger-spinner.scss */ 62 | 25% { 63 | -moz-transform: rotate(180deg); 64 | } 65 | 66 | /* line 82, ../../src/sass/messenger-spinner.scss */ 67 | 50% { 68 | -moz-transform: rotate(180deg); 69 | } 70 | 71 | /* line 83, ../../src/sass/messenger-spinner.scss */ 72 | 75% { 73 | -moz-transform: rotate(360deg); 74 | } 75 | 76 | /* line 84, ../../src/sass/messenger-spinner.scss */ 77 | 100% { 78 | -moz-transform: rotate(360deg); 79 | } 80 | } 81 | 82 | @-moz-keyframes ui-spinner-rotate-left { 83 | /* line 88, ../../src/sass/messenger-spinner.scss */ 84 | 0% { 85 | -moz-transform: rotate(0deg); 86 | } 87 | 88 | /* line 89, ../../src/sass/messenger-spinner.scss */ 89 | 25% { 90 | -moz-transform: rotate(0deg); 91 | } 92 | 93 | /* line 90, ../../src/sass/messenger-spinner.scss */ 94 | 50% { 95 | -moz-transform: rotate(180deg); 96 | } 97 | 98 | /* line 91, ../../src/sass/messenger-spinner.scss */ 99 | 75% { 100 | -moz-transform: rotate(180deg); 101 | } 102 | 103 | /* line 92, ../../src/sass/messenger-spinner.scss */ 104 | 100% { 105 | -moz-transform: rotate(360deg); 106 | } 107 | } 108 | 109 | @keyframes ui-spinner-rotate-right { 110 | /* line 96, ../../src/sass/messenger-spinner.scss */ 111 | 0% { 112 | transform: rotate(0deg); 113 | } 114 | 115 | /* line 97, ../../src/sass/messenger-spinner.scss */ 116 | 25% { 117 | transform: rotate(180deg); 118 | } 119 | 120 | /* line 98, ../../src/sass/messenger-spinner.scss */ 121 | 50% { 122 | transform: rotate(180deg); 123 | } 124 | 125 | /* line 99, ../../src/sass/messenger-spinner.scss */ 126 | 75% { 127 | transform: rotate(360deg); 128 | } 129 | 130 | /* line 100, ../../src/sass/messenger-spinner.scss */ 131 | 100% { 132 | transform: rotate(360deg); 133 | } 134 | } 135 | 136 | @keyframes ui-spinner-rotate-left { 137 | /* line 104, ../../src/sass/messenger-spinner.scss */ 138 | 0% { 139 | transform: rotate(0deg); 140 | } 141 | 142 | /* line 105, ../../src/sass/messenger-spinner.scss */ 143 | 25% { 144 | transform: rotate(0deg); 145 | } 146 | 147 | /* line 106, ../../src/sass/messenger-spinner.scss */ 148 | 50% { 149 | transform: rotate(180deg); 150 | } 151 | 152 | /* line 107, ../../src/sass/messenger-spinner.scss */ 153 | 75% { 154 | transform: rotate(180deg); 155 | } 156 | 157 | /* line 108, ../../src/sass/messenger-spinner.scss */ 158 | 100% { 159 | transform: rotate(360deg); 160 | } 161 | } 162 | 163 | /* line 116, ../../src/sass/messenger-spinner.scss */ 164 | .messenger-spinner { 165 | position: relative; 166 | border-radius: 100%; 167 | } 168 | /* line 120, ../../src/sass/messenger-spinner.scss */ 169 | ul.messenger.messenger-spinner-active .messenger-spinner .messenger-spinner { 170 | display: block; 171 | } 172 | /* line 124, ../../src/sass/messenger-spinner.scss */ 173 | .messenger-spinner .messenger-spinner-side { 174 | width: 50%; 175 | height: 100%; 176 | overflow: hidden; 177 | position: absolute; 178 | } 179 | /* line 130, ../../src/sass/messenger-spinner.scss */ 180 | .messenger-spinner .messenger-spinner-side .messenger-spinner-fill { 181 | border-radius: 999px; 182 | position: absolute; 183 | width: 100%; 184 | height: 100%; 185 | -webkit-animation-iteration-count: infinite; 186 | -moz-animation-iteration-count: infinite; 187 | -ms-animation-iteration-count: infinite; 188 | -o-animation-iteration-count: infinite; 189 | animation-iteration-count: infinite; 190 | -webkit-animation-timing-function: linear; 191 | -moz-animation-timing-function: linear; 192 | -ms-animation-timing-function: linear; 193 | -o-animation-timing-function: linear; 194 | animation-timing-function: linear; 195 | } 196 | /* line 140, ../../src/sass/messenger-spinner.scss */ 197 | .messenger-spinner .messenger-spinner-side-left { 198 | left: 0; 199 | } 200 | /* line 143, ../../src/sass/messenger-spinner.scss */ 201 | .messenger-spinner .messenger-spinner-side-left .messenger-spinner-fill { 202 | left: 100%; 203 | border-top-left-radius: 0; 204 | border-bottom-left-radius: 0; 205 | -webkit-animation-name: ui-spinner-rotate-left; 206 | -moz-animation-name: ui-spinner-rotate-left; 207 | -ms-animation-name: ui-spinner-rotate-left; 208 | -o-animation-name: ui-spinner-rotate-left; 209 | animation-name: ui-spinner-rotate-left; 210 | -webkit-transform-origin: 0 50%; 211 | -moz-transform-origin: 0 50%; 212 | -ms-transform-origin: 0 50%; 213 | -o-transform-origin: 0 50%; 214 | transform-origin: 0 50%; 215 | } 216 | /* line 152, ../../src/sass/messenger-spinner.scss */ 217 | .messenger-spinner .messenger-spinner-side-right { 218 | left: 50%; 219 | } 220 | /* line 155, ../../src/sass/messenger-spinner.scss */ 221 | .messenger-spinner .messenger-spinner-side-right .messenger-spinner-fill { 222 | left: -100%; 223 | border-top-right-radius: 0; 224 | border-bottom-right-radius: 0; 225 | -webkit-animation-name: ui-spinner-rotate-right; 226 | -moz-animation-name: ui-spinner-rotate-right; 227 | -ms-animation-name: ui-spinner-rotate-right; 228 | -o-animation-name: ui-spinner-rotate-right; 229 | animation-name: ui-spinner-rotate-right; 230 | -webkit-transform-origin: 100% 50%; 231 | -moz-transform-origin: 100% 50%; 232 | -ms-transform-origin: 100% 50%; 233 | -o-transform-origin: 100% 50%; 234 | transform-origin: 100% 50%; 235 | } 236 | 237 | /* line 15, ../../src/sass/messenger-theme-flat.sass */ 238 | ul.messenger-theme-flat { 239 | -webkit-border-radius: 4px; 240 | -moz-border-radius: 4px; 241 | -ms-border-radius: 4px; 242 | -o-border-radius: 4px; 243 | border-radius: 4px; 244 | -moz-user-select: none; 245 | -webkit-user-select: none; 246 | -o-user-select: none; 247 | user-select: none; 248 | background: #404040; 249 | } 250 | /* line 20, ../../src/sass/messenger-theme-flat.sass */ 251 | ul.messenger-theme-flat.messenger-empty { 252 | display: none; 253 | } 254 | /* line 23, ../../src/sass/messenger-theme-flat.sass */ 255 | ul.messenger-theme-flat .messenger-message { 256 | -webkit-box-shadow: inset 0px 1px rgba(255, 255, 255, 0.13), inset 48px 0px 0px #292929; 257 | -moz-box-shadow: inset 0px 1px rgba(255, 255, 255, 0.13), inset 48px 0px 0px #292929; 258 | box-shadow: inset 0px 1px rgba(255, 255, 255, 0.13), inset 48px 0px 0px #292929; 259 | -webkit-border-radius: 0px; 260 | -moz-border-radius: 0px; 261 | -ms-border-radius: 0px; 262 | -o-border-radius: 0px; 263 | border-radius: 0px; 264 | position: relative; 265 | border: 0px; 266 | margin-bottom: 0px; 267 | font-size: 13px; 268 | background: transparent; 269 | color: #f0f0f0; 270 | font-weight: 500; 271 | padding: 10px 30px 13px 65px; 272 | } 273 | /* line 35, ../../src/sass/messenger-theme-flat.sass */ 274 | ul.messenger-theme-flat .messenger-message .messenger-close { 275 | position: absolute; 276 | top: 0px; 277 | right: 0px; 278 | color: #888888; 279 | opacity: 1; 280 | font-weight: bold; 281 | display: block; 282 | font-size: 20px; 283 | line-height: 20px; 284 | padding: 8px 10px 7px 7px; 285 | cursor: pointer; 286 | background: transparent; 287 | border: 0; 288 | -webkit-appearance: none; 289 | } 290 | /* line 51, ../../src/sass/messenger-theme-flat.sass */ 291 | ul.messenger-theme-flat .messenger-message .messenger-close:hover { 292 | color: #bbbbbb; 293 | } 294 | /* line 54, ../../src/sass/messenger-theme-flat.sass */ 295 | ul.messenger-theme-flat .messenger-message .messenger-close:active { 296 | color: #777777; 297 | } 298 | /* line 57, ../../src/sass/messenger-theme-flat.sass */ 299 | ul.messenger-theme-flat .messenger-message .messenger-actions { 300 | float: none; 301 | margin-top: 10px; 302 | } 303 | /* line 61, ../../src/sass/messenger-theme-flat.sass */ 304 | ul.messenger-theme-flat .messenger-message .messenger-actions a { 305 | -webkit-border-radius: 4px; 306 | -moz-border-radius: 4px; 307 | -ms-border-radius: 4px; 308 | -o-border-radius: 4px; 309 | border-radius: 4px; 310 | text-decoration: none; 311 | color: #aaaaaa; 312 | background: #2e2e2e; 313 | display: inline-block; 314 | padding: 10px; 315 | margin-right: 10px; 316 | padding: 4px 11px 6px; 317 | text-transform: capitalize; 318 | } 319 | /* line 72, ../../src/sass/messenger-theme-flat.sass */ 320 | ul.messenger-theme-flat .messenger-message .messenger-actions a:hover { 321 | color: #f0f0f0; 322 | background: #2e2e2e; 323 | } 324 | /* line 76, ../../src/sass/messenger-theme-flat.sass */ 325 | ul.messenger-theme-flat .messenger-message .messenger-actions a:active { 326 | background: #292929; 327 | color: #aaaaaa; 328 | } 329 | /* line 80, ../../src/sass/messenger-theme-flat.sass */ 330 | ul.messenger-theme-flat .messenger-message .messenger-actions .messenger-phrase { 331 | display: none; 332 | } 333 | /* line 83, ../../src/sass/messenger-theme-flat.sass */ 334 | ul.messenger-theme-flat .messenger-message .messenger-message-inner:before { 335 | -webkit-border-radius: 50%; 336 | -moz-border-radius: 50%; 337 | -ms-border-radius: 50%; 338 | -o-border-radius: 50%; 339 | border-radius: 50%; 340 | position: absolute; 341 | left: 17px; 342 | display: block; 343 | content: " "; 344 | top: 50%; 345 | margin-top: -8px; 346 | height: 13px; 347 | width: 13px; 348 | z-index: 20; 349 | } 350 | /* line 95, ../../src/sass/messenger-theme-flat.sass */ 351 | ul.messenger-theme-flat .messenger-message.alert-success .messenger-message-inner:before { 352 | background: #5fca4a; 353 | } 354 | /* line 98, ../../src/sass/messenger-theme-flat.sass */ 355 | ul.messenger-theme-flat .messenger-message.alert-info .messenger-message-inner:before { 356 | background: #61c4b8; 357 | } 358 | /* line 103, ../../src/sass/messenger-theme-flat.sass */ 359 | ul.messenger-theme-flat .messenger-message.alert-error .messenger-message-inner:before { 360 | background: #dd6a45; 361 | } 362 | /* line 32, ../../src/sass/messenger-spinner.scss */ 363 | ul.messenger-theme-flat .messenger-message.alert-error.messenger-retry-soon .messenger-spinner { 364 | width: 32px; 365 | height: 32px; 366 | background: transparent; 367 | } 368 | /* line 37, ../../src/sass/messenger-spinner.scss */ 369 | ul.messenger-theme-flat .messenger-message.alert-error.messenger-retry-soon .messenger-spinner .messenger-spinner-side .messenger-spinner-fill { 370 | background: #dd6a45; 371 | -webkit-animation-duration: 20s; 372 | -moz-animation-duration: 20s; 373 | -ms-animation-duration: 20s; 374 | -o-animation-duration: 20s; 375 | animation-duration: 20s; 376 | opacity: 1; 377 | } 378 | /* line 45, ../../src/sass/messenger-spinner.scss */ 379 | ul.messenger-theme-flat .messenger-message.alert-error.messenger-retry-soon .messenger-spinner:after { 380 | content: ""; 381 | background: #292929; 382 | position: absolute; 383 | width: 26px; 384 | height: 26px; 385 | border-radius: 50%; 386 | top: 3px; 387 | left: 3px; 388 | display: block; 389 | } 390 | /* line 32, ../../src/sass/messenger-spinner.scss */ 391 | ul.messenger-theme-flat .messenger-message.alert-error.messenger-retry-later .messenger-spinner { 392 | width: 32px; 393 | height: 32px; 394 | background: transparent; 395 | } 396 | /* line 37, ../../src/sass/messenger-spinner.scss */ 397 | ul.messenger-theme-flat .messenger-message.alert-error.messenger-retry-later .messenger-spinner .messenger-spinner-side .messenger-spinner-fill { 398 | background: #dd6a45; 399 | -webkit-animation-duration: 600s; 400 | -moz-animation-duration: 600s; 401 | -ms-animation-duration: 600s; 402 | -o-animation-duration: 600s; 403 | animation-duration: 600s; 404 | opacity: 1; 405 | } 406 | /* line 45, ../../src/sass/messenger-spinner.scss */ 407 | ul.messenger-theme-flat .messenger-message.alert-error.messenger-retry-later .messenger-spinner:after { 408 | content: ""; 409 | background: #292929; 410 | position: absolute; 411 | width: 26px; 412 | height: 26px; 413 | border-radius: 50%; 414 | top: 3px; 415 | left: 3px; 416 | display: block; 417 | } 418 | /* line 114, ../../src/sass/messenger-theme-flat.sass */ 419 | ul.messenger-theme-flat .messenger-message-slot.messenger-last .messenger-message { 420 | -webkit-border-radius: 4px 4px 0px 0px; 421 | -moz-border-radius: 4px 4px 0px 0px; 422 | -ms-border-radius: 4px 4px 0px 0px; 423 | -o-border-radius: 4px 4px 0px 0px; 424 | border-radius: 4px 4px 0px 0px; 425 | -webkit-box-shadow: inset 48px 0px 0px #292929; 426 | -moz-box-shadow: inset 48px 0px 0px #292929; 427 | box-shadow: inset 48px 0px 0px #292929; 428 | } 429 | /* line 118, ../../src/sass/messenger-theme-flat.sass */ 430 | ul.messenger-theme-flat .messenger-message-slot.messenger-first .messenger-message { 431 | -webkit-border-radius: 0px 0px 4px 4px; 432 | -moz-border-radius: 0px 0px 4px 4px; 433 | -ms-border-radius: 0px 0px 4px 4px; 434 | -o-border-radius: 0px 0px 4px 4px; 435 | border-radius: 0px 0px 4px 4px; 436 | -webkit-box-shadow: inset 0px 1px rgba(255, 255, 255, 0.13), inset 48px 0px 0px #292929; 437 | -moz-box-shadow: inset 0px 1px rgba(255, 255, 255, 0.13), inset 48px 0px 0px #292929; 438 | box-shadow: inset 0px 1px rgba(255, 255, 255, 0.13), inset 48px 0px 0px #292929; 439 | } 440 | /* line 122, ../../src/sass/messenger-theme-flat.sass */ 441 | ul.messenger-theme-flat .messenger-message-slot.messenger-first.messenger-last .messenger-message { 442 | -webkit-border-radius: 4px; 443 | -moz-border-radius: 4px; 444 | -ms-border-radius: 4px; 445 | -o-border-radius: 4px; 446 | border-radius: 4px; 447 | -webkit-box-shadow: inset 48px 0px 0px #292929; 448 | -moz-box-shadow: inset 48px 0px 0px #292929; 449 | box-shadow: inset 48px 0px 0px #292929; 450 | } 451 | /* line 126, ../../src/sass/messenger-theme-flat.sass */ 452 | ul.messenger-theme-flat .messenger-spinner { 453 | display: block; 454 | position: absolute; 455 | left: 7px; 456 | top: 50%; 457 | margin-top: -18px; 458 | z-index: 999; 459 | height: 32px; 460 | width: 32px; 461 | z-index: 10; 462 | } 463 | -------------------------------------------------------------------------------- /github2fedmsg/static/bootstrap-3.1.1-fedora/css/bootstrap-theme.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.1.1 (http://getbootstrap.com) 3 | * Copyright 2011-2014 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */ 6 | 7 | .btn-default, 8 | .btn-primary, 9 | .btn-success, 10 | .btn-info, 11 | .btn-warning, 12 | .btn-danger { 13 | text-shadow: 0 -1px 0 rgba(0, 0, 0, .2); 14 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075); 15 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075); 16 | } 17 | .btn-default:active, 18 | .btn-primary:active, 19 | .btn-success:active, 20 | .btn-info:active, 21 | .btn-warning:active, 22 | .btn-danger:active, 23 | .btn-default.active, 24 | .btn-primary.active, 25 | .btn-success.active, 26 | .btn-info.active, 27 | .btn-warning.active, 28 | .btn-danger.active { 29 | -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); 30 | box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); 31 | } 32 | .btn:active, 33 | .btn.active { 34 | background-image: none; 35 | } 36 | .btn-default { 37 | text-shadow: 0 1px 0 #fff; 38 | background-image: -webkit-linear-gradient(top, #fff 0%, #e0e0e0 100%); 39 | background-image: linear-gradient(to bottom, #fff 0%, #e0e0e0 100%); 40 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0); 41 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 42 | background-repeat: repeat-x; 43 | border-color: #dbdbdb; 44 | border-color: #ccc; 45 | } 46 | .btn-default:hover, 47 | .btn-default:focus { 48 | background-color: #e0e0e0; 49 | background-position: 0 -15px; 50 | } 51 | .btn-default:active, 52 | .btn-default.active { 53 | background-color: #e0e0e0; 54 | border-color: #dbdbdb; 55 | } 56 | .btn-primary { 57 | background-image: -webkit-linear-gradient(top, #428bca 0%, #2d6ca2 100%); 58 | background-image: linear-gradient(to bottom, #428bca 0%, #2d6ca2 100%); 59 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff2d6ca2', GradientType=0); 60 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 61 | background-repeat: repeat-x; 62 | border-color: #2b669a; 63 | } 64 | .btn-primary:hover, 65 | .btn-primary:focus { 66 | background-color: #2d6ca2; 67 | background-position: 0 -15px; 68 | } 69 | .btn-primary:active, 70 | .btn-primary.active { 71 | background-color: #2d6ca2; 72 | border-color: #2b669a; 73 | } 74 | .btn-success { 75 | background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%); 76 | background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%); 77 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0); 78 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 79 | background-repeat: repeat-x; 80 | border-color: #3e8f3e; 81 | } 82 | .btn-success:hover, 83 | .btn-success:focus { 84 | background-color: #419641; 85 | background-position: 0 -15px; 86 | } 87 | .btn-success:active, 88 | .btn-success.active { 89 | background-color: #419641; 90 | border-color: #3e8f3e; 91 | } 92 | .btn-info { 93 | background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%); 94 | background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%); 95 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0); 96 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 97 | background-repeat: repeat-x; 98 | border-color: #28a4c9; 99 | } 100 | .btn-info:hover, 101 | .btn-info:focus { 102 | background-color: #2aabd2; 103 | background-position: 0 -15px; 104 | } 105 | .btn-info:active, 106 | .btn-info.active { 107 | background-color: #2aabd2; 108 | border-color: #28a4c9; 109 | } 110 | .btn-warning { 111 | background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%); 112 | background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%); 113 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0); 114 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 115 | background-repeat: repeat-x; 116 | border-color: #e38d13; 117 | } 118 | .btn-warning:hover, 119 | .btn-warning:focus { 120 | background-color: #eb9316; 121 | background-position: 0 -15px; 122 | } 123 | .btn-warning:active, 124 | .btn-warning.active { 125 | background-color: #eb9316; 126 | border-color: #e38d13; 127 | } 128 | .btn-danger { 129 | background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%); 130 | background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%); 131 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0); 132 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 133 | background-repeat: repeat-x; 134 | border-color: #b92c28; 135 | } 136 | .btn-danger:hover, 137 | .btn-danger:focus { 138 | background-color: #c12e2a; 139 | background-position: 0 -15px; 140 | } 141 | .btn-danger:active, 142 | .btn-danger.active { 143 | background-color: #c12e2a; 144 | border-color: #b92c28; 145 | } 146 | .thumbnail, 147 | .img-thumbnail { 148 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075); 149 | box-shadow: 0 1px 2px rgba(0, 0, 0, .075); 150 | } 151 | .dropdown-menu > li > a:hover, 152 | .dropdown-menu > li > a:focus { 153 | background-color: #e8e8e8; 154 | background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 155 | background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%); 156 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0); 157 | background-repeat: repeat-x; 158 | } 159 | .dropdown-menu > .active > a, 160 | .dropdown-menu > .active > a:hover, 161 | .dropdown-menu > .active > a:focus { 162 | background-color: #357ebd; 163 | background-image: -webkit-linear-gradient(top, #428bca 0%, #357ebd 100%); 164 | background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%); 165 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0); 166 | background-repeat: repeat-x; 167 | } 168 | .navbar-default { 169 | background-image: -webkit-linear-gradient(top, #fff 0%, #f8f8f8 100%); 170 | background-image: linear-gradient(to bottom, #fff 0%, #f8f8f8 100%); 171 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0); 172 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 173 | background-repeat: repeat-x; 174 | border-radius: 4px; 175 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075); 176 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075); 177 | } 178 | .navbar-default .navbar-nav > .active > a { 179 | background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f3f3f3 100%); 180 | background-image: linear-gradient(to bottom, #ebebeb 0%, #f3f3f3 100%); 181 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff3f3f3', GradientType=0); 182 | background-repeat: repeat-x; 183 | -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075); 184 | box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075); 185 | } 186 | .navbar-brand, 187 | .navbar-nav > li > a { 188 | text-shadow: 0 1px 0 rgba(255, 255, 255, .25); 189 | } 190 | .navbar-inverse { 191 | background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222 100%); 192 | background-image: linear-gradient(to bottom, #3c3c3c 0%, #222 100%); 193 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0); 194 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 195 | background-repeat: repeat-x; 196 | } 197 | .navbar-inverse .navbar-nav > .active > a { 198 | background-image: -webkit-linear-gradient(top, #222 0%, #282828 100%); 199 | background-image: linear-gradient(to bottom, #222 0%, #282828 100%); 200 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222', endColorstr='#ff282828', GradientType=0); 201 | background-repeat: repeat-x; 202 | -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25); 203 | box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25); 204 | } 205 | .navbar-inverse .navbar-brand, 206 | .navbar-inverse .navbar-nav > li > a { 207 | text-shadow: 0 -1px 0 rgba(0, 0, 0, .25); 208 | } 209 | .navbar-static-top, 210 | .navbar-fixed-top, 211 | .navbar-fixed-bottom { 212 | border-radius: 0; 213 | } 214 | .alert { 215 | text-shadow: 0 1px 0 rgba(255, 255, 255, .2); 216 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05); 217 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05); 218 | } 219 | .alert-success { 220 | background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%); 221 | background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%); 222 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0); 223 | background-repeat: repeat-x; 224 | border-color: #b2dba1; 225 | } 226 | .alert-info { 227 | background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%); 228 | background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%); 229 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0); 230 | background-repeat: repeat-x; 231 | border-color: #9acfea; 232 | } 233 | .alert-warning { 234 | background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%); 235 | background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%); 236 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0); 237 | background-repeat: repeat-x; 238 | border-color: #f5e79e; 239 | } 240 | .alert-danger { 241 | background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%); 242 | background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%); 243 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0); 244 | background-repeat: repeat-x; 245 | border-color: #dca7a7; 246 | } 247 | .progress { 248 | background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%); 249 | background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%); 250 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0); 251 | background-repeat: repeat-x; 252 | } 253 | .progress-bar { 254 | background-image: -webkit-linear-gradient(top, #428bca 0%, #3071a9 100%); 255 | background-image: linear-gradient(to bottom, #428bca 0%, #3071a9 100%); 256 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0); 257 | background-repeat: repeat-x; 258 | } 259 | .progress-bar-success { 260 | background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%); 261 | background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%); 262 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0); 263 | background-repeat: repeat-x; 264 | } 265 | .progress-bar-info { 266 | background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%); 267 | background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%); 268 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0); 269 | background-repeat: repeat-x; 270 | } 271 | .progress-bar-warning { 272 | background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%); 273 | background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%); 274 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0); 275 | background-repeat: repeat-x; 276 | } 277 | .progress-bar-danger { 278 | background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%); 279 | background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%); 280 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0); 281 | background-repeat: repeat-x; 282 | } 283 | .list-group { 284 | border-radius: 4px; 285 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075); 286 | box-shadow: 0 1px 2px rgba(0, 0, 0, .075); 287 | } 288 | .list-group-item.active, 289 | .list-group-item.active:hover, 290 | .list-group-item.active:focus { 291 | text-shadow: 0 -1px 0 #3071a9; 292 | background-image: -webkit-linear-gradient(top, #428bca 0%, #3278b3 100%); 293 | background-image: linear-gradient(to bottom, #428bca 0%, #3278b3 100%); 294 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3278b3', GradientType=0); 295 | background-repeat: repeat-x; 296 | border-color: #3278b3; 297 | } 298 | .panel { 299 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .05); 300 | box-shadow: 0 1px 2px rgba(0, 0, 0, .05); 301 | } 302 | .panel-default > .panel-heading { 303 | background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 304 | background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%); 305 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0); 306 | background-repeat: repeat-x; 307 | } 308 | .panel-primary > .panel-heading { 309 | background-image: -webkit-linear-gradient(top, #428bca 0%, #357ebd 100%); 310 | background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%); 311 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0); 312 | background-repeat: repeat-x; 313 | } 314 | .panel-success > .panel-heading { 315 | background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%); 316 | background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%); 317 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0); 318 | background-repeat: repeat-x; 319 | } 320 | .panel-info > .panel-heading { 321 | background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%); 322 | background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%); 323 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0); 324 | background-repeat: repeat-x; 325 | } 326 | .panel-warning > .panel-heading { 327 | background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%); 328 | background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%); 329 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0); 330 | background-repeat: repeat-x; 331 | } 332 | .panel-danger > .panel-heading { 333 | background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%); 334 | background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%); 335 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0); 336 | background-repeat: repeat-x; 337 | } 338 | .well { 339 | background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%); 340 | background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%); 341 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0); 342 | background-repeat: repeat-x; 343 | border-color: #dcdcdc; 344 | -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1); 345 | box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1); 346 | } 347 | /*# sourceMappingURL=bootstrap-theme.css.map */ 348 | -------------------------------------------------------------------------------- /github2fedmsg/static/messenger-1.4.1/js/messenger.min.js: -------------------------------------------------------------------------------- 1 | /*! messenger 1.4.1 */ 2 | (function(){var e,t=window.Messenger;e=window.Messenger=function(){return e._call.apply(this,arguments)},window.Messenger.noConflict=function(){return window.Messenger=t,e}})(),window.Messenger._=function(){if(window._)return window._;var e=Array.prototype,t=Object.prototype,n=Function.prototype,s=(e.push,e.slice),r=(e.concat,t.toString);t.hasOwnProperty;var o=e.forEach,i=(e.map,e.reduce,e.reduceRight,e.filter),a=(e.every,e.some,e.indexOf,e.lastIndexOf,Array.isArray,Object.keys),l=n.bind,u={},c={},h=u.each=u.forEach=function(e,t,n){if(null!=e)if(o&&e.forEach===o)e.forEach(t,n);else if(e.length===+e.length){for(var s=0,r=e.length;r>s;s++)if(t.call(n,e[s],s,e)===c)return}else for(var i in e)if(u.has(e,i)&&t.call(n,e[i],i,e)===c)return};u.result=function(e,t){if(null==e)return null;var n=e[t];return u.isFunction(n)?n.call(e):n},u.once=function(e){var t,n=!1;return function(){return n?t:(n=!0,t=e.apply(this,arguments),e=null,t)}};var p=0;return u.uniqueId=function(e){var t=++p+"";return e?e+t:t},u.filter=u.select=function(e,t,n){var s=[];return null==e?s:i&&e.filter===i?e.filter(t,n):(h(e,function(e,r,o){t.call(n,e,r,o)&&(s[s.length]=e)}),s)},h(["Arguments","Function","String","Number","Date","RegExp"],function(e){u["is"+e]=function(t){return r.call(t)=="[object "+e+"]"}}),u.defaults=function(e){return h(s.call(arguments,1),function(t){if(t)for(var n in t)null==e[n]&&(e[n]=t[n])}),e},u.extend=function(e){return h(s.call(arguments,1),function(t){if(t)for(var n in t)e[n]=t[n]}),e},u.keys=a||function(e){if(e!==Object(e))throw new TypeError("Invalid object");var t=[];for(var n in e)u.has(e,n)&&(t[t.length]=n);return t},u.bind=function(e,t){if(e.bind===l&&l)return l.apply(e,s.call(arguments,1));var n=s.call(arguments,2);return function(){return e.apply(t,n.concat(s.call(arguments)))}},u.isObject=function(e){return e===Object(e)},u}(),window.Messenger.Events=function(){if(window.Backbone&&Backbone.Events)return Backbone.Events;var e=function(){var e=/\s+/,t=function(t,n,s,r){if(!s)return!0;if("object"==typeof s)for(var o in s)t[n].apply(t,[o,s[o]].concat(r));else{if(!e.test(s))return!0;for(var i=s.split(e),a=0,l=i.length;l>a;a++)t[n].apply(t,[i[a]].concat(r))}},n=function(e,t){var n,s=-1,r=e.length;switch(t.length){case 0:for(;r>++s;)(n=e[s]).callback.call(n.ctx);return;case 1:for(;r>++s;)(n=e[s]).callback.call(n.ctx,t[0]);return;case 2:for(;r>++s;)(n=e[s]).callback.call(n.ctx,t[0],t[1]);return;case 3:for(;r>++s;)(n=e[s]).callback.call(n.ctx,t[0],t[1],t[2]);return;default:for(;r>++s;)(n=e[s]).callback.apply(n.ctx,t)}},s={on:function(e,n,s){if(!t(this,"on",e,[n,s])||!n)return this;this._events||(this._events={});var r=this._events[e]||(this._events[e]=[]);return r.push({callback:n,context:s,ctx:s||this}),this},once:function(e,n,s){if(!t(this,"once",e,[n,s])||!n)return this;var r=this,o=_.once(function(){r.off(e,o),n.apply(this,arguments)});return o._callback=n,this.on(e,o,s),this},off:function(e,n,s){var r,o,i,a,l,u,c,h;if(!this._events||!t(this,"off",e,[n,s]))return this;if(!e&&!n&&!s)return this._events={},this;for(a=e?[e]:_.keys(this._events),l=0,u=a.length;u>l;l++)if(e=a[l],r=this._events[e]){if(i=[],n||s)for(c=0,h=r.length;h>c;c++)o=r[c],(n&&n!==o.callback&&n!==o.callback._callback||s&&s!==o.context)&&i.push(o);this._events[e]=i}return this},trigger:function(e){if(!this._events)return this;var s=Array.prototype.slice.call(arguments,1);if(!t(this,"trigger",e,s))return this;var r=this._events[e],o=this._events.all;return r&&n(r,s),o&&n(o,arguments),this},listenTo:function(e,t,n){var s=this._listeners||(this._listeners={}),r=e._listenerId||(e._listenerId=_.uniqueId("l"));return s[r]=e,e.on(t,"object"==typeof t?this:n,this),this},stopListening:function(e,t,n){var s=this._listeners;if(s){if(e)e.off(t,"object"==typeof t?this:n,this),t||n||delete s[e._listenerId];else{"object"==typeof t&&(n=this);for(var r in s)s[r].off(t,n,this);this._listeners={}}return this}}};return s.bind=s.on,s.unbind=s.off,s};return e()}(),function(){var e,t,n,s,r,o,i,a,l,u,c,h={}.hasOwnProperty,p=function(e,t){function n(){this.constructor=e}for(var s in t)h.call(t,s)&&(e[s]=t[s]);return n.prototype=t.prototype,e.prototype=new n,e.__super__=t.prototype,e},d=[].slice,f=[].indexOf||function(e){for(var t=0,n=this.length;n>t;t++)if(t in this&&this[t]===e)return t;return-1};e=jQuery,o=null!=(l=window._)?l:window.Messenger._,s=null!=(u="undefined"!=typeof Backbone&&null!==Backbone?Backbone.Events:void 0)?u:window.Messenger.Events,n=function(){function t(t){e.extend(this,s),o.isObject(t)&&(t.el&&this.setElement(t.el),this.model=t.model),this.initialize.apply(this,arguments)}return t.prototype.setElement=function(t){return this.$el=e(t),this.el=this.$el[0]},t.prototype.delegateEvents=function(e){var t,n,s,r,i,a,l;if(e||(e=o.result(this,"events"))){this.undelegateEvents(),t=/^(\S+)\s*(.*)$/,l=[];for(s in e){if(i=e[s],o.isFunction(i)||(i=this[e[s]]),!i)throw Error('Method "'+e[s]+'" does not exist');r=s.match(t),n=r[1],a=r[2],i=o.bind(i,this),n+=".delegateEvents"+this.cid,""===a?l.push(this.jqon(n,i)):l.push(this.jqon(n,a,i))}return l}},t.prototype.jqon=function(e,t,n){var s;return null!=this.$el.on?(s=this.$el).on.apply(s,arguments):(null==n&&(n=t,t=void 0),null!=t?this.$el.delegate(t,e,n):this.$el.bind(e,n))},t.prototype.jqoff=function(e){var t;return null!=this.$el.off?(t=this.$el).off.apply(t,arguments):(this.$el.undelegate(),this.$el.unbind(e))},t.prototype.undelegateEvents=function(){return this.jqoff(".delegateEvents"+this.cid)},t.prototype.remove=function(){return this.undelegateEvents(),this.$el.remove()},t}(),i=function(t){function n(){return n.__super__.constructor.apply(this,arguments)}return p(n,t),n.prototype.defaults={hideAfter:10,scroll:!0,closeButtonText:"×"},n.prototype.initialize=function(t){return null==t&&(t={}),this.shown=!1,this.rendered=!1,this.messenger=t.messenger,this.options=e.extend({},this.options,t,this.defaults)},n.prototype.show=function(){var e;return this.rendered||this.render(),this.$message.removeClass("messenger-hidden"),e=this.shown,this.shown=!0,e?void 0:this.trigger("show")},n.prototype.hide=function(){var e;if(this.rendered)return this.$message.addClass("messenger-hidden"),e=this.shown,this.shown=!1,e?this.trigger("hide"):void 0},n.prototype.cancel=function(){return this.hide()},n.prototype.update=function(t){var n,s=this;return o.isString(t)&&(t={message:t}),e.extend(this.options,t),this.lastUpdate=new Date,this.rendered=!1,this.events=null!=(n=this.options.events)?n:{},this.render(),this.actionsToEvents(),this.delegateEvents(),this.checkClickable(),this.options.hideAfter?(this.$message.addClass("messenger-will-hide-after"),null!=this._hideTimeout&&clearTimeout(this._hideTimeout),this._hideTimeout=setTimeout(function(){return s.hide()},1e3*this.options.hideAfter)):this.$message.removeClass("messenger-will-hide-after"),this.options.hideOnNavigate?(this.$message.addClass("messenger-will-hide-on-navigate"),null!=("undefined"!=typeof Backbone&&null!==Backbone?Backbone.history:void 0)&&Backbone.history.on("route",function(){return s.hide()})):this.$message.removeClass("messenger-will-hide-on-navigate"),this.trigger("update",this)},n.prototype.scrollTo=function(){return this.options.scroll?e.scrollTo(this.$el,{duration:400,offset:{left:0,top:-20}}):void 0},n.prototype.timeSinceUpdate=function(){return this.lastUpdate?new Date-this.lastUpdate:null},n.prototype.actionsToEvents=function(){var e,t,n,s,r=this;n=this.options.actions,s=[];for(t in n)e=n[t],s.push(this.events['click [data-action="'+t+'"] a']=function(e){return function(n){return n.preventDefault(),n.stopPropagation(),r.trigger("action:"+t,e,n),e.action.call(r,n,r)}}(e));return s},n.prototype.checkClickable=function(){var e,t,n,s;n=this.events,s=[];for(t in n)e=n[t],"click"===t?s.push(this.$message.addClass("messenger-clickable")):s.push(void 0);return s},n.prototype.undelegateEvents=function(){var e;return n.__super__.undelegateEvents.apply(this,arguments),null!=(e=this.$message)?e.removeClass("messenger-clickable"):void 0},n.prototype.parseActions=function(){var t,n,s,r,o,i;n=[],o=this.options.actions;for(r in o)t=o[r],s=e.extend({},t),s.name=r,null==(i=s.label)&&(s.label=r),n.push(s);return n},n.prototype.template=function(t){var n,s,r,o,i,a,l,u,c,h,p=this;for(i=e("<div class='messenger-message message alert "+t.type+" message-"+t.type+" alert-"+t.type+"'>"),t.showCloseButton&&(r=e('<button type="button" class="messenger-close" data-dismiss="alert">'),r.html(t.closeButtonText),r.click(function(){return p.cancel(),!0}),i.append(r)),a=e('<div class="messenger-message-inner">'+t.message+"</div>"),i.append(a),t.actions.length&&(s=e('<div class="messenger-actions">')),h=t.actions,u=0,c=h.length;c>u;u++)l=h[u],n=e("<span>"),n.attr("data-action",""+l.name),o=e("<a>"),o.html(l.label),n.append(e('<span class="messenger-phrase">')),n.append(o),s.append(n);return i.append(s),i},n.prototype.render=function(){var t;if(!this.rendered)return this._hasSlot||(this.setElement(this.messenger._reserveMessageSlot(this)),this._hasSlot=!0),t=e.extend({},this.options,{actions:this.parseActions()}),this.$message=e(this.template(t)),this.$el.html(this.$message),this.shown=!0,this.rendered=!0,this.trigger("render")},n}(n),r=function(e){function t(){return t.__super__.constructor.apply(this,arguments)}return p(t,e),t.prototype.initialize=function(){return t.__super__.initialize.apply(this,arguments),this._timers={}},t.prototype.cancel=function(){return this.clearTimers(),this.hide(),null!=this._actionInstance&&null!=this._actionInstance.abort?this._actionInstance.abort():void 0},t.prototype.clearTimers=function(){var e,t,n,s;n=this._timers;for(e in n)t=n[e],clearTimeout(t);return this._timers={},null!=(s=this.$message)?s.removeClass("messenger-retry-soon messenger-retry-later"):void 0},t.prototype.render=function(){var e,n,s,r;t.__super__.render.apply(this,arguments),this.clearTimers(),s=this.options.actions,r=[];for(n in s)e=s[n],e.auto?r.push(this.startCountdown(n,e)):r.push(void 0);return r},t.prototype.renderPhrase=function(e,t){var n;return n=e.phrase.replace("TIME",this.formatTime(t))},t.prototype.formatTime=function(e){var t;return t=function(e,t){return e=Math.floor(e),1!==e&&(t+="s"),"in "+e+" "+t},0===Math.floor(e)?"now...":60>e?t(e,"second"):(e/=60,60>e?t(e,"minute"):(e/=60,t(e,"hour")))},t.prototype.startCountdown=function(e,t){var n,s,r,o,i=this;if(null==this._timers[e])return n=this.$message.find("[data-action='"+e+"'] .messenger-phrase"),s=null!=(o=t.delay)?o:3,10>=s?(this.$message.removeClass("messenger-retry-later"),this.$message.addClass("messenger-retry-soon")):(this.$message.removeClass("messenger-retry-soon"),this.$message.addClass("messenger-retry-later")),r=function(){var o;return n.text(i.renderPhrase(t,s)),s>0?(o=Math.min(s,1),s-=o,i._timers[e]=setTimeout(r,1e3*o)):(i.$message.removeClass("messenger-retry-soon messenger-retry-later"),delete i._timers[e],t.action())},r()},t}(i),a=function(t){function n(){return n.__super__.constructor.apply(this,arguments)}return p(n,t),n.prototype.tagName="ul",n.prototype.className="messenger",n.prototype.messageDefaults={type:"info"},n.prototype.initialize=function(t){return this.options=null!=t?t:{},this.history=[],this.messageDefaults=e.extend({},this.messageDefaults,this.options.messageDefaults)},n.prototype.render=function(){return this.updateMessageSlotClasses()},n.prototype.findById=function(e){return o.filter(this.history,function(t){return t.msg.options.id===e})},n.prototype._reserveMessageSlot=function(t){var n,s,r=this;for(n=e("<li>"),n.addClass("messenger-message-slot"),this.$el.prepend(n),this.history.push({msg:t,$slot:n}),this._enforceIdConstraint(t),t.on("update",function(){return r._enforceIdConstraint(t)});this.options.maxMessages&&this.history.length>this.options.maxMessages;)s=this.history.shift(),s.msg.remove(),s.$slot.remove();return n},n.prototype._enforceIdConstraint=function(e){var t,n,s,r,o;if(null!=e.options.id)for(o=this.history,n=0,s=o.length;s>n;n++)if(t=o[n],r=t.msg,null!=r.options.id&&r.options.id===e.options.id&&e!==r){if(e.options.singleton)return e.hide(),void 0;r.hide()}},n.prototype.newMessage=function(e){var t,n,s,o,a=this;return null==e&&(e={}),e.messenger=this,i=null!=(n=null!=(s=Messenger.themes[null!=(o=e.theme)?o:this.options.theme])?s.Message:void 0)?n:r,t=new i(e),t.on("show",function(){return e.scrollTo&&"fixed"!==a.$el.css("position")?t.scrollTo():void 0}),t.on("hide show render",this.updateMessageSlotClasses,this),t},n.prototype.updateMessageSlotClasses=function(){var e,t,n,s,r,o,i;for(s=!0,t=null,e=!1,i=this.history,r=0,o=i.length;o>r;r++)n=i[r],n.$slot.removeClass("messenger-first messenger-last messenger-shown"),n.msg.shown&&n.msg.rendered&&(n.$slot.addClass("messenger-shown"),e=!0,t=n,s&&(s=!1,n.$slot.addClass("messenger-first")));return null!=t&&t.$slot.addClass("messenger-last"),this.$el[""+(e?"remove":"add")+"Class"]("messenger-empty")},n.prototype.hideAll=function(){var e,t,n,s,r;for(s=this.history,r=[],t=0,n=s.length;n>t;t++)e=s[t],r.push(e.msg.hide());return r},n.prototype.post=function(t){var n;return o.isString(t)&&(t={message:t}),t=e.extend(!0,{},this.messageDefaults,t),n=this.newMessage(t),n.update(t),n},n}(n),t=function(t){function n(){return n.__super__.constructor.apply(this,arguments)}return p(n,t),n.prototype.doDefaults={progressMessage:null,successMessage:null,errorMessage:"Error connecting to the server.",showSuccessWithoutError:!0,retry:{auto:!0,allow:!0},action:e.ajax},n.prototype.hookBackboneAjax=function(t){var n,s=this;if(null==t&&(t={}),null==window.Backbone)throw"Expected Backbone to be defined";return t=o.defaults(t,{id:"BACKBONE_ACTION",errorMessage:!1,successMessage:"Request completed successfully.",showSuccessWithoutError:!1}),n=function(e){var n;return n=o.extend({},t,e.messenger),s["do"](n,e)},null!=Backbone.ajax?(Backbone.ajax._withoutMessenger&&(Backbone.ajax=Backbone.ajax._withoutMessenger),(null==t.action||t.action===this.doDefaults.action)&&(t.action=Backbone.ajax),n._withoutMessenger=Backbone.ajax,Backbone.ajax=n):Backbone.sync=o.wrap(Backbone.sync,function(){var t,s,r;return r=arguments[0],t=arguments.length>=2?d.call(arguments,1):[],s=e.ajax,e.ajax=n,r.call.apply(r,[this].concat(d.call(t))),e.ajax=s})},n.prototype._getHandlerResponse=function(e){return e===!1?!1:e===!0||null==e?!0:e},n.prototype._parseEvents=function(e){var t,n,s,r,o,i,a;null==e&&(e={}),o={};for(r in e)s=e[r],n=r.indexOf(" "),i=r.substring(0,n),t=r.substring(n+1),null==(a=o[i])&&(o[i]={}),o[i][t]=s;return o},n.prototype._normalizeResponse=function(){var e,t,n,s,r,i,a;for(n=arguments.length>=1?d.call(arguments,0):[],s=null,r=null,e=null,i=0,a=n.length;a>i;i++)t=n[i],"success"===t||"timeout"===t||"abort"===t?s=t:null!=(null!=t?t.readyState:void 0)&&null!=(null!=t?t.responseText:void 0)?r=t:o.isObject(t)&&(e=t);return[s,e,r]},n.prototype.run=function(){var t,n,s,r,i,a,l,u,c,h,p,g=this;if(a=arguments[0],c=arguments[1],t=arguments.length>=3?d.call(arguments,2):[],null==c&&(c={}),a=e.extend(!0,{},this.messageDefaults,this.doDefaults,null!=a?a:{}),n=this._parseEvents(a.events),s=function(e,t){var n;return n=a[e+"Message"],o.isFunction(n)?n.call(g,e,t):n},l=null!=(p=a.messageInstance)?p:this.newMessage(a),null!=a.id&&(l.options.id=a.id),null!=a.progressMessage&&l.update(e.extend({},a,{message:s("progress",null),type:"info"})),i={},o.each(["error","success"],function(r){var u;return u=c[r],i[r]=function(){var i,h,p,m,y,v,_,w,b,x,M,C,k,$,E;return v=arguments.length>=1?d.call(arguments,0):[],b=g._normalizeResponse.apply(g,v),y=b[0],i=b[1],w=b[2],"success"===r&&null==l.errorCount&&a.showSuccessWithoutError===!1&&(a.successMessage=null),"error"===r&&(null==(x=a.errorCount)&&(a.errorCount=0),a.errorCount+=1),p=a.returnsPromise?v[0]:"function"==typeof u?u.apply(null,v):void 0,_=g._getHandlerResponse(p),o.isString(_)&&(_={message:_}),"error"!==r||0!==(null!=w?w.status:void 0)&&"abort"!==y?"error"===r&&null!=a.ignoredErrorCodes&&(M=null!=w?w.status:void 0,f.call(a.ignoredErrorCodes,M)>=0)?(l.hide(),void 0):(h={message:s(r,w),type:r,events:null!=(C=n[r])?C:{},hideOnNavigate:"success"===r},m=e.extend({},a,h,_),"number"==typeof(null!=(k=m.retry)?k.allow:void 0)&&m.retry.allow--,"error"===r&&(null!=w?w.status:void 0)>=500&&(null!=($=m.retry)?$.allow:void 0)?(null==m.retry.delay&&(m.retry.delay=4>m.errorCount?10:300),m.hideAfter&&(null==(E=m._hideAfter)&&(m._hideAfter=m.hideAfter),m.hideAfter=m._hideAfter+m.retry.delay),m._retryActions=!0,m.actions={retry:{label:"retry now",phrase:"Retrying TIME",auto:m.retry.auto,delay:m.retry.delay,action:function(){return m.messageInstance=l,setTimeout(function(){return g["do"].apply(g,[m,c].concat(d.call(t)))},0)}},cancel:{action:function(){return l.cancel()}}}):m._retryActions&&(delete m.actions.retry,delete m.actions.cancel,delete a._retryActions),l.update(m),_&&m.message?(Messenger(o.extend({},g.options,{instance:g})),l.show()):l.hide()):(l.hide(),void 0)}}),!a.returnsPromise)for(h in i)r=i[h],u=c[h],c[h]=r;return l._actionInstance=a.action.apply(a,[c].concat(d.call(t))),a.returnsPromise&&l._actionInstance.then(i.success,i.error),l},n.prototype["do"]=n.prototype.run,n.prototype.ajax=function(){var t,n;return n=arguments[0],t=arguments.length>=2?d.call(arguments,1):[],n.action=e.ajax,this.run.apply(this,[n].concat(d.call(t)))},n.prototype.expectPromise=function(e,t){return t=o.extend({},t,{action:e,returnsPromise:!0}),this.run(t)},n.prototype.error=function(e){return null==e&&(e={}),"string"==typeof e&&(e={message:e}),e.type="error",this.post(e)},n.prototype.info=function(e){return null==e&&(e={}),"string"==typeof e&&(e={message:e}),e.type="info",this.post(e)},n.prototype.success=function(e){return null==e&&(e={}),"string"==typeof e&&(e={message:e}),e.type="success",this.post(e)},n}(a),e.fn.messenger=function(){var n,s,r,i,l,u,c,h;return r=arguments[0],s=arguments.length>=2?d.call(arguments,1):[],null==r&&(r={}),n=this,null!=r&&o.isString(r)?(h=n.data("messenger"))[r].apply(h,s):(l=r,null==n.data("messenger")&&(a=null!=(u=null!=(c=Messenger.themes[l.theme])?c.Messenger:void 0)?u:t,n.data("messenger",i=new a(e.extend({el:n},l))),i.render()),n.data("messenger"))},window.Messenger._call=function(t){var n,s,r,o,i,a,l,u,c,h,p;if(a={extraClasses:"messenger-fixed messenger-on-bottom messenger-on-right",theme:"future",maxMessages:9,parentLocations:["body"]},t=e.extend(a,e._messengerDefaults,Messenger.options,t),null!=t.theme&&(t.extraClasses+=" messenger-theme-"+t.theme),l=t.instance||Messenger.instance,null==t.instance){for(c=t.parentLocations,s=null,r=null,h=0,p=c.length;p>h;h++)if(u=c[h],s=e(u),s.length){o=u;break}l?e(l._location).is(e(o))||(l.$el.detach(),s.prepend(l.$el)):(n=e("<ul>"),s.prepend(n),l=n.messenger(t),l._location=o,Messenger.instance=l)}return null!=l._addedClasses&&l.$el.removeClass(l._addedClasses),l.$el.addClass(i=""+l.className+" "+t.extraClasses),l._addedClasses=i,l},e.extend(Messenger,{Message:r,Messenger:t,themes:null!=(c=Messenger.themes)?c:{}}),e.globalMessenger=window.Messenger=Messenger}.call(this); -------------------------------------------------------------------------------- /github2fedmsg/static/messenger-1.4.1/css/messenger-theme-future.css: -------------------------------------------------------------------------------- 1 | @-webkit-keyframes ui-spinner-rotate-right { 2 | /* line 64, ../../src/sass/messenger-spinner.scss */ 3 | 0% { 4 | -webkit-transform: rotate(0deg); 5 | } 6 | 7 | /* line 65, ../../src/sass/messenger-spinner.scss */ 8 | 25% { 9 | -webkit-transform: rotate(180deg); 10 | } 11 | 12 | /* line 66, ../../src/sass/messenger-spinner.scss */ 13 | 50% { 14 | -webkit-transform: rotate(180deg); 15 | } 16 | 17 | /* line 67, ../../src/sass/messenger-spinner.scss */ 18 | 75% { 19 | -webkit-transform: rotate(360deg); 20 | } 21 | 22 | /* line 68, ../../src/sass/messenger-spinner.scss */ 23 | 100% { 24 | -webkit-transform: rotate(360deg); 25 | } 26 | } 27 | 28 | @-webkit-keyframes ui-spinner-rotate-left { 29 | /* line 72, ../../src/sass/messenger-spinner.scss */ 30 | 0% { 31 | -webkit-transform: rotate(0deg); 32 | } 33 | 34 | /* line 73, ../../src/sass/messenger-spinner.scss */ 35 | 25% { 36 | -webkit-transform: rotate(0deg); 37 | } 38 | 39 | /* line 74, ../../src/sass/messenger-spinner.scss */ 40 | 50% { 41 | -webkit-transform: rotate(180deg); 42 | } 43 | 44 | /* line 75, ../../src/sass/messenger-spinner.scss */ 45 | 75% { 46 | -webkit-transform: rotate(180deg); 47 | } 48 | 49 | /* line 76, ../../src/sass/messenger-spinner.scss */ 50 | 100% { 51 | -webkit-transform: rotate(360deg); 52 | } 53 | } 54 | 55 | @-moz-keyframes ui-spinner-rotate-right { 56 | /* line 80, ../../src/sass/messenger-spinner.scss */ 57 | 0% { 58 | -moz-transform: rotate(0deg); 59 | } 60 | 61 | /* line 81, ../../src/sass/messenger-spinner.scss */ 62 | 25% { 63 | -moz-transform: rotate(180deg); 64 | } 65 | 66 | /* line 82, ../../src/sass/messenger-spinner.scss */ 67 | 50% { 68 | -moz-transform: rotate(180deg); 69 | } 70 | 71 | /* line 83, ../../src/sass/messenger-spinner.scss */ 72 | 75% { 73 | -moz-transform: rotate(360deg); 74 | } 75 | 76 | /* line 84, ../../src/sass/messenger-spinner.scss */ 77 | 100% { 78 | -moz-transform: rotate(360deg); 79 | } 80 | } 81 | 82 | @-moz-keyframes ui-spinner-rotate-left { 83 | /* line 88, ../../src/sass/messenger-spinner.scss */ 84 | 0% { 85 | -moz-transform: rotate(0deg); 86 | } 87 | 88 | /* line 89, ../../src/sass/messenger-spinner.scss */ 89 | 25% { 90 | -moz-transform: rotate(0deg); 91 | } 92 | 93 | /* line 90, ../../src/sass/messenger-spinner.scss */ 94 | 50% { 95 | -moz-transform: rotate(180deg); 96 | } 97 | 98 | /* line 91, ../../src/sass/messenger-spinner.scss */ 99 | 75% { 100 | -moz-transform: rotate(180deg); 101 | } 102 | 103 | /* line 92, ../../src/sass/messenger-spinner.scss */ 104 | 100% { 105 | -moz-transform: rotate(360deg); 106 | } 107 | } 108 | 109 | @keyframes ui-spinner-rotate-right { 110 | /* line 96, ../../src/sass/messenger-spinner.scss */ 111 | 0% { 112 | transform: rotate(0deg); 113 | } 114 | 115 | /* line 97, ../../src/sass/messenger-spinner.scss */ 116 | 25% { 117 | transform: rotate(180deg); 118 | } 119 | 120 | /* line 98, ../../src/sass/messenger-spinner.scss */ 121 | 50% { 122 | transform: rotate(180deg); 123 | } 124 | 125 | /* line 99, ../../src/sass/messenger-spinner.scss */ 126 | 75% { 127 | transform: rotate(360deg); 128 | } 129 | 130 | /* line 100, ../../src/sass/messenger-spinner.scss */ 131 | 100% { 132 | transform: rotate(360deg); 133 | } 134 | } 135 | 136 | @keyframes ui-spinner-rotate-left { 137 | /* line 104, ../../src/sass/messenger-spinner.scss */ 138 | 0% { 139 | transform: rotate(0deg); 140 | } 141 | 142 | /* line 105, ../../src/sass/messenger-spinner.scss */ 143 | 25% { 144 | transform: rotate(0deg); 145 | } 146 | 147 | /* line 106, ../../src/sass/messenger-spinner.scss */ 148 | 50% { 149 | transform: rotate(180deg); 150 | } 151 | 152 | /* line 107, ../../src/sass/messenger-spinner.scss */ 153 | 75% { 154 | transform: rotate(180deg); 155 | } 156 | 157 | /* line 108, ../../src/sass/messenger-spinner.scss */ 158 | 100% { 159 | transform: rotate(360deg); 160 | } 161 | } 162 | 163 | /* line 116, ../../src/sass/messenger-spinner.scss */ 164 | .messenger-spinner { 165 | position: relative; 166 | border-radius: 100%; 167 | } 168 | /* line 120, ../../src/sass/messenger-spinner.scss */ 169 | ul.messenger.messenger-spinner-active .messenger-spinner .messenger-spinner { 170 | display: block; 171 | } 172 | /* line 124, ../../src/sass/messenger-spinner.scss */ 173 | .messenger-spinner .messenger-spinner-side { 174 | width: 50%; 175 | height: 100%; 176 | overflow: hidden; 177 | position: absolute; 178 | } 179 | /* line 130, ../../src/sass/messenger-spinner.scss */ 180 | .messenger-spinner .messenger-spinner-side .messenger-spinner-fill { 181 | border-radius: 999px; 182 | position: absolute; 183 | width: 100%; 184 | height: 100%; 185 | -webkit-animation-iteration-count: infinite; 186 | -moz-animation-iteration-count: infinite; 187 | -ms-animation-iteration-count: infinite; 188 | -o-animation-iteration-count: infinite; 189 | animation-iteration-count: infinite; 190 | -webkit-animation-timing-function: linear; 191 | -moz-animation-timing-function: linear; 192 | -ms-animation-timing-function: linear; 193 | -o-animation-timing-function: linear; 194 | animation-timing-function: linear; 195 | } 196 | /* line 140, ../../src/sass/messenger-spinner.scss */ 197 | .messenger-spinner .messenger-spinner-side-left { 198 | left: 0; 199 | } 200 | /* line 143, ../../src/sass/messenger-spinner.scss */ 201 | .messenger-spinner .messenger-spinner-side-left .messenger-spinner-fill { 202 | left: 100%; 203 | border-top-left-radius: 0; 204 | border-bottom-left-radius: 0; 205 | -webkit-animation-name: ui-spinner-rotate-left; 206 | -moz-animation-name: ui-spinner-rotate-left; 207 | -ms-animation-name: ui-spinner-rotate-left; 208 | -o-animation-name: ui-spinner-rotate-left; 209 | animation-name: ui-spinner-rotate-left; 210 | -webkit-transform-origin: 0 50%; 211 | -moz-transform-origin: 0 50%; 212 | -ms-transform-origin: 0 50%; 213 | -o-transform-origin: 0 50%; 214 | transform-origin: 0 50%; 215 | } 216 | /* line 152, ../../src/sass/messenger-spinner.scss */ 217 | .messenger-spinner .messenger-spinner-side-right { 218 | left: 50%; 219 | } 220 | /* line 155, ../../src/sass/messenger-spinner.scss */ 221 | .messenger-spinner .messenger-spinner-side-right .messenger-spinner-fill { 222 | left: -100%; 223 | border-top-right-radius: 0; 224 | border-bottom-right-radius: 0; 225 | -webkit-animation-name: ui-spinner-rotate-right; 226 | -moz-animation-name: ui-spinner-rotate-right; 227 | -ms-animation-name: ui-spinner-rotate-right; 228 | -o-animation-name: ui-spinner-rotate-right; 229 | animation-name: ui-spinner-rotate-right; 230 | -webkit-transform-origin: 100% 50%; 231 | -moz-transform-origin: 100% 50%; 232 | -ms-transform-origin: 100% 50%; 233 | -o-transform-origin: 100% 50%; 234 | transform-origin: 100% 50%; 235 | } 236 | 237 | /* line 15, ../../src/sass/messenger-theme-future.sass */ 238 | ul.messenger-theme-future { 239 | -webkit-box-shadow: inset 0px 1px rgba(255, 255, 255, 0.24), 0px 1px 5px rgba(0, 0, 0, 0.6); 240 | -moz-box-shadow: inset 0px 1px rgba(255, 255, 255, 0.24), 0px 1px 5px rgba(0, 0, 0, 0.6); 241 | box-shadow: inset 0px 1px rgba(255, 255, 255, 0.24), 0px 1px 5px rgba(0, 0, 0, 0.6); 242 | -webkit-border-radius: 4px; 243 | -moz-border-radius: 4px; 244 | -ms-border-radius: 4px; 245 | -o-border-radius: 4px; 246 | border-radius: 4px; 247 | -moz-user-select: none; 248 | -webkit-user-select: none; 249 | -o-user-select: none; 250 | user-select: none; 251 | background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #5c5b5b), color-stop(100%, #353535)); 252 | background-image: -webkit-linear-gradient(#5c5b5b, #353535); 253 | background-image: -moz-linear-gradient(#5c5b5b, #353535); 254 | background-image: -o-linear-gradient(#5c5b5b, #353535); 255 | background-image: linear-gradient(#5c5b5b, #353535); 256 | background-color: #5c5b5b; 257 | border: 1px solid rgba(0, 0, 0, 0.5); 258 | } 259 | /* line 23, ../../src/sass/messenger-theme-future.sass */ 260 | ul.messenger-theme-future .messenger-message { 261 | -webkit-box-shadow: inset 0px 1px rgba(255, 255, 255, 0.13), inset 0px -1px rgba(0, 0, 0, 0.23), inset 48px 0px 0px rgba(0, 0, 0, 0.3), inset 46px 0px 0px rgba(255, 255, 255, 0.07); 262 | -moz-box-shadow: inset 0px 1px rgba(255, 255, 255, 0.13), inset 0px -1px rgba(0, 0, 0, 0.23), inset 48px 0px 0px rgba(0, 0, 0, 0.3), inset 46px 0px 0px rgba(255, 255, 255, 0.07); 263 | box-shadow: inset 0px 1px rgba(255, 255, 255, 0.13), inset 0px -1px rgba(0, 0, 0, 0.23), inset 48px 0px 0px rgba(0, 0, 0, 0.3), inset 46px 0px 0px rgba(255, 255, 255, 0.07); 264 | -webkit-border-radius: 0px; 265 | -moz-border-radius: 0px; 266 | -ms-border-radius: 0px; 267 | -o-border-radius: 0px; 268 | border-radius: 0px; 269 | position: relative; 270 | border: 0px; 271 | margin-bottom: 0px; 272 | font-size: 13px; 273 | background: transparent; 274 | color: #f0f0f0; 275 | text-shadow: 0px 1px #111111; 276 | font-weight: 500; 277 | padding: 10px 30px 13px 65px; 278 | } 279 | /* line 36, ../../src/sass/messenger-theme-future.sass */ 280 | ul.messenger-theme-future .messenger-message a { 281 | color: #5599ff; 282 | } 283 | /* line 39, ../../src/sass/messenger-theme-future.sass */ 284 | ul.messenger-theme-future .messenger-message .messenger-close { 285 | position: absolute; 286 | top: 0px; 287 | right: 0px; 288 | color: #888888; 289 | text-shadow: 0px 1px black; 290 | opacity: 1; 291 | font-weight: bold; 292 | display: block; 293 | font-size: 20px; 294 | line-height: 20px; 295 | padding: 8px 10px 7px 7px; 296 | cursor: pointer; 297 | background: transparent; 298 | border: 0; 299 | -webkit-appearance: none; 300 | } 301 | /* line 56, ../../src/sass/messenger-theme-future.sass */ 302 | ul.messenger-theme-future .messenger-message .messenger-close:hover { 303 | color: #bbbbbb; 304 | } 305 | /* line 59, ../../src/sass/messenger-theme-future.sass */ 306 | ul.messenger-theme-future .messenger-message .messenger-close:active { 307 | color: #777777; 308 | } 309 | /* line 62, ../../src/sass/messenger-theme-future.sass */ 310 | ul.messenger-theme-future .messenger-message .messenger-actions { 311 | float: none; 312 | margin-top: 10px; 313 | } 314 | /* line 66, ../../src/sass/messenger-theme-future.sass */ 315 | ul.messenger-theme-future .messenger-message .messenger-actions a { 316 | -webkit-box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.2), inset 0px 1px rgba(255, 255, 255, 0.1); 317 | -moz-box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.2), inset 0px 1px rgba(255, 255, 255, 0.1); 318 | box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.2), inset 0px 1px rgba(255, 255, 255, 0.1); 319 | -webkit-border-radius: 4px; 320 | -moz-border-radius: 4px; 321 | -ms-border-radius: 4px; 322 | -o-border-radius: 4px; 323 | border-radius: 4px; 324 | text-decoration: none; 325 | display: inline-block; 326 | padding: 10px; 327 | color: #aaaaaa; 328 | text-shadow: 0px 1px #222222; 329 | margin-right: 10px; 330 | padding: 3px 10px 5px; 331 | text-transform: capitalize; 332 | } 333 | /* line 78, ../../src/sass/messenger-theme-future.sass */ 334 | ul.messenger-theme-future .messenger-message .messenger-actions a:hover { 335 | -webkit-box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.2), inset 0px 1px rgba(255, 255, 255, 0.2); 336 | -moz-box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.2), inset 0px 1px rgba(255, 255, 255, 0.2); 337 | box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.2), inset 0px 1px rgba(255, 255, 255, 0.2); 338 | color: #f0f0f0; 339 | } 340 | /* line 82, ../../src/sass/messenger-theme-future.sass */ 341 | ul.messenger-theme-future .messenger-message .messenger-actions a:active { 342 | -webkit-box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.28), inset 0px 1px rgba(0, 0, 0, 0.1); 343 | -moz-box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.28), inset 0px 1px rgba(0, 0, 0, 0.1); 344 | box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.28), inset 0px 1px rgba(0, 0, 0, 0.1); 345 | background: rgba(0, 0, 0, 0.04); 346 | color: #aaaaaa; 347 | } 348 | /* line 87, ../../src/sass/messenger-theme-future.sass */ 349 | ul.messenger-theme-future .messenger-message .messenger-actions .messenger-phrase { 350 | display: none; 351 | } 352 | /* line 90, ../../src/sass/messenger-theme-future.sass */ 353 | ul.messenger-theme-future .messenger-message .messenger-message-inner:before { 354 | -webkit-box-shadow: inset 0px 1px 3px rgba(0, 0, 0, 0.6), 0px 0px 0px 1px rgba(0, 0, 0, 0.2); 355 | -moz-box-shadow: inset 0px 1px 3px rgba(0, 0, 0, 0.6), 0px 0px 0px 1px rgba(0, 0, 0, 0.2); 356 | box-shadow: inset 0px 1px 3px rgba(0, 0, 0, 0.6), 0px 0px 0px 1px rgba(0, 0, 0, 0.2); 357 | -webkit-border-radius: 50%; 358 | -moz-border-radius: 50%; 359 | -ms-border-radius: 50%; 360 | -o-border-radius: 50%; 361 | border-radius: 50%; 362 | position: absolute; 363 | left: 17px; 364 | display: block; 365 | content: " "; 366 | top: 50%; 367 | margin-top: -8px; 368 | height: 13px; 369 | width: 13px; 370 | z-index: 20; 371 | } 372 | /* line 103, ../../src/sass/messenger-theme-future.sass */ 373 | ul.messenger-theme-future .messenger-message.alert-success .messenger-message-inner:before { 374 | background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #5fca4a), color-stop(100%, #098d38)); 375 | background-image: -webkit-linear-gradient(top, #5fca4a, #098d38); 376 | background-image: -moz-linear-gradient(top, #5fca4a, #098d38); 377 | background-image: -o-linear-gradient(top, #5fca4a, #098d38); 378 | background-image: linear-gradient(top, #5fca4a, #098d38); 379 | background-color: #5fca4a; 380 | } 381 | /* line 107, ../../src/sass/messenger-theme-future.sass */ 382 | ul.messenger-theme-future .messenger-message.alert-info .messenger-message-inner:before { 383 | background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #61c4b8), color-stop(100%, #1992a3)); 384 | background-image: -webkit-linear-gradient(top, #61c4b8, #1992a3); 385 | background-image: -moz-linear-gradient(top, #61c4b8, #1992a3); 386 | background-image: -o-linear-gradient(top, #61c4b8, #1992a3); 387 | background-image: linear-gradient(top, #61c4b8, #1992a3); 388 | background-color: #61c4b8; 389 | } 390 | /* line 113, ../../src/sass/messenger-theme-future.sass */ 391 | ul.messenger-theme-future .messenger-message.alert-error .messenger-message-inner:before { 392 | background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #dd6a45), color-stop(100%, #91361a)); 393 | background-image: -webkit-linear-gradient(top, #dd6a45, #91361a); 394 | background-image: -moz-linear-gradient(top, #dd6a45, #91361a); 395 | background-image: -o-linear-gradient(top, #dd6a45, #91361a); 396 | background-image: linear-gradient(top, #dd6a45, #91361a); 397 | background-color: #dd6a45; 398 | } 399 | /* line 32, ../../src/sass/messenger-spinner.scss */ 400 | ul.messenger-theme-future .messenger-message.alert-error.messenger-retry-soon .messenger-spinner { 401 | width: 32px; 402 | height: 32px; 403 | background: transparent; 404 | } 405 | /* line 37, ../../src/sass/messenger-spinner.scss */ 406 | ul.messenger-theme-future .messenger-message.alert-error.messenger-retry-soon .messenger-spinner .messenger-spinner-side .messenger-spinner-fill { 407 | background: #dd6a45; 408 | -webkit-animation-duration: 20s; 409 | -moz-animation-duration: 20s; 410 | -ms-animation-duration: 20s; 411 | -o-animation-duration: 20s; 412 | animation-duration: 20s; 413 | opacity: 1; 414 | } 415 | /* line 45, ../../src/sass/messenger-spinner.scss */ 416 | ul.messenger-theme-future .messenger-message.alert-error.messenger-retry-soon .messenger-spinner:after { 417 | content: ""; 418 | background: #333333; 419 | position: absolute; 420 | width: 26px; 421 | height: 26px; 422 | border-radius: 50%; 423 | top: 3px; 424 | left: 3px; 425 | display: block; 426 | } 427 | /* line 32, ../../src/sass/messenger-spinner.scss */ 428 | ul.messenger-theme-future .messenger-message.alert-error.messenger-retry-later .messenger-spinner { 429 | width: 32px; 430 | height: 32px; 431 | background: transparent; 432 | } 433 | /* line 37, ../../src/sass/messenger-spinner.scss */ 434 | ul.messenger-theme-future .messenger-message.alert-error.messenger-retry-later .messenger-spinner .messenger-spinner-side .messenger-spinner-fill { 435 | background: #dd6a45; 436 | -webkit-animation-duration: 600s; 437 | -moz-animation-duration: 600s; 438 | -ms-animation-duration: 600s; 439 | -o-animation-duration: 600s; 440 | animation-duration: 600s; 441 | opacity: 1; 442 | } 443 | /* line 45, ../../src/sass/messenger-spinner.scss */ 444 | ul.messenger-theme-future .messenger-message.alert-error.messenger-retry-later .messenger-spinner:after { 445 | content: ""; 446 | background: #333333; 447 | position: absolute; 448 | width: 26px; 449 | height: 26px; 450 | border-radius: 50%; 451 | top: 3px; 452 | left: 3px; 453 | display: block; 454 | } 455 | /* line 125, ../../src/sass/messenger-theme-future.sass */ 456 | ul.messenger-theme-future .messenger-message-slot.messenger-last .messenger-message { 457 | -webkit-border-radius: 4px 4px 0px 0px; 458 | -moz-border-radius: 4px 4px 0px 0px; 459 | -ms-border-radius: 4px 4px 0px 0px; 460 | -o-border-radius: 4px 4px 0px 0px; 461 | border-radius: 4px 4px 0px 0px; 462 | } 463 | /* line 128, ../../src/sass/messenger-theme-future.sass */ 464 | ul.messenger-theme-future .messenger-message-slot.messenger-first .messenger-message { 465 | -webkit-border-radius: 0px 0px 4px 4px; 466 | -moz-border-radius: 0px 0px 4px 4px; 467 | -ms-border-radius: 0px 0px 4px 4px; 468 | -o-border-radius: 0px 0px 4px 4px; 469 | border-radius: 0px 0px 4px 4px; 470 | -webkit-box-shadow: inset 0px 1px rgba(255, 255, 255, 0.13), inset 48px 0px 0px rgba(0, 0, 0, 0.3), inset 46px 0px 0px rgba(255, 255, 255, 0.07); 471 | -moz-box-shadow: inset 0px 1px rgba(255, 255, 255, 0.13), inset 48px 0px 0px rgba(0, 0, 0, 0.3), inset 46px 0px 0px rgba(255, 255, 255, 0.07); 472 | box-shadow: inset 0px 1px rgba(255, 255, 255, 0.13), inset 48px 0px 0px rgba(0, 0, 0, 0.3), inset 46px 0px 0px rgba(255, 255, 255, 0.07); 473 | } 474 | /* line 132, ../../src/sass/messenger-theme-future.sass */ 475 | ul.messenger-theme-future .messenger-message-slot.messenger-first.messenger-last .messenger-message { 476 | -webkit-border-radius: 4px; 477 | -moz-border-radius: 4px; 478 | -ms-border-radius: 4px; 479 | -o-border-radius: 4px; 480 | border-radius: 4px; 481 | -webkit-box-shadow: inset 48px 0px 0px rgba(0, 0, 0, 0.3), inset 46px 0px 0px rgba(255, 255, 255, 0.07); 482 | -moz-box-shadow: inset 48px 0px 0px rgba(0, 0, 0, 0.3), inset 46px 0px 0px rgba(255, 255, 255, 0.07); 483 | box-shadow: inset 48px 0px 0px rgba(0, 0, 0, 0.3), inset 46px 0px 0px rgba(255, 255, 255, 0.07); 484 | } 485 | /* line 136, ../../src/sass/messenger-theme-future.sass */ 486 | ul.messenger-theme-future .messenger-spinner { 487 | display: block; 488 | position: absolute; 489 | left: 7px; 490 | top: 50%; 491 | margin-top: -18px; 492 | z-index: 999; 493 | height: 32px; 494 | width: 32px; 495 | z-index: 10; 496 | } 497 | --------------------------------------------------------------------------------