├── pamphlets
├── CentOS.odt
├── CentOS.pdf
├── CentOS-SIGs.odt
├── CentOS-SIGs.pdf
├── CentOS-ContainerPipeline.odt
└── CentOS-ContainerPipeline.pdf
├── scripts
├── README
├── who.sh
├── tshirts.py
├── followers.py
├── get_blogs.pl
├── meeting_minutes.pl
├── centos-announcements.pl
├── rss_updates.py
├── sig_reporting
│ └── reporting.pl
└── get_meetups
├── README.md
└── LICENSE
/pamphlets/CentOS.odt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rbowen/centos-community-tools/HEAD/pamphlets/CentOS.odt
--------------------------------------------------------------------------------
/pamphlets/CentOS.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rbowen/centos-community-tools/HEAD/pamphlets/CentOS.pdf
--------------------------------------------------------------------------------
/pamphlets/CentOS-SIGs.odt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rbowen/centos-community-tools/HEAD/pamphlets/CentOS-SIGs.odt
--------------------------------------------------------------------------------
/pamphlets/CentOS-SIGs.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rbowen/centos-community-tools/HEAD/pamphlets/CentOS-SIGs.pdf
--------------------------------------------------------------------------------
/pamphlets/CentOS-ContainerPipeline.odt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rbowen/centos-community-tools/HEAD/pamphlets/CentOS-ContainerPipeline.odt
--------------------------------------------------------------------------------
/pamphlets/CentOS-ContainerPipeline.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rbowen/centos-community-tools/HEAD/pamphlets/CentOS-ContainerPipeline.pdf
--------------------------------------------------------------------------------
/scripts/README:
--------------------------------------------------------------------------------
1 | Various scripts that are used around the CentOS community.
2 |
3 | Stuff you might need to install:
4 |
5 | dnf install python3-feedparser
6 |
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # centos-community-tools
2 | CentOS Community Manager tools and scripts
3 |
4 | Automates some of the mudane daily tasks that are part of my job as CentOS Community Manager.
5 |
--------------------------------------------------------------------------------
/scripts/who.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # Who contributed (post-process output of rss_updates.py (Numbers are
3 | # not reliable, as one person will invariably appear more than once in a
4 | # given change, but it's a good way to get a list to start with.)
5 |
6 | egrep '@' rss_updates.html | egrep ' GMT ' | sed 's/^.* GMT - //;' | sed 's/\>.*$//' | sort | uniq -c | sort -rn > who_contributed.txt
7 |
8 |
--------------------------------------------------------------------------------
/scripts/tshirts.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | import sys
3 |
4 | # How many tshirts to order? This uses a bell curve distribution of
5 | # sizes, which works fairly well in Europe. Might want to skew larger
6 | # for US events.
7 |
8 | shirts = int(input("How many shirts? "))
9 |
10 | print ("For a total of " + str(shirts) + " shirts, order:\n" )
11 |
12 | s = shirts * 5 / 50
13 | m = shirts * 12 / 50
14 | l = shirts * 16 / 50
15 | xl = shirts * 12 / 50
16 | xxl = shirts * 5 / 50
17 |
18 | # For APAC ...
19 | # s = shirts * 7 / 50
20 | # m = shirts * 17 / 50
21 | # l = shirts * 16 / 50
22 | # xl = shirts * 7 / 50
23 | # xxl = shirts * 3 / 50
24 |
25 | print( str(s) + " small")
26 | print( str(m) + " medium")
27 | print( str(l) + " large")
28 | print( str(xl) + " xl")
29 | print( str(xxl) + " 2xl")
30 |
31 |
--------------------------------------------------------------------------------
/scripts/followers.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 |
3 | # How many followers do you have?
4 | import urllib.request
5 | import re
6 |
7 | print ("This doesn't work any more because Twitter is actively preventing it. Sorry.")
8 | quit()
9 |
10 | feeds = [
11 | 'rbowen','centosproject','centos'
12 | ];
13 | for feed in feeds:
14 | req = urllib.request.Request( 'https://twitter.com/' + feed + '/',
15 | data = None,
16 | headers={
17 | 'User-Agent':
18 | 'Mozilla/5.0 (Windows NT 6.1; Win64; x64)',
19 | } )
20 | f = urllib.request.urlopen(req)
21 | html = f.read().decode('utf-8')
22 |
23 | # Looks like ...
24 | #
2,615
25 | # Followers
26 |
27 | print ( feed + ': ' + re.search('.*?followers">.+?statnum">([\d,MK]+).*?<.*?statlabel"> Followers.*', html, re.DOTALL).group(1) )
28 |
29 |
--------------------------------------------------------------------------------
/scripts/get_blogs.pl:
--------------------------------------------------------------------------------
1 | #!/usr/bin/perl
2 | use strict;
3 | use warnings;
4 |
5 | use XML::Feed;
6 | use URI;
7 | use XML::Simple;
8 | use LWP::Simple;
9 |
10 | $|++;
11 |
12 | my $url = URI->new("http://planet.centos.org/atom.xml");
13 |
14 | my $feed = XML::Feed->parse($url);
15 |
16 | open (my $md, '>', 'blogs.md');
17 | open (my $tweets, '>', 'blogs.tweets.csv');
18 |
19 | foreach ( $feed->entries ) {
20 |
21 | print $md "**"
22 | . $_->title . "** by "
23 | . $_->author . "\n\n";
24 | my $body = $_->content->body;
25 |
26 | # Or possibly the summary?
27 | unless ( $body ) {
28 | $body = $_->summary->body;
29 | $body =~ s/\n.*//is
30 | }
31 |
32 | $body =~ s/^.*?]*?>//i;
33 | $body =~ s!
.*$!!is;
34 | $body =~ s/<[^>]+>//igs; # Strip HTML
35 | $body =~ s/[\r\n]/ /gs; # Strip newlines from whatever's left
36 | print $md "> $body\n\n";
37 |
38 | my $link = $_->link;
39 | print $md "Read more at [$link]($link)\n\n\n";
40 |
41 | print $tweets '"01/01/2018 00:00:00","' . $_->title
42 | . ' #OpenStack #RDOCommunity","'
43 | . $link . '"' . "\n";
44 | }
45 |
46 | close $md;
47 | close $tweets;
48 |
49 | print "\nDone\n";
50 |
--------------------------------------------------------------------------------
/scripts/meeting_minutes.pl:
--------------------------------------------------------------------------------
1 | #!/usr/bin/perl
2 | use strict;
3 | use warnings;
4 | use DateTime;
5 |
6 | use LWP::Simple qw(get);
7 | use Getopt::Long qw(GetOptions);
8 | my $help=0;
9 | GetOptions("help" => \$help) or help();
10 | help() if $help;
11 |
12 | my $dt = DateTime->now( time_zone => 'local' );
13 | my $year = $ARGV[0] || $dt->year();
14 | my $month = $ARGV[1] || $dt->month_name();
15 |
16 | my $url = 'https://centos.org/minutes/' . $year . '/' . $month . '/';
17 | # print "*** $url ***\n";
18 | my $html = get $url;
19 |
20 | if ($html) {
21 | my @html=split(/\n/, $html);
22 | foreach my $line(@html) {
23 |
24 | # Looking for meeting minutes like centos-meeting.2018-09-04-13.01.html
25 | next unless $line =~ m/(centos-meeting|epel)(\d?)\.$year-\d\d-\d\d-\d\d\.\d\d\.html/;
26 |
27 | $line =~ s/^.+href="//;
28 |
29 | my ($filename, $date) = ( $line =~ m/^(.+?)".+?"indexcollastmod">(.+?) / );
30 |
31 | # On $date, there are meeting minutes in $filename. What SIG was that?
32 | my $minutesurl = $url . "$filename";
33 | my $minutes = get $minutesurl;
34 |
35 | # Get the meeting title from the minutes.
36 | my ($title) = ( $minutes =~ m!#(?:centos-meeting\d?|epel): (.*?)
! );
37 |
38 | print "Meeting minutes - $title, $date - $minutesurl #CentOS #SIG\n";
39 |
40 | }
41 | } else {
42 | print "There appear to be no meeting minutes for that month. See $url to confirm.\n";
43 | }
44 |
45 | sub help {
46 | print "Usage: ./$0 YEAR MONTH - eg, ./$0 2018 September\n";
47 | print "Defaults to current month of none specified.\n";
48 | exit();
49 | }
50 |
51 |
--------------------------------------------------------------------------------
/scripts/centos-announcements.pl:
--------------------------------------------------------------------------------
1 | #!/usr/bin/perl
2 | use strict;
3 | use warnings;
4 | use DateTime;
5 |
6 | use LWP::Simple qw(get);
7 |
8 | use Getopt::Long qw(GetOptions);
9 | my $help=0;
10 | GetOptions("help" => \$help) or help();
11 | help() if $help;
12 |
13 | # Start scheduling them today
14 | my $dt = DateTime->now( time_zone => 'local' );
15 | my $mday = $dt->day();
16 | my $start_mday = $mday;
17 | my $mon = $dt->month();
18 |
19 | # Which month are we reporting on?
20 | my $year = $ARGV[0] || $dt->year();
21 | my $month = $ARGV[1] || $dt->month_name();
22 |
23 | my $baseurl = 'https://lists.centos.org/pipermail/centos-announce/' . $year . '-' . $month;
24 | my $url = $baseurl . '/thread.html';
25 | # print "*** $url *** \n";
26 | my $html = get $url;
27 |
28 | unless ($html) {
29 | print "It appears that there haven't been any announcements yet in the specified month.\n";
30 | exit();
31 | }
32 |
33 | open (my $tweets, '>', 'announce_tweets.csv' );
34 | open (my $news, '>', 'announce_newsletter.txt' );
35 | my (@ceea, @cesa, @ceba, @other);
36 |
37 | my @html=split(/\n/, $html);
38 | foreach my $line (@html) {
39 |
40 | # Looking for lines like
41 | # [CentOS-announce] CEEA-2018:2675 CentOS 6 microcode_ctl ...
42 | # print "Considering $line\n";
43 | next unless $line =~ m/^ .+$!!;
50 |
51 | $subject =~ s/\s+/ /g;
52 |
53 | # We need a little information from that post
54 | my $email = get ( $baseurl . '/' . $filename );
55 | my ( $date ) = ( $email =~ m!(.+?)! );
56 |
57 | # The time isn't particularly important ...
58 | $date =~ s! \d\d:\d\d:\d\d ...!!;
59 |
60 | my $t = "On $date we issued the following update: $subject. Details at $baseurl/$filename #CentOS #Updates";
61 | print $t . "\n";
62 | print $tweets "\"$mon/$mday/$year 09:00\",\"$t\"\n";
63 | $mday++; $mday = $start_mday if $mday > 30;
64 |
65 | my $announce = "$date: $subject - $baseurl/$filename";
66 |
67 | push (@cesa, $announce) if $subject =~ m/CESA/;
68 | push (@ceea, $announce) if $subject =~ m/CEEA/;
69 | push (@ceba, $announce) if $subject =~ m/CEBA/;
70 | push (@other, $announce) unless $subject =~ m/CE.A/;
71 | }
72 |
73 | errata( $month, $news, 'Enhancements', 'CEEA', @ceea );
74 | errata( $month, $news, 'Security', 'CESA', @cesa );
75 | errata( $month, $news, 'Bugfix', 'CEBA', @ceba );
76 | errata( $month, $news, 'Other', 'Other', @other);
77 |
78 | sub errata {
79 | my ( $month, $news, $name, $acro, @arry ) = @_;
80 |
81 | if ($name eq 'Other') {
82 | print $news qq~\n\nOther releases
83 |
84 | The following releases also happened during $month:
85 |
86 | ~;
87 | } else {
88 | print $news qq~\n\nErrata and $name Advisories
89 |
90 | We issued the following $acro (CentOS Errata and $name Advisories) during $month:
91 |
92 | ~;
93 | }
94 |
95 | print $news "\n";
96 |
97 | foreach my $i (@arry) {
98 | print $news " $i\n";
99 | }
100 |
101 | print $news "
\n\n";
102 |
103 | }
104 |
105 | sub help {
106 | print "Usage: $0 YEAR MONTH - eg, $0 2018 September\n";
107 | print "Defaults to current month if none specified.\n";
108 | exit();
109 | }
110 |
111 |
--------------------------------------------------------------------------------
/scripts/rss_updates.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 | import sys
3 | import urllib3
4 | import sqlite3
5 | import feedparser
6 | import re
7 |
8 | db_connection = sqlite3.connect('centos_update_rss.sqlite')
9 | db = db_connection.cursor()
10 |
11 | # Uncomment this line to start with a fresh database
12 | db.execute('DROP TABLE centosupdates')
13 |
14 | db.execute('CREATE TABLE IF NOT EXISTS centosupdates (title TEXT, id TEXT)')
15 | out = open('rss_updates.txt', 'w')
16 | html = open('rss_updates.html', 'w')
17 |
18 | def read_release_feed():
19 | """ Get packages from RSS feed """
20 | feedbase = 'https://feeds.centos.org/';
21 | feeds = [
22 |
23 | # Stream 8
24 | 'centos-8-stream-aarch64-BaseOS',
25 | 'centos-8-stream-aarch64-AppStream',
26 | 'centos-8-stream-aarch64-PowerTools',
27 | 'centos-8-stream-ppc64le-AppStream',
28 | 'centos-8-stream-ppc64le-BaseOS',
29 | 'centos-8-stream-ppc64le-PowerTools',
30 | 'centos-8-stream-x86_64-AppStream',
31 | 'centos-8-stream-x86_64-BaseOS',
32 | 'centos-8-stream-x86_64-PowerTools',
33 |
34 | # Stream 9
35 | 'centos-9-stream-aarch64-AppStream',
36 | 'centos-9-stream-aarch64-BaseOS',
37 | 'centos-9-stream-aarch64-CRB',
38 | 'centos-9-stream-ppc64le-AppStream',
39 | 'centos-9-stream-ppc64le-BaseOS',
40 | 'centos-9-stream-ppc64le-CRB',
41 | 'centos-9-stream-s390x-AppStream',
42 | 'centos-9-stream-s390x-BaseOS',
43 | 'centos-9-stream-s390x-CRB',
44 | 'centos-9-stream-x86_64-AppStream',
45 | 'centos-9-stream-x86_64-BaseOS',
46 | 'centos-9-stream-x86_64-CRB'
47 |
48 | ]
49 |
50 | # HTML headers
51 | html.write("\n");
52 | for f in feeds:
53 | html.write("- " + f + "
\n");
54 | html.write("
\n");
55 |
56 | for f in feeds:
57 | count = 0
58 | feed = feedparser.parse(feedbase + f + '.xml')
59 | print("Checking new packages in " + f + " ", end="\r")
60 | out.write("\n\nNew packages in " + f + ":")
61 | html.write("New packages in " + f + "
")
62 |
63 | for release in feed['entries']:
64 | if release_is_not_db(release['title'], release['id']):
65 | format_release(release)
66 | add_release_to_db(release['title'], release['id'])
67 | count += 1
68 |
69 | if (count == 0):
70 | msg = "No new packages in " + f + " since last scan."
71 | out.write("\n" + msg + "\n\n")
72 | html.write("" + msg + "
\n")
73 |
74 | print("See rss_updates.txt and rss_updates.html for results \n")
75 |
76 | def release_is_not_db(release_title, release_id):
77 | """ Check if we've already seen a release
78 | Args:
79 | release_title (str): Title
80 | release_id (str): Unique ID of release
81 | Return:
82 | True if we haven't seen it yet
83 | False otherwise
84 | """
85 | db.execute("SELECT * from centosupdates WHERE title=? AND id=?", (release_title, release_id))
86 | if not db.fetchall():
87 | return True
88 | else:
89 | return False
90 |
91 |
92 | def add_release_to_db(release_title, release_id):
93 | """ Add a new release to the database
94 | Args:
95 | release_title (str): The title of a release
96 | release_id (str): Unique ID of release
97 | """
98 |
99 | db.execute("INSERT INTO centosupdates VALUES (?, ?)", (release_title, release_id))
100 | db_connection.commit()
101 |
102 |
103 | def format_release( release ):
104 | """ Prettyprint the release info """
105 | out.write("\n" + release['published'] + ": " + release['title'])
106 | html.write("\n" + release['published'] + ": " + release['title'] + "
")
107 |
108 | """ Strip HTML, special chars """
109 | summary = re.sub('<[^<]+?>', '', release['summary'])
110 | summary = re.sub('<', '<', summary)
111 | summary = re.sub('>', '>', summary)
112 |
113 | """ Drop everything past the first (most recent) Change Log: entry """
114 | summary = re.sub('(Change Log:\n\n.*?)\n\n.*$', r'\1', summary, flags=re.S)
115 |
116 | html.write(release['summary'])
117 | html.write("\n\n")
118 | print(".", end ="", flush=True)
119 |
120 | if __name__ == '__main__':
121 |
122 | read_release_feed()
123 | db_connection.close()
124 | out.close()
125 | html.close()
126 |
127 |
--------------------------------------------------------------------------------
/scripts/sig_reporting/reporting.pl:
--------------------------------------------------------------------------------
1 | #!/usr/bin/perl
2 | use strict;
3 | use warnings;
4 | use DateTime;
5 | use Getopt::Long qw(GetOptions);
6 | use Term::ANSIColor;
7 |
8 | my $help = 0;
9 | my $t = 0;
10 | GetOptions(
11 | "help" => \$help,
12 | "this" => \$t, # Defaults to NEXT month
13 | );
14 |
15 | help() if $help;
16 |
17 | my %reporters = (
18 | 1 => [
19 | [ 'Core', 'sig-core@centosproject.org' ],
20 | [ 'Config Management', 'sig-configmanagement@centosproject.org' ],
21 | [ 'Software Collections', 'sig-sclo@centosproject.org' ],
22 | [ 'Hyperscale', 'sig-hyperscale@centosproject.org' ],
23 | [ 'Stream Feature Request', 'ENOSIG' ],
24 | [ 'kmods', 'sig-kmods@centosproject.org' ],
25 | [ 'Automotive', 'jefro@redhat.com' ],
26 | ],
27 |
28 | 2 => [
29 | [ 'Alt Arch', 'sig-altarch@centosproject.org' ],
30 | [ 'Cloud', 'sig-cloud@centosproject.org' ],
31 | [ 'NFV', 'sig-nfv@centosproject.org' ],
32 | [ 'Promo', 'centos-promo@centos.org' ],
33 | [ 'Storage', 'sig-storage@centosproject.org' ],
34 | [ 'Messaging', 'sig-messaging@centosproject.org' ],
35 | ],
36 |
37 | 3 => [
38 | [ 'Artwork', 'sig-artwork@centosproject.org' ],
39 | [ 'Cloud Instance', 'sig-cloudinstance@centosproject.org' ],
40 | [ 'OpsTools', 'sig-opstools@centosproject.org' ],
41 | [ 'Public CI', 'ENOSIG' ],
42 | [ 'Virtualization', 'sig-virt@centosproject.org' ],
43 | [ 'Infrastructure', 'sig-infra@centosproject.org' ],
44 | ]
45 |
46 | );
47 |
48 | my @sigs;
49 |
50 | # Who is reporting next month?
51 | # Weirdly, just doing month+1 can sometimes skip past a shorter month.
52 | # eg, March 31 + 1 month puts you in May, not April.
53 | my $today = DateTime->now( time_zone => 'local' );
54 | my $dt = DateTime->new(
55 | day => 1,
56 | month => $today->month(),
57 | year => $today->year()
58 | )->add( months => ( $t ? 0 : 1 ) ); # Defaults to next month
59 |
60 | my $month = $dt->month_name;
61 | print "Reports due for $month.\n";
62 |
63 | my $group = ( ( $dt->month() % 3 ) || 3 );
64 |
65 | print "We expect reports from SIGs in Group " . $group . ":\n\n";
66 |
67 | foreach my $sig ( @{ $reporters{$group} } ) {
68 | print " * $sig->[0]\n";
69 | push @sigs, $sig->[0];
70 | }
71 |
72 | # When is the first Monday?
73 | my $dow = $dt->day_of_week; # 1-7 (Monday is 1)
74 | my $first_monday = ( $dow == 1 ) ? 1 : (9 - $dow);
75 |
76 | print "\n";
77 |
78 | print "Send the following reminders to these SIGs\n";
79 |
80 | foreach my $sig ( @{ $reporters{$group} } ) {
81 | my $name = $sig->[0];
82 | my $email = $sig->[1];
83 |
84 | # TODO: Calculate the first Monday of the month, rather than editing
85 | # this every time.
86 |
87 | print qq~
88 | --------------------------------------------------
89 |
90 | SUBJECT: CentOS $name SIG quarterly report for $month newsletter
91 | To: $email
92 |
93 | Dear $name SIG,
94 |
95 | As documented in the wiki, we request that each SIG reports quarterly
96 | about the accomplishments of their SIG during the previous quarter.
97 |
98 | (You are receiving this because you are listed in the $name
99 | SIG ACL on accounts.centos.org. If that is not accurate, you should take
100 | it up with your SIG lead. See https://accounts.centos.org/groups/)
101 |
102 | As you may be aware, your SIG - the $name SIG - is on the
103 | list for $month reports. See "Reporting" section towards the bottom of
104 | https://wiki.centos.org/SpecialInterestGroup
105 |
106 | I encourage you to have a look at the report produced by the Cloud SIG
107 | as an example of what we're looking for. It's not onerous - just an
108 | overview of releases and community activity over the preceding few
109 | months. https://blog.centos.org/2019/01/centos-cloud-sig-quarterly-report/
110 |
111 | Reports can be submitted either directly to me, or, if you prefer, you
112 | can submit it as a blog post on blog.centos.org.
113 |
114 | See https://wiki.centos.org/SpecialInterestGroup/Promo/Blog for details
115 | about posting to the CentOS blog.
116 |
117 | The report will be included in the $month community newsletter. As
118 | such I need it by Monday $month $first_monday, at the very latest.
119 |
120 | Thanks!
121 |
122 | ~;
123 |
124 | }
125 |
126 | print "Summary: reports expected for $month:\n";
127 | foreach my $sig (@sigs) {
128 | print " * $sig\n";
129 | }
130 |
131 | # Usage reminder
132 | unless ($t) {
133 | print color("bold red");
134 | print "\nNote: Use -t to run for THIS month. Defaults to NEXT month.\n\n";
135 | print color('reset');
136 | }
137 |
138 | sub help {
139 | print "\nNote: Use -t to run for THIS month. Defaults to NEXT month.\n";
140 | print "Usage: $0\n";
141 | print " use -t to run for THIS month\n";
142 | print "Defaults to NEXT month if run without -t.\n";
143 | exit();
144 | }
145 |
146 |
--------------------------------------------------------------------------------
/scripts/get_meetups:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | import datetime
3 | from datetime import date
4 | import urllib.request
5 | import json
6 | import time
7 | import re
8 |
9 | # Magic
10 | import sys # sys.setdefaultencoding is cancelled by site.py
11 | # reload(sys) # to re-enable sys.setdefaultencoding()
12 | # sys.setdefaultencoding('utf-8')
13 |
14 | print ("Rounding up the ponies ...")
15 |
16 | # NOTE: This is Rich's personal API key. Get your own.
17 | key = "3a7711454d145e404e531c2ee6f391d"
18 |
19 | keyword = sys.argv[1] if len(sys.argv) >= 2 else 'linux'
20 | url = "https://api.meetup.com/2/open_events?&sign=true&photo-host=public&state=ky&city=lexington&country=usa&text=" + keyword + "&radius=10000&sign=true&key=" + key
21 |
22 | print(url)
23 | week = 7
24 | twoweek = 14
25 | now = datetime.datetime.now()
26 | nowts = time.mktime(now.timetuple())
27 |
28 | print ("Fetching meetups ...")
29 |
30 | response = urllib.request.urlopen(url)
31 | m = response.read().decode('utf-8')
32 |
33 | print ("Parsing results ...")
34 | r = json.loads(m)
35 | meetups = r['results']
36 |
37 | groups = {}
38 |
39 | print ("Fetching group details ...")
40 |
41 | for meetup in meetups:
42 | groups[ str( meetup['group']['id'] ) ] = meetup['group']['name']
43 |
44 | keys = groups.keys()
45 | keyarg = ",".join( keys )
46 |
47 | group_url = "https://api.meetup.com/2/groups?&sign=true&photo-host=public&group_id=" + keyarg + "&key=" + key
48 |
49 | response = urllib2.urlopen( group_url )
50 | m = response.read()
51 | r = json.loads(m)
52 |
53 | grps = r['results']
54 |
55 | grp_deets = {}
56 | for g in grps:
57 | grp_deets[ g['id'] ] = g
58 |
59 | seen = set()
60 |
61 | print ("Ok, ready to print meetup details ...")
62 |
63 | weeklater = (nowts * 1000 ) + ( week * 86400 * 1000 )
64 | twoweeklater = (nowts * 1000 ) + ( twoweek * 86400 * 1000 )
65 |
66 | tweets = open('meetups.tweets.csv', 'w')
67 | mlist = open('meetups.mlist', 'w')
68 | # wiki = open('meetups.wiki', 'w')
69 | # md = open('meetups.md', 'w')
70 | yml = open('meetups.yml', 'w')
71 |
72 | # Standard intro to mailing list post
73 | mlist.write( '''The following are the meetups I'm aware of in the next two weeks where
74 | CentOS and/or Linux enthusiasts are likely to be present.
75 |
76 | If there's a meetup in your area, please consider attending. If you
77 | attend, please consider taking a few photos, and possibly even writing
78 | up a brief summary of what was covered.
79 |
80 | --Rich
81 |
82 | ''')
83 |
84 | yml.write( '''name: RDO Meetups
85 | type: series
86 | tags: RDO
87 |
88 | talks:
89 |
90 | ''')
91 |
92 | for meetup in meetups:
93 | eventts = int( meetup['time'] + meetup['utc_offset'] )
94 | eventutc = meetup['time']
95 |
96 | # Skip it if it's more than two weeks away
97 | if eventts > twoweeklater:
98 | continue
99 |
100 | eventtimeutc = datetime.datetime.fromtimestamp(eventutc/1000)
101 | endtimeutc = datetime.datetime.fromtimestamp(eventutc/1000 + 3600)
102 | eventtime = date.fromtimestamp( eventts/1000 )
103 | t = eventtime.strftime("%A %B %d")
104 |
105 | # Skip it if we've already seen it
106 | uniq = meetup['name'] + t
107 | if uniq in seen:
108 | continue
109 | seen.add( uniq )
110 |
111 |
112 | # Group information ...
113 | grp = grp_deets[ meetup['group']['id'] ]
114 |
115 | # Skip it if it doesn't actually mention the keyword
116 | if (re.search(keyword, grp['name'], re.IGNORECASE) == None) \
117 | and (re.search(keyword, meetup['name'], re.IGNORECASE) == None) \
118 | and (re.search(keyword, meetup['event_url'], re.IGNORECASE) == None):
119 | continue
120 |
121 | # For yml
122 | mname = meetup['name']
123 | mname = mname.replace(':','')
124 | eventout = '- title: ' + mname + "\n"
125 | eventout = eventout + ' location: ' + grp['city'] + ', '
126 | if 'state' in grp.keys():
127 | eventout = eventout + grp['state'] + "\n"
128 | else:
129 | eventout = eventout + grp['country'] + "\n"
130 | eventout = eventout + ' speaker: ' + grp['name'] + "\n"
131 | eventout = eventout + ' start: ' + eventtimeutc.strftime("%Y-%m-%d %H:%M") + ' UTC' + "\n"
132 | eventout = eventout + ' end: ' + endtimeutc.strftime("%Y-%m-%d %H:%M") + ' UTC' + "\n"
133 | eventout = eventout + ' description: |' + "\n"
134 | eventout = eventout + ' ' + grp['city'] + ', '
135 | if 'state' in grp.keys():
136 | eventout = eventout + grp['state'] + ', '
137 | eventout = eventout + grp['country'] + "\n"
138 | eventout = eventout + ' ' + mname + "\n"
139 | eventout = eventout + ' ' + meetup['event_url'] + "\n"
140 |
141 | eventout = eventout + "\n"
142 | yml.write( str(eventout) )
143 |
144 | # For mailing list
145 | eventout = '* ' + t + ' in ' + grp['city'] + ', '
146 | if 'state' in grp.keys():
147 | eventout = eventout + grp['state'] + ', '
148 | eventout = eventout + grp['country'] + ': ' + meetup['name'] + ' - ' + meetup['event_url'] + "\n\n"
149 | mlist.write( str( eventout ))
150 |
151 | # For Twitter, a week is enough
152 | if eventts > weeklater:
153 | continue
154 |
155 | # For Twitter
156 | eventout = '"01/01/2018 00:00:00","' + t + ' in ' + grp['city'] + ', '
157 | if 'state' in grp.keys():
158 | eventout = eventout + grp['state'] + ', '
159 | eventout = eventout + grp['country'] + ': ' + meetup['name'] + '","' + meetup['event_url'] + '"' + "\n"
160 | tweets.write( str( eventout ))
161 |
162 | # Barn door
163 | tweets.close()
164 | mlist.close()
165 | yml.close()
166 |
167 | print ("Done!\n")
168 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------