1 | # Created by Noah Kantrowitz on 2008-05-16. |
---|
2 | # Copyright (c) 2008 Noah Kantrowitz. All rights reserved. |
---|
3 | import itertools |
---|
4 | import re |
---|
5 | |
---|
6 | |
---|
7 | from trac.core import * |
---|
8 | from trac.web.api import ITemplateStreamFilter |
---|
9 | from trac.web.chrome import ITemplateProvider, add_stylesheet |
---|
10 | from trac.config import Option, OrderedExtensionsOption |
---|
11 | from genshi.builder import tag |
---|
12 | from genshi.filters.transform import Transformer |
---|
13 | from pkg_resources import resource_filename |
---|
14 | |
---|
15 | from hackergotchi.api import IHackergotchiProvider |
---|
16 | |
---|
17 | |
---|
18 | class HackergotchiModule(Component): |
---|
19 | """A stream filter to add hackergotchi emblems to the timeline.""" |
---|
20 | |
---|
21 | providers = OrderedExtensionsOption('hackergotchi', 'providers', |
---|
22 | IHackergotchiProvider, |
---|
23 | default='GravatarHackergotchiProvider, IdenticonHackergotchiProvider') |
---|
24 | |
---|
25 | implements(ITemplateStreamFilter, ITemplateProvider) |
---|
26 | |
---|
27 | anon_re = re.compile('([^<]+?)\s+<([^>]+)>', re.U) |
---|
28 | |
---|
29 | # ITemplateStreamFilter methods |
---|
30 | def filter_stream(self, req, method, filename, stream, data): |
---|
31 | if req.path_info.startswith('/timeline'): |
---|
32 | closure_state = [0] |
---|
33 | cache = {} |
---|
34 | |
---|
35 | def f(stream): |
---|
36 | # Update the closed value |
---|
37 | n = closure_state[0] |
---|
38 | closure_state[0] += 1 |
---|
39 | |
---|
40 | # Extract the user information |
---|
41 | author = data['events'][n]['author'].strip() |
---|
42 | user_info = cache.get(author) |
---|
43 | if user_info is not None: |
---|
44 | author, name, email = user_info |
---|
45 | else: |
---|
46 | db = self.env.get_db_cnx() |
---|
47 | user_info = self._get_info(author, db) |
---|
48 | cache[author] = user_info |
---|
49 | author, name, email = user_info |
---|
50 | |
---|
51 | # Try to find a provider |
---|
52 | for provider in self.providers: |
---|
53 | href = provider.get_hackergotchi( |
---|
54 | req.href, author, name, email) |
---|
55 | if href is not None: |
---|
56 | break |
---|
57 | else: |
---|
58 | href = req.href.chrome('hackergotchi', 'default.png') |
---|
59 | |
---|
60 | # Build our element |
---|
61 | elm = tag.img(src=href, alt='Hackergotchi for %s' % author, |
---|
62 | class_='hackergotchi') |
---|
63 | |
---|
64 | # Output the combined stream |
---|
65 | return itertools.chain(elm.generate(), stream) |
---|
66 | |
---|
67 | stream |= Transformer( |
---|
68 | '//div[@id="content"]/dl/dt/a/span[@class="time"]').filter(f) |
---|
69 | add_stylesheet(req, 'hackergotchi/hackergotchi.css') |
---|
70 | return stream |
---|
71 | |
---|
72 | # ITemplateProvider methods |
---|
73 | def get_htdocs_dirs(self): |
---|
74 | yield 'hackergotchi', resource_filename(__name__, 'htdocs') |
---|
75 | |
---|
76 | def get_templates_dirs(self): |
---|
77 | # return [resource_filename(__name__, 'templates')] |
---|
78 | return [] |
---|
79 | |
---|
80 | # Internal methods |
---|
81 | def _get_info(self, author, db): |
---|
82 | if author == 'anonymous': |
---|
83 | # Don't even bother trying for "anonymous" |
---|
84 | return author, None, None |
---|
85 | |
---|
86 | md = self.anon_re.match(author) |
---|
87 | if md: |
---|
88 | # name <email> |
---|
89 | return 'anonymous', md.group(1), md.group(2) |
---|
90 | |
---|
91 | cursor = db.cursor() |
---|
92 | cursor.execute('SELECT name, value FROM session_attribute WHERE sid=%s AND authenticated=%s', |
---|
93 | (author, 1)) |
---|
94 | rows = cursor.fetchall() |
---|
95 | if rows: |
---|
96 | # Authenticated user, with session |
---|
97 | name = email = None |
---|
98 | for key, value in rows: |
---|
99 | if key == 'name': |
---|
100 | name = value |
---|
101 | elif key == 'email': |
---|
102 | email = value |
---|
103 | if name or email: |
---|
104 | return author, name, email |
---|
105 | else: |
---|
106 | return author, None, None |
---|
107 | |
---|
108 | # Assume anonymous user from this point on |
---|
109 | if '@' in author: |
---|
110 | # Likely an email address |
---|
111 | return 'anonymous', None, author |
---|
112 | |
---|
113 | # See if there is a default domain |
---|
114 | domain = self.config.get('notification', 'smtp_default_domain') |
---|
115 | if domain and ' ' not in author: |
---|
116 | return author, None, author + '@' + domain |
---|
117 | |
---|
118 | return 'anonymous', author, None |
---|