diff --git a/.coverage b/.coverage new file mode 100644 index 000000000..91c403f7e --- /dev/null +++ b/.coverage @@ -0,0 +1 @@ +!coverage.py: This is a private format, don't read it directly!{"lines":{"/usr/local/lib/python3.5/dist-packages/sympy/core/symbol.py":[512,1,514,3,4,5,6,7,8,9,10,11,12,13,15,16,17,515,20,473,559,555,34,36,38,40,41,554,43,557,477,513,567,57,58,316,318,63,64,324,70,327,328,73,74,75,476,335,339,85,86,313,89,71,91,348,314,352,354,472,101,102,104,105,108,109,488,119,55,122,123,124,125,126,128,129,130,131,133,490,136,139,141,143,145,146,148,149,150,152,156,160,161,162,163,167,171,498,176,72,178,499,181,195,502,470,484,208,209,210,212,214,505,216,217,474,475,220,221,223,224,225,227,228,485,230,232,234,491,492,237,238,242,243,246,503,494,506,508,509],"/media/androbin/Daten/git/phenny/modules/eightball.py":[5,7,9,10,11,12,13,14,17,18,19,20,21,24,25,26,27,28,29,32,33,34,35,36,37,40,42,47,48,49,50,51,53],"/media/androbin/Daten/git/phenny/modules/mylife.py":[34,35,36,37,6,17,40,9,10,11,14,16,8,21,22,23,24,27,29,30],"/usr/local/lib/python3.5/dist-packages/sympy/parsing/__init__.py":[1],"/usr/local/lib/python3.5/dist-packages/sympy/functions/special/tensor_functions.py":[448,1,3,4,133,6,7,8,73,5,15,80,131,84,216,282,347,135,30,69,420,40,71,176,182,440,249,315,444,383],"/usr/local/lib/python3.5/dist-packages/idna/core.py":[1,2,3,4,5,6,8,9,10,12,13,14,335,16,17,18,131,21,22,23,26,27,28,286,31,32,33,36,146,39,42,231,364,45,307,49,140,258,56,124,190,63],"/usr/local/lib/python3.5/dist-packages/sympy/tensor/array/ndim_array.py":[1,2,387,4,390,7,393,396,399,403,404,406,260,410,411,412,413,161,290,263,300,177,310,58,59,319,193,328,208,337,82,340,344,90,224,101,63,106,239,369,372,373,375,122,123,381],"/usr/local/lib/python3.5/dist-packages/sympy/tensor/indexed.py":[644,136,137,138,139,140,141,143,401,277,665,623,412,672,112,164,113,169,554,556,557,558,559,560,305,562,564,439,364,314,700,189,456,714,207,561,365,88,603,478,223,668,114,108,493,110,367,368,497,370,115,244,366,118,119,122,686],"/usr/local/lib/python3.5/dist-packages/mpmath/functions/qfunctions.py":[1,3,4,131,170,204],"/usr/local/lib/python3.5/dist-packages/mpmath/usertools.py":[2,63],"/usr/local/lib/python3.5/dist-packages/mpmath/libmp/libhyper.py":[64,864,386,715,453,6,8,9,11,1042,13,526,15,464,594,1091,597,856,1068,591,335,732,861,32,976,27,484,40,41,1104,43,44,877,1134,1071,432,1035,310,940,441,763,380,917],"/usr/local/lib/python3.5/dist-packages/sympy/ntheory/factor_.py":[3,4,6,7,264,9,10,11,12,13,14,15,16,17,18,19,660,21,22,25,155,1952,1595,1954,1315,1700,805,1190,1191,1449,1863,1582,1583,1713,1907,52,695,696,697,698,699,700,701,1599,704,193,1860,1862,1697,1358,1488,1473,1699,1294,1810,1627,1628,1776,483,806,1254,1643,1389,1647,368,1875,1525,1919,1909,1556,1403,639],"/usr/local/lib/python3.5/dist-packages/sympy/matrices/expressions/matexpr.py":[512,1,3,5,6,7,8,9,10,11,12,13,557,16,18,19,534,478,537,27,540,29,543,32,546,549,552,555,353,301,558,559,520,53,521,445,58,60,62,63,64,65,66,67,68,69,326,71,96,74,481,334,79,531,82,85,86,345,90,91,348,95,352,272,354,355,100,101,358,438,360,105,106,487,110,111,515,370,115,116,120,121,378,125,126,448,391,407,140,141,145,146,404,405,150,151,156,157,159,163,167,412,171,428,431,176,434,499,182,186,415,189,192,458,198,456,356,202,460,206,463,209,419,213,471,216,475,220,222,225,556,484,229,357,423,237,495,467,509,510],"/usr/local/lib/python3.5/dist-packages/sympy/__init__.py":[64,65,66,67,68,69,70,71,72,73,74,87,12,14,15,80,17,18,84,85,23,25,27,92,79,36,37,43,82,46,48,49,50,51,55,57,58,59,60,61,62,63],"/usr/local/lib/python3.5/dist-packages/sympy/geometry/line.py":[1538,262,2312,780,269,1295,18,19,21,22,23,24,25,26,27,28,29,30,32,33,34,35,806,40,2183,556,1330,302,1672,1842,1076,2359,1080,2361,573,1086,64,65,2115,2116,1607,1355,589,78,2130,1877,89,1116,610,1636,104,2411,108,1170,2152,1043,631,2169,890,1659,1660,1918,1301,2181,1415,1416,2443,1426,1171,2477,2204,1818,159,674,2075,933,1564,1709,1966,1967,2227,1207,1183,1469,2379,457,719,210,2515,2260,2517,1743,989,991,2171,2278,1255,1512,508,2258,2011,1008,2043,1788,1789],"/usr/local/lib/python3.5/dist-packages/sympy/utilities/decorator.py":[1,130,3,5,6,7,9,138,11,12,14,144,17,146,147,149,133,134,39,42,135,181,182,137,184,185,186,190,62,191,193,139,198,200,201,202,203,204,205,206,207,82,85,86,88,79,95,96,16,99,195,140,19,117,118,122,183],"/usr/local/lib/python3.5/dist-packages/sympy/polys/domains/ring.py":[1,66,3,5,6,8,10,11,12,14,16,81,20,27,94,31,35,70,39,48,118,55,121,62],"/usr/local/lib/python3.5/dist-packages/pkg_resources/_vendor/packaging/version.py":[321,225,200,201,205,206,207,336,209,210,212,213,214,216,217,218,367,220,349,30,31,224,208,226,227,228,229,230,379,362,299,353,366,352,370,371,350,374,375,377,351,393],"/usr/local/lib/python3.5/dist-packages/sympy/polys/densearith.py":[1280,1,258,3,516,5,1671,14,15,1360,17,1811,878,278,1629,795,1744,670,1311,1056,419,1702,551,1578,300,46,687,1459,926,438,1722,190,1205,704,963,1605,838,455,1413,329,143,1101,207,80,721,163,399,477,224,1121,738,355,1764,1512,593,1256,1003,109,494,1536,241,1650,628,377,1786,1148,1557],"/usr/local/lib/python3.5/dist-packages/sympy/plotting/intervalmath/interval_arithmetic.py":[64,66,402,278,262,162,137,13,272,210,86,100,90,221,94,97,34,36,357,406,39,298,359,300,306,356,242,244,439,377,186,315,124],"/usr/local/lib/python3.5/dist-packages/sympy/logic/__init__.py":[1,3],"/usr/local/lib/python3.5/dist-packages/sympy/series/gruntz.py":[409,129,130,459,132,197,135,200,457,458,203,444,479,208,340,480,214,153,196,218,411,410,478,453,352,353,354,233,554,555,517,242,628,118,119,312,121,122,123,124,125,127],"/media/androbin/Daten/git/phenny/modules/wuvt.py":[4,6,7,10,30,31],"/usr/lib/python3/dist-packages/humanize/i18n.py":[33,2,3,4,37,6,8,9,11,14,52,21,41,56,61],"/usr/local/lib/python3.5/dist-packages/mpmath/functions/rszeta.py":[1378,1379,196,1393,237,78,1394,49,1138,51,53,54,55,248,57,1178,1240,1306,766,223],"/media/androbin/Daten/git/phenny/modules/test/test_tell.py":[3,5,6,7,8,10,12,13,14,15,17,19,21,22,23,24,26,27,28,30,31,32,34,35,37,38,39,41,43,44],"/usr/local/lib/python3.5/dist-packages/sympy/simplify/simplify.py":[1,3,1160,5,385,264,9,11,12,13,14,15,17,18,19,20,21,22,23,24,26,28,29,30,31,32,33,35,166,1009,42,688,305,1161,828,323,198,801,1100,206,720,1358,853,1295,988,997,993,994,995,996,38,999,616,1001,112,1000,659,1012,511,508,510,767],"/usr/local/lib/python3.5/dist-packages/sympy/tensor/array/mutable_ndim_array.py":[1,4,6],"/usr/local/lib/python3.5/dist-packages/sympy/polys/groebnertools.py":[1,744,3,5,262,7,8,9,10,12,397,784,337,406,282,349,286,325,290,484,6,294,730,298,493,366,477,304,520,52,841,376,441,698,571,764],"/usr/local/lib/python3.5/dist-packages/urllib3/contrib/pyopenssl.py":[43,44,46],"/usr/local/lib/python3.5/dist-packages/sympy/integrals/__init__.py":[26,12,13,14],"/usr/local/lib/python3.5/dist-packages/sympy/polys/domains/simpledomain.py":[1,3,5,6,8,9,10,12,14],"/media/androbin/Daten/git/phenny/modules/apertium_wiki.py":[66,67,68,6,71,8,9,23,12,13,14,77,80,17,18,20,85,86,87,24,4,27,29,32,48,35,38,40,43,46,47,76,49,25,53,56,57,58,61,21],"/media/androbin/Daten/git/phenny/modules/test/test_head.py":[4,5,6,7,8,9,10,12,13,15,16,17,19,20,22,23,25,26,27,28,30,32,33,34,35,36,38,39,40,41,42,43,45,46,47,48,49,50,52,53,54,55,56,57,60,63,64,66,67,68,69,70,71,72,73,75,77,78,79,80,81,82,83],"/usr/local/lib/python3.5/dist-packages/idna/package_data.py":[1],"/usr/local/lib/python3.5/dist-packages/chardet/hebrewprober.py":[128,130,131,132,133,134,135,136,137,138,139,144,282,149,151,152,196,154,28,29,286,164,174,178,182,255],"/usr/local/lib/python3.5/dist-packages/urllib3/fields.py":[1,2,3,5,71,8,105,138,50,116,158,22,157,62,63],"/usr/local/lib/python3.5/dist-packages/chardet/langthaimodel.py":[193,194,195,52,197,198,196,189],"/usr/local/lib/python3.5/dist-packages/sympy/printing/mathematica.py":[3,5,6,7,8,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,37,40,41,44,45,46,47,48,51,52,54,64,66,71,80,83,86,89,91,92,94,102,109,112,116],"/media/androbin/Daten/git/phenny/modules/imdb.py":[8,10,11,13,14,16,17,18,19,20,21,22,23,24,25,26,27,28,29,32,35,36,37,39,40,41,42,43,44,45,48,49,50],"/usr/local/lib/python3.5/dist-packages/chardet/langgreekmodel.py":[224,69,50,206,210,211,212,213,214,215,219,220,221,222,223],"/usr/local/lib/python3.5/dist-packages/sympy/matrices/expressions/slice.py":[32,1,3,4,5,102,7,71,79,83,52,53,54,55,88,57],"/usr/local/lib/python3.5/dist-packages/sympy/polys/domains/pythonrationalfield.py":[1,3,5,6,65,8,9,11,12,13,15,16,17,18,20,21,23,28,69,32,43,47,51,55,60],"/usr/local/lib/python3.5/dist-packages/idna/__init__.py":[1,2],"/usr/local/lib/python3.5/dist-packages/sympy/core/add.py":[1,1026,3,4,6,7,8,9,10,11,12,13,271,16,17,274,19,276,22,1047,281,796,907,518,554,561,819,905,312,863,232,574,437,224,435,267,69,71,73,75,268,851,341,854,857,904,860,93,94,95,96,353,98,99,100,869,102,97,104,105,103,108,146,111,113,115,628,118,377,379,381,385,130,643,1046,135,392,137,906,395,908,450,142,911,912,658,915,916,406,921,922,150,160,161,164,165,168,199,428,431,432,200,178,179,436,949,438,440,188,189,190,447,448,449,194,228,198,158,712,713,202,205,206,717,209,466,469,214,472,1030,219,476,221,478,101,480,482,914,484,486,488,714,490,492,749,495,753,131,245,758,759,136,1021,264],"/media/androbin/Daten/git/phenny/modules/iso639.py":[5,7,8,9,10,11,12,13,14,16,18,20,22,24,25,26,27,28,29,30,32,33,34,35,37,40,42,43,44,49,50,51,52,53,55,57,58,59,60,61,62,63,64,65,66,68,71,73,74,75,77,78,79,80,81,82,84,85,87,89,90,91,92,93,95,96,100,101,102,103,105,109,112,116,117,119,120,121,123,124,125,126,127,128,129,130,131,132,133,134,135,136,138,150,158,160,162,163,166,169,171,172,173,175,176,177,179,180,182],"/usr/local/lib/python3.5/dist-packages/sympy/series/fourier.py":[1,3,147,5,6,7,8,9,10,11,12,13,14,15,16,375,19,139,407,151,388,143,135,93,30,263,290,347,131,198,39,156,106,107,411,111,385,115,119,123,380,318,127],"/usr/local/lib/python3.5/dist-packages/mpmath/libmp/libmpf.py":[512,3,516,5,7,520,9,11,955,15,17,515,20,601,602,30,517,176,35,549,453,39,43,44,557,46,47,48,49,562,52,53,54,55,56,57,570,59,556,62,575,64,864,67,69,1101,867,84,85,86,87,88,89,90,604,93,94,95,609,98,611,616,444,107,621,189,1128,628,446,631,632,633,635,638,642,643,132,133,136,137,652,450,142,143,656,657,661,662,664,153,667,1180,1181,670,671,672,673,674,675,677,171,172,174,175,607,177,178,179,181,182,183,186,187,313,190,191,192,193,194,195,196,197,198,199,204,205,206,973,208,212,213,214,215,216,217,218,219,220,223,224,226,227,228,230,231,232,1319,978,1262,239,240,245,349,247,249,250,251,252,254,469,1283,772,1285,263,777,452,1294,1295,1296,273,350,277,280,644,284,285,445,901,561,560,291,294,295,296,297,298,299,300,302,303,304,1329,306,307,308,309,310,311,312,825,314,315,316,317,318,319,320,322,324,327,330,333,846,335,336,352,338,1367,345,859,861,862,863,1376,865,866,1339,868,357,358,871,361,367,881,574,576,389,905,906,395,908,606,401,915,238,407,410,925,415,928,929,930,419,420,933,425,426,427,429,645,947,948,949,952,953,442,927,956,957,958,959,448,962,964,965,967,970,971,972,305,974,975,466,931,334,471,932,484,1002,1003,1004,1005,1006,1010,1011,1012,1013,1014,503,1017,443],"/usr/local/lib/python3.5/dist-packages/sympy/functions/combinatorial/numbers.py":[1025,8,777,10,267,12,13,14,15,16,17,18,19,20,21,22,23,25,26,29,1315,38,39,1117,48,94,1339,266,322,951,588,738,269,592,594,595,270,87,856,89,858,1116,93,898,98,99,871,876,879,882,115,630,887,634,891,638,384,386,387,645,396,397,911,1171,406,151,153,122,161,678,682,171,626,1053,435,436,949,695,1465,446,1474,965,1224,969,973,1357,464,980,1017,987,118,736,994,1001,748,246,759,1016,249,1020,1021,1022],"/usr/local/lib/python3.5/dist-packages/sympy/functions/special/gamma_functions.py":[256,1,3,4,5,6,7,8,9,10,11,12,13,14,933,401,899,21,922,412,925,159,928,162,906,421,167,939,590,174,944,177,180,695,313,187,572,586,575,322,139,196,582,327,330,205,462,269,336,593,467,340,398,470,855,856,730,975,92,94,479,96,737,102,747,878,724,634,253],"/usr/local/lib/python3.5/dist-packages/sympy/solvers/pde.py":[64,899,488,201,394,558,662,791,809,217,924,34,35,37,38,39,40,41,42,43,44,45,46,47,49,50,51,53,233,60,949],"/usr/local/lib/python3.5/dist-packages/sympy/printing/octave.py":[512,86,11,13,14,15,16,17,18,19,20,278,24,25,282,27,28,29,30,31,288,48,36,37,38,39,40,41,42,43,44,45,46,47,304,52,55,56,57,60,61,62,480,66,67,68,69,70,71,72,78,336,339,270,342,90,347,94,98,102,106,274,317,368,373,377,124,381,386,391,651,396,401,407,26,414,112,421,284,429,285,433,437,443,190,210,216,292,220,224,228,234,296,242,425],"/usr/local/lib/python3.5/dist-packages/sympy/concrete/summations.py":[1,258,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,790,151,24,153,900,155,223,208,995,164,269,1049,743,855,664,172,530,559,305,908,308,812],"/usr/local/lib/python3.5/dist-packages/chardet/charsetprober.py":[32,66,35,37,39,103,44,61,47,51,54,58,29,30],"/usr/local/lib/python3.5/dist-packages/sympy/printing/mathml.py":[385,3,5,390,7,8,9,10,11,12,13,16,148,22,23,25,26,157,34,44,175,436,308,442,189,447,192,195,329,202,205,334,208,211,85,217,349,356,362,29,370,246,120,377,251],"/usr/local/lib/python3.5/dist-packages/sympy/matrices/expressions/blockmatrix.py":[128,1,3,4,5,6,7,8,137,10,11,12,13,14,15,16,17,18,19,21,265,413,197,160,423,391,301,174,47,306,437,55,186,190,65,69,73,77,335,81,210,211,214,88,345,218,95,227,374,102,321,232,46,364,237,111,368,241,245,118,248,380,255],"/usr/lib/python3/dist-packages/nose/plugins/isolate.py":[61,62],"/usr/lib/python3/dist-packages/nose/plugins/testid.py":[137,138,142,143,144,145,148,149,150,151,154,155],"/usr/local/lib/python3.5/dist-packages/sympy/polys/polyfuncs.py":[1,258,3,5,7,9,12,210,16,18,20,23,316,315,156,257],"/usr/local/lib/python3.5/dist-packages/sympy/printing/cxxcode.py":[1,5,6,11,12,13,14,15,16,17,18,19,20,21,22,26,27,28,30,37,38,39,41,42,44,45,46,47,52,53,54,57,58,61,62,63,65,67,68,71,72,73,76,77,78,79,81,84,90,97,98,99,102,105,106,107,110,113,114,115,118,121,122,123,126],"/usr/local/lib/python3.5/dist-packages/certifi/core.py":[18,27,36,21,22,24,9,10,11,14],"/media/androbin/Daten/git/phenny/modules/tell.py":[107,8,226,10,11,12,13,14,16,23,27,29,69,159,32,161,162,35,292,37,294,167,40,169,42,44,45,48,50,52,222,54,55,56,116,58,59,61,191,65,182,97,72,73,34,103,227,41,57,164,228,221,94,223,96,225,98,99,100,102,230,231,232,105,106,235,108,109,111,229,114,115,244,118,233,33,63,166],"/usr/local/lib/python3.5/dist-packages/sympy/polys/ring_series.py":[642,1539,1842,263,1663,526,1167,472,1684,1555,148,790,47,1843,1221,987,1862,42,1287,44,45,46,303,49,50,51,564,53,54,55,52,58,1083,1845,577,1347,452,1093,1609,96,1738,845,212,1844,728,89,859,1453,480,1846,187,1636,358,913,1383,1947,1849,878,1266,1760,1526,759,1044,123,426,1790,1151],"/usr/local/lib/python3.5/dist-packages/sympy/functions/special/hyper.py":[1024,1,1026,3,5,6,7,8,9,10,779,12,782,15,16,786,771,790,27,796,947,287,803,804,1030,806,810,814,47,304,49,51,308,53,823,569,315,1034,831,832,833,66,837,993,780,845,850,851,852,1038,953,856,859,608,865,612,841,872,873,874,878,957,986,882,886,1001,891,892,894,899,647,904,909,1005,656,915,916,661,918,666,922,926,671,676,936,681,686,944,177,691,180,949,695,184,441,444,701,192,708,967,200,975,208,721,978,467,724,982,218,731,223,736,976,483,228,741,1000,233,746,237,751,1009,242,1013,1017,1022],"/usr/local/lib/python3.5/dist-packages/mpmath/calculus/approximation.py":[1,2,139,228,38,17,26,39],"/usr/local/lib/python3.5/dist-packages/mpmath/functions/__init__.py":[1,3,4,5,6,7,8,9,10,11,12,13],"/usr/local/lib/python3.5/dist-packages/mpmath/libmp/gammazeta.py":[14,16,17,274,19,20,22,24,1584,38,1576,1578,299,1582,1840,1586,1587,1589,1590,1673,1592,2361,315,2364,2370,2358,2118,72,2355,1355,1122,1358,1106,867,1108,398,342,343,344,345,346,347,1378,1123,612,1125,1382,873,109,879,1912,1409,646,391,392,393,395,397,654,400,402,610,1178,669,934,48,682,175,691,694,697,1467,1469,1365,1730,356,1379,1362,2381,632,986,477,995,742,488,1380,237,2352,1265,1779,1787,341],"/usr/local/lib/python3.5/dist-packages/sympy/polys/orderings.py":[256,1,3,5,7,8,137,10,11,13,14,15,17,188,20,23,154,156,26,159,32,162,35,36,38,39,129,169,42,199,45,46,29,48,49,51,180,54,55,184,57,58,187,60,63,192,193,194,195,196,113,177,183,245,185,186,251,105,107,110,157,40,242,243,117,248,121,191,126],"/usr/local/lib/python3.5/dist-packages/mpmath/calculus/differentiation.py":[448,1,2,67,4,5,6,7,521,522,13,206,580,68,223,224,546,360,297,31,306,393,449],"/usr/local/lib/python3.5/dist-packages/mpmath/identification.py":[512,513,514,515,4,517,6,7,520,521,10,523,524,13,14,527,528,17,530,533,534,516,518,519,429,312,522,838,839,840,843,525,464,440,526,529,487,504,505,506,507,508,509,510,511],"/media/androbin/Daten/git/phenny/modules/test/test_8ball.py":[1,2,3,5,6,7,8,10,11,12,13],"/usr/local/lib/python3.5/dist-packages/sympy/matrices/expressions/diagonal.py":[128,1,130,3,4,5,71,8,129,48,49,51,148,85,152,53],"/usr/local/lib/python3.5/dist-packages/urllib3/contrib/socks.py":[32,33,37,23,24,39,26,27,28,29,30],"/usr/local/lib/python3.5/dist-packages/sympy/polys/polyoptions.py":[512,1,514,3,516,5,518,7,8,9,10,11,13,15,528,17,18,20,533,22,24,25,27,28,30,517,34,38,553,43,44,46,560,561,50,563,52,565,566,568,60,61,63,64,578,71,72,586,587,76,589,591,592,595,596,598,600,527,605,606,608,610,612,616,530,622,623,625,627,632,532,122,635,124,125,127,192,642,643,534,648,649,651,653,536,658,659,661,663,668,669,671,673,677,685,686,688,690,183,696,698,187,700,189,190,704,193,195,198,199,712,713,715,204,717,725,633,216,222,736,235,637,245,767,256,257,259,261,262,775,264,269,270,272,274,275,277,281,520,49,298,299,301,303,304,306,308,325,326,328,330,331,333,337,347,348,350,352,353,355,359,364,365,367,369,370,373,374,376,378,379,382,383,385,387,391,392,395,396,695,398,400,401,403,405,406,407,408,409,410,412,645,75,186,485,496,497,499,501,502,503,505,511],"/usr/local/lib/python3.5/dist-packages/sympy/utilities/misc.py":[128,1,3,5,6,7,8,9,13,16,120,19,239,30,227,262,129,193,107,108,109,110,111,304,117,119,184,122,126],"/usr/local/lib/python3.5/dist-packages/mpmath/functions/elliptic.py":[1202,300,1034,344,917,153,154,671,672,289,290,291,292,293,294,295,296,297,298,299,556,301,302,303,306,307,439,826,187,188,63,65,67,288,856,345,474,92,93,123,232,233,749,122,507],"/usr/local/lib/python3.5/dist-packages/mpmath/matrices/matrices.py":[1,770,939,5,6,8,394,991,400,659,407,665,284,286,601,752,419,668,683,686,433,692,675,694,739,697,671,703,576,705,822,838,737,712,714,717,336,722,595,727,729,346,860,735,656,571,740,741,796,743,874,368,498,371,628,888,720,637,639],"/usr/local/lib/python3.5/dist-packages/pkg_resources/_vendor/packaging/specifiers.py":[674,708,726,717,625,594,724,610,598,599,725,606,702,703],"/usr/local/lib/python3.5/dist-packages/chardet/latin1prober.py":[130,29,30,32,34,35,36,37,38,39,40,41,42,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,92,96,97,103,108,112,116],"/usr/lib/python3/dist-packages/nose/plugins/logcapture.py":[64,193,194,195,196,69,198,199,204,76,77,78,79,80,209,82,86,89,88,217,207,222,208,34,38,39,40,41,71,44,49,178,179,52,58,59,60,65],"/media/androbin/Daten/git/phenny/__init__.py":[33,83,37,77,8,41,10,11,12,13,14,15,17,19,21,27],"/usr/local/lib/python3.5/dist-packages/chardet/euctwfreq.py":[385,44,47],"/usr/local/lib/python3.5/dist-packages/sympy/core/relational.py":[1,3,4,5,6,7,8,9,11,293,781,16,786,23,280,793,282,795,284,797,286,799,800,801,802,803,804,805,806,295,297,775,44,45,47,776,52,478,54,777,308,309,64,779,69,292,72,74,55,227,92,93,94,96,784,99,742,796,364,110,111,368,113,114,371,119,120,121,123,125,299,127,491,279,450,400,401,403,405,772,794,112,418,422,425,172,431,432,434,435,436,798,438,440,287,449,194,451,197,199,457,460,290,288,466,467,469,303,289,473,734,735,737,67,739,484,485,230,487,744,747,748,749,751,496,753,758,761,762,763,765,767],"/media/androbin/Daten/git/phenny/modules/test/__init__.py":[2,3],"/usr/local/lib/python3.5/dist-packages/urllib3/packages/ssl_match_hostname/__init__.py":[19,1,3,6,9],"/usr/local/lib/python3.5/dist-packages/sympy/plotting/textplot.py":[8,1,3,4,5],"/usr/local/lib/python3.5/dist-packages/sympy/core/sympify.py":[256,1,3,5,390,7,8,9,12,13,14,15,17,387,252,278,279,280,25,282,27,284,285,261,290,291,292,293,295,296,297,298,299,300,306,302,305,50,51,308,309,54,55,283,287,318,311,334,335,336,340,342,343,345,347,349,350,352,353,354,355,356,358,307,361,238,239,242,243,244,245,248,249,281,253,254,255],"/usr/local/lib/python3.5/dist-packages/sympy/functions/elementary/__init__.py":[1,2,3,4,5,6],"/usr/local/lib/python3.5/dist-packages/sympy/matrices/expressions/trace.py":[1,34,3,4,5,8,41,20,21,23,56,62,37],"/usr/local/lib/python3.5/dist-packages/sympy/solvers/recurr.py":[64,65,66,67,68,71,460,662,349,48,49,51,53,54,55,56,57,58,59,61,62,63],"/usr/local/lib/python3.5/dist-packages/mpmath/__init__.py":[1,3,5,6,7,9,10,11,13,14,15,16,17,18,19,20,23,24,25,27,28,30,31,32,33,34,35,36,38,40,42,43,44,45,46,47,48,49,51,52,53,54,56,58,59,60,62,64,65,66,68,69,70,71,72,73,74,75,77,78,80,82,83,85,86,88,90,91,92,94,95,96,97,98,99,101,102,103,104,106,107,108,109,111,112,113,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,139,140,141,142,143,144,145,146,147,148,149,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,174,175,176,177,178,179,181,182,183,184,185,186,187,188,189,190,191,193,194,195,196,197,198,199,200,201,202,203,204,205,206,208,209,211,212,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,290,291,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,371,372,373,374,375,376,377,378,379,380,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,400,401,402,403,404,405,406,407,408,409,410,411,412,413,414,415,416,417,418,419,420,421,422,423,424,425,428,439,463],"/usr/local/lib/python3.5/dist-packages/mpmath/libmp/__init__.py":[1,17,40,72,58,76,46,31],"/usr/local/lib/python3.5/dist-packages/urllib3/connectionpool.py":[1,2,3,4,5,7,8,11,795,26,27,28,29,544,35,36,38,39,40,41,554,43,46,547,50,52,565,54,58,571,572,62,64,65,67,68,71,72,73,599,75,588,589,79,593,82,598,87,600,601,95,98,613,614,615,616,617,620,622,570,626,627,630,631,633,635,638,639,646,858,651,652,175,654,658,660,826,156,158,159,160,162,163,164,165,167,168,681,170,172,173,541,176,178,179,181,182,184,185,188,189,192,193,194,196,709,710,202,206,207,208,210,211,212,213,215,548,734,737,227,228,229,551,753,242,755,756,758,759,760,761,762,251,764,42,767,768,769,771,854,774,775,776,777,778,267,268,269,783,789,790,791,792,793,794,283,796,797,798,287,800,289,293,295,298,299,779,305,818,781,822,905,824,607,830,831,832,322,836,837,838,840,842,823,846,849,338,340,341,342,345,346,861,354,357,360,363,369,372,375,889,378,379,380,381,382,383,576,780,903,393,394,395,396,398,399,405,407,580,410,415,417,418,419,420,421,423,424,426,584,670,850,586,447,448,449,450,765,76,77,591,763,610,594,253,680,852,853],"/usr/local/lib/python3.5/dist-packages/sympy/polys/factortools.py":[1,3,5,1161,1035,524,13,131,1173,1178,30,69,261,553,1322,455,48,773,645,841,1336,59,671,1341,63,68,197,71,140,74,75,1100,77,79,82,725,1239,480,1250,102,1131,364,466,625,122,379,893,254],"/usr/local/lib/python3.5/dist-packages/sympy/functions/special/error_functions.py":[2,4,1541,6,7,8,9,10,11,12,13,14,15,16,17,532,535,1048,538,27,542,2081,546,1883,549,552,1067,556,1581,559,2096,1800,1075,1590,1896,1080,1972,1594,1887,1086,577,523,2065,1548,1098,1975,1100,1101,1102,1549,1104,1107,526,1115,96,1552,98,100,529,107,189,113,1538,2068,638,641,2178,2179,2181,2182,1709,650,1675,192,1677,1678,143,1680,2194,1684,2197,1688,2200,156,159,1696,673,162,1051,676,166,679,1705,170,685,174,688,177,691,180,2229,694,183,697,186,700,1213,1216,705,708,201,1239,220,2204,1249,1253,1266,755,1269,758,1783,1273,1274,1275,764,1277,1792,770,1796,2309,2310,2177,1288,2313,2314,1804,1293,1807,1812,789,2326,2009,792,2329,2332,1970,2311,289,1314,291,293,299,305,1587,2056,2366,833,2370,836,2373,330,331,2018,1870,848,1873,1875,1879,345,859,348,862,351,2400,144,354,1891,357,2407,360,2411,364,1901,2415,368,2418,371,374,1401,378,1404,381,2430,1413,390,1089,2440,1420,910,913,1426,1429,1432,922,1437,1441,1443,2336,1446,1448,1692,1452,1456,946,1460,1973,1609,1979,842,1983,1987,1991,1995,2005,1785,1700,474,476,1786,478,1872,408,2019,484,2021,2023,2437,1788,2062,1518,1521,1528,511,682,510,1535],"/usr/local/lib/python3.5/dist-packages/mpmath/libmp/libelefun.py":[516,10,290,12,13,15,16,18,538,33,553,43,44,48,51,52,55,56,59,60,62,63,64,66,68,69,70,74,75,76,1122,156,99,85,1111,600,100,92,93,94,95,96,609,98,611,612,613,102,615,616,617,106,103,620,621,622,623,624,104,114,627,628,629,630,631,120,121,634,123,124,618,126,1151,640,641,642,619,132,645,646,647,648,649,650,651,652,653,654,655,656,657,658,144,664,153,154,155,668,157,673,162,676,625,680,682,1196,626,686,125,690,691,697,717,206,207,208,209,211,727,728,729,730,738,740,233,234,127,1263,131,250,639,253,265,130,784,1299,281,133,802,291,292,293,294,295,298,304,817,308,309,137,318,837,842,614,142,346,143,912,1378,1379,1380,1381,1382,1383,1384,1385,1386,877,1391,1086,1403,1415,400,610,665,923,666,422,937,954,146,962,983,101,168,1011,505,170],"/usr/local/lib/python3.5/dist-packages/sympy/functions/special/zeta_functions.py":[512,1,2,195,4,5,198,7,8,524,460,206,463,272,468,270,281,520,414,287,416,16,290,6,454,557,559,560,417,179,308,118,116,479,188,510],"/usr/local/lib/python3.5/dist-packages/mpmath/calculus/optimization.py":[384,1,386,3,5,6,577,521,522,140,13,400,1083,515,174,981,687,157,30,288,176,646,178,46,303,304,689,306,435,692,56,314,688,1084,445,1087,32,197,70,71,456,73,334,211,690,85,600,475,476,478,355,102,230,231,488,233,29,630,1085,1008,627,628,118,119,121,541,253,524,383],"/usr/local/lib/python3.5/dist-packages/sympy/matrices/expressions/transpose.py":[32,1,3,4,69,6,33,8,73,97,45,78,79,49,82,35,53,56,59,62,65],"/usr/local/lib/python3.5/dist-packages/sympy/core/evalf.py":[256,1282,1283,4,5,1286,7,1288,9,10,12,13,18,19,20,21,22,24,25,26,303,28,1309,30,31,1072,34,1243,1316,1317,806,295,40,41,1290,1287,44,813,47,48,1330,1333,1334,1336,825,1338,839,1034,1328,1419,836,822,812,1292,842,1264,525,1409,824,1251,852,1395,1370,1371,1373,863,1377,1254,1384,1386,1387,1388,1389,1391,880,1393,1394,275,1397,1398,1257,1148,1406,639,1408,897,386,1411,1412,1157,390,1417,811,139,1399,1421,1261,400,913,107,1428,1429,1431,826,1285,1293,112,1284,1407,109,177,1310,1203,1204,1276,183,834,1268,1311,815,1471,1220,1223,1225,1226,1227,1228,1229,1230,1231,1232,1233,210,1235,1236,1237,1238,1240,1241,1242,79,1244,1245,1246,1247,1248,1249,1250,483,1252,230,808,745,1258,807,1260,1234,1262,1008,1265,1266,244,1269,1270,1271,248,1273,1274,1275,252,1278],"/usr/local/lib/python3.5/dist-packages/mpmath/calculus/odes.py":[1,2,51,4,5,7,284,286],"/usr/local/lib/python3.5/dist-packages/sympy/polys/domains/gmpyfinitefield.py":[16,1,3,5,6,8,10,11,12,14],"/usr/local/lib/python3.5/dist-packages/lxml/html/defs.py":[131,9,10,11,13,14,15,18,19,20,21,23,28,29,30,31,32,33,36,37,38,39,40,41,42,43,44,45,46,49,50,53,54,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,77,78,81,82,83,88,90,93,94,95,98,99,100,101,104,105,106,109,110,113,114,117,118,119,120,121,122,126],"/usr/local/lib/python3.5/dist-packages/sympy/polys/polyutils.py":[320,1,386,3,388,5,6,65,8,457,10,459,13,15,336,18,19,20,21,22,23,24,478,27,28,150,31,228,421,455,391,387,173,302,113,178,342,308,158,372,314],"/usr/local/lib/python3.5/dist-packages/sympy/printing/pretty/__init__.py":[1,3,7],"/usr/local/lib/python3.5/dist-packages/sympy/solvers/diophantine.py":[1,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,20,21,22,23,24,25,26,27,28,29,2593,34,1828,806,39,40,41,42,43,44,45,46,47,48,49,50,53,2614,2057,2872,61,65,579,582,97,1098,75,1615,1872,2389,1111,88,346,92,96,1889,867,2157,2926,2415,3048,1651,629,1308,916,378,1403,1917,2430,2604,3209,3212,2196,2709,2073,2969,3226,3098,1696,2843,1959,2332,2742,1465,2238,2499,2678,3274,464,2808,2782,2271,80,860,1777,1525,2552],"/usr/local/lib/python3.5/dist-packages/sympy/polys/euclidtools.py":[1152,897,3,5,1,776,137,1850,396,1833,16,259,1044,1431,537,154,923,413,1531,32,673,678,1703,41,299,44,944,51,53,438,560,185,58,1567,316,1598,885,1090,1731,836,1480,457,202,55,972,1615,1635,600,1754,93,737,867,1382,1258,1133,110,1136,242,1683,1780,1013,809,1811,1275,1660],"/usr/local/lib/python3.5/dist-packages/sympy/printing/tableform.py":[1,3,4,6,33,9,236,205,240,209,35,244,213,319],"/usr/local/lib/python3.5/dist-packages/sympy/utilities/lambdify.py":[4,6,8,9,10,12,14,17,18,19,20,21,22,28,29,30,31,32,33,37,38,39,43,44,45,46,47,48,49,50,51,52,53,55,57,58,59,60,61,62,63,64,65,66,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,93,94,95,96,97,98,99,102,106,107,108,109,110,111,112,113,114,115,119,657,177,178,179,457,589,466,481],"/usr/local/lib/python3.5/dist-packages/lxml/__init__.py":[3],"/usr/local/lib/python3.5/dist-packages/chardet/sbcsgroupprober.py":[34,35,37,38,39,40,43,44,29,30,31],"/usr/local/lib/python3.5/dist-packages/sympy/concrete/expr_with_limits.py":[1,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,75,403,20,398,344,207,349,158,261,408,347,77,139,424,176,393,120,212,266,74],"/media/androbin/Daten/git/phenny/modules/wikipedia.py":[8,10,12,13,16,17,18,21,24,25,28,29,31,33,34,36,37,38,40,42,45,48,50,51,53,54,57,59,60,61,68,70,71,72,73,76,82,83,84,87,93,94,95,98,107,110],"/usr/lib/python3/dist-packages/nose/plugins/errorclass.py":[144,147,149,150,151,152,153,139,140,142,143],"/usr/local/lib/python3.5/dist-packages/mpmath/matrices/eigen_symmetric.py":[577,578,505,1729,653,654,340,727,728,1730,1627,1628,898,38,40,41,170,44,1197,305,1522,1523,504,377],"/usr/lib/python3/dist-packages/nose/proxy.py":[64,155,158,177,78,80,81,82,83,110,153,154,111,156,30,159,160,112,163,164,165,102,103,104,169,170,43,45,46,47,176,168,178,116,117,118,57,58,59,60,62],"/usr/local/lib/python3.5/dist-packages/sympy/simplify/fu.py":[1773,1494,1601,2058,524,399,1555,665,283,1180,1727,1055,1612,545,1614,807,939,1965,1783,1584,1523,566,439,1465,187,189,191,1344,193,194,195,197,199,200,201,202,203,204,205,206,207,208,209,210,211,212,590,214,220,1629,1630,1631,1632,1616,1636,229,2022,2096,1772,1618,1776,467,1780,1227,759,1620,1019,253],"/usr/local/lib/python3.5/dist-packages/urllib3/util/selectors.py":[256,8,9,10,11,12,13,14,16,17,21,22,279,24,281,26,539,29,30,287,288,289,34,548,37,294,41,556,557,302,559,48,392,53,51,308,565,312,436,58,59,266,574,181,64,581,437,336,337,338,339,270,343,570,353,571,358,63,402,573,372,558,127,130,131,133,390,391,136,393,394,139,396,399,400,401,146,403,404,150,25,409,282,453,278,419,164,165,406,167,389,170,427,172,47,433,435,180,286,438,439,543,134,192,449,194,451,407,197,199,456,203,204,418,206,420,421,226,550,44,245,250,452,253,254],"/usr/local/lib/python3.5/dist-packages/sympy/solvers/decompogen.py":[1,2,5,61],"/media/androbin/Daten/git/phenny/modules/wiktionary.py":[8,10,11,12,13,15,16,17,19,20,21,22,23,24,25,26,27,29,30,31,32,33,34,35,36,37,38,39,41,42,43,44,45,47,48,49,50,52,53,54,55,56,58,59,60,62,64,66,68,70,72,79,80,82,83,85,87,95,106,131,133,134,135,136,137,138,139,140,141,144,145,147,148,149,150,151,154,156,158,161,162,163,164,165,166,167,168,173,176,178,180,182,183,185,186,187,195,197,198,199,202,207,208,211,215,216,219,230,231,233],"/usr/local/lib/python3.5/dist-packages/sympy/core/expr.py":[1,3,4,5,6,7,8,9,10,3038,12,14,3005,3091,2990,2071,3098,27,29,31,1968,717,3108,3334,1969,3113,3118,1072,3080,3123,1077,3128,1716,3133,62,63,3138,1025,3143,3148,3153,3086,3158,3163,186,606,187,3055,2664,105,107,110,111,113,2675,117,118,3177,120,122,123,127,2176,3061,130,132,133,2695,137,138,140,142,143,147,148,150,152,153,157,158,1989,160,162,163,2724,165,1137,168,2217,170,171,175,176,236,180,181,1720,2932,1722,3103,1029,192,262,3041,1733,3273,714,715,716,3362,324,3279,3280,3281,3283,3285,3286,3287,3290,3293,315,1759,3296,1761,3299,228,230,3303,744,745,746,747,748,237,750,239,752,241,2290,755,757,246,2295,2808,249,1437,252,253,2814,256,128,258,259,261,774,263,266,268,762,2833,3348,3350,3355,1987,290,3366,3367,1834,3037,3376,3377,3378,3379,3380,2869,3382,312,313,314,2363,318,3381,2368,322,1860,325,327,328,329,843,332,334,848,2872,1763,854,858,347,3012,3045,2912,2914,867,2916,871,2920,2921,2791,1717,371,884,373,3049,888,234,319,1917,2433,2946,2947,2948,2949,2438,1928,1417,1930,2958,320,2962,2963,1943,2884,2971,925,2975,929,764,753,2985,1798,1966,1693,2992,2993,2994,1971,1973,1976,3002,3004,1981,2495,3008,451,1988,965,3015,1992,247,2001,1997,2000,248,2002,761,3035,3007,1501,1502,3039,1505,3042,3043,3044,2021,2022,3047,3048,2025,167,3052,3053,3054,765,3058,3059,235,254,1021],"/media/androbin/Daten/git/phenny/test/test_bot.py":[3,5,6,7,10,11,13,14,15,16,17,18,19,20,21,22,23,24,26,28,29,30,31,33,34,35,36,37,39,40,41,42,43,44,45,46,48,49,50,51,53,54,55,56,57,58,60,62,63,64,65,67,68,69,70,71,72,74],"/media/androbin/Daten/git/phenny/modules/test/test_commit.py":[17,4,5,6,7,8,11,12,13,15],"/usr/local/lib/python3.5/dist-packages/sympy/functions/special/delta_functions.py":[1,450,3,4,5,6,7,8,9,10,11,268,577,19,139,203,535,355,331,413,95,97,99,359,443,199,449,369,306,599,417,415,508,138],"/media/androbin/Daten/git/phenny/modules/test/test_archwiki.py":[4,5,6,7,8,9,12,14,15,16,18,19,21,22,23,25,27,28,30,31,33,34,36,37,39,41,42,44,45,47,48,50,52,53,54,56,57,59,60,62,64,65,66,68,69,71,72,74,76,77,79,80,82,83],"/usr/local/lib/python3.5/dist-packages/chardet/eucjpprober.py":[32,33,36,37,44,48,52,56,89,28,29,30,31],"/usr/local/lib/python3.5/dist-packages/chardet/euckrprober.py":[34,35,41,45,28,29,30,31],"/media/androbin/Daten/git/phenny/modules/remind.py":[8,10,11,12,13,14,16,22,25,26,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,44,45,46,47,50,51,52,53,55,56,57,59,60,61,62,63,65,66,67,69,70,71,72,73,75,76,77,78,79,81,82,83,84,85,88,89,90,92,94,95,97,99,100,101,103,105,107,108,110,111,113,115,121,122,123,124,126,127,129,131,175,177],"/media/androbin/Daten/git/phenny/modules/rule34.py":[32,33,34,36,5,7,8,9,11,14,15,19,20,21,22,23,24,25,27,28],"/usr/local/lib/python3.5/dist-packages/mpmath/functions/zeta.py":[384,1,2,43,4,5,522,398,528,529,515,409,795,668,925,287,163,164,165,166,167,168,169,42,171,172,173,174,175,176,177,178,179,180,181,182,185,1055,445,193,194,582,583,332,76,206,849,84,462,733,734,869,924,229,796,489,490,619,237,829,372,373,502,503,888,251,170],"/usr/local/lib/python3.5/dist-packages/mpmath/functions/bessel.py":[1,642,3,646,385,8,905,650,396,258,14,402,643,788,277,406,152,281,410,539,285,414,240,538,906,424,297,647,175,179,693,183,697,827,701,446,447,197,326,714,715,386,13,80,81,339,852,398,702,352,737,738,614,849,387,368,753,754,211,116,117,249,1019,1020],"/usr/local/lib/python3.5/dist-packages/sympy/polys/densetools.py":[1,898,3,5,1031,267,143,784,401,19,663,1050,411,540,929,34,293,39,1169,41,42,44,437,1207,696,186,958,831,576,322,1223,76,1143,332,1100,467,853,987,732,356,487,233,618,875,1261,111,1295,369,243,758,1015,121,511],"/media/androbin/Daten/git/phenny/modules/lastfm.py":[128,129,131,133,6,8,9,10,11,12,13,14,143,16,17,146,19,20,21,23,156,157,159,161,167,168,41,170,173,174,176,200,179,138,202,192,194,196,69,198,71,72,73,74,75,76,77,78,80,81,82,83,84,85,121,169,93,94,95,144,98,100,103,104,105,106,107,109,111,113,114,117,118,119,120,148,123],"/usr/local/lib/python3.5/dist-packages/sympy/geometry/curve.py":[193,145,311,7,72,9,74,11,12,13,14,15,17,20,85,246,281,89,220,167,329],"/usr/local/lib/python3.5/dist-packages/chardet/gb2312prober.py":[33,34,40,44,28,29,30,31],"/usr/local/lib/python3.5/dist-packages/sympy/geometry/plane.py":[515,580,7,8,329,10,11,12,13,14,15,16,17,18,723,20,21,22,409,88,25,474,283,605,222,671,554,173,72,434,51,52,308,136,763,533],"/usr/local/lib/python3.5/dist-packages/mpmath/libmp/libintmath.py":[7,9,10,87,12,13,15,245,275,276,46,89,541,291,39,298,300,558,50,307,53,312,313,58,315,60,61,318,63,64,65,66,68,81,83,85,86,343,88,345,347,348,349,350,351,352,353,354,355,356,357,358,103,360,106,111,112,114,62,118,119,376,378,123,382,277,128,130,392,141,398,399,401,163,91,185,188,445,190,191,192,193,194,195,197,100,202,247,204,206,207,271,314,223,464,295,241,242,243,244,501,246,503,249],"/usr/local/lib/python3.5/dist-packages/chardet/sjisprober.py":[32,33,36,37,44,48,52,56,89,28,29,30,31],"/usr/local/lib/python3.5/dist-packages/chardet/escprober.py":[35,69,40,73,42,77,83,58,28,29,30,31],"/usr/local/lib/python3.5/dist-packages/sympy/core/power.py":[1,258,3,5,6,7,8,9,10,12,13,14,15,17,205,531,1046,23,30,547,1064,61,561,564,56,57,826,59,522,63,19,65,66,67,1025,1095,1036,1085,1105,341,345,102,1135,1393,371,1399,634,1404,1407,390,1168,658,707,408,670,241,425,684,948,182,183,696,185,187,188,189,190,191,193,194,195,196,198,201,202,203,484,206,207,1488,209,210,212,215,480,227,228,229,1511,237,1518,1519,1520,1521,245,249,1019],"/usr/local/lib/python3.5/dist-packages/sympy/polys/domains/complexfield.py":[1,3,5,6,8,9,10,11,12,14,15,16,18,20,22,23,25,26,28,30,34,38,42,46,47,48,49,51,52,53,55,60,63,67,77,80,83,86,89,92,98,102,106,110,114],"/media/androbin/Daten/git/phenny/irc.py":[258,259,8,265,10,11,12,13,14,15,16,17,18,19,20,21,22,24,27,28,30,31,32,33,34,36,37,38,40,41,44,45,46,47,48,50,51,52,53,55,56,58,59,61,63,64,65,67,72,268,75,76,79,81,84,85,86,87,89,90,92,93,94,95,96,100,101,273,106,111,183,126,127,136,137,139,147,150,151,153,157,160,161,162,164,166,167,171,173,175,177,178,180,181,73,184,186,187,189,190,193,194,198,199,205,214,215,216,217,218,219,220,223,224,225,226,230,231,232,234,236,237,238,240],"/usr/lib/python3/dist-packages/nose/core.py":[193,66,199,200,201,202,203,204,205,207,34,187,36,37,65,41,42,43,44,61,50,51,55,56,59,188,60,62],"/media/androbin/Daten/git/phenny/modules/test/test_apy.py":[5,6,7,8,9,10,11,12,15,16,18,19,20,22,23,24,26,27,28,29,30,35,38,39,41,42,43,45,46,47,49,50,51,52,53,54,55,56,58,60,61,62,63,64,65,68,69,70,71,72,73,74,75,76,77,79,81,84,85,86,87,88,89,92,93,94,95,96,99,100,101,102,103,104,105,106,107,109,110,113,114,115,116,119,120,121,122,123,124,125,127,128,129,130,131,132,135,136,137,138,139,142,143,144,145,146,147,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,166,169,170,171,172,173,174,175,176,177,178,181,182,183,184,185,186,187,188,189,192,193,195,197,198,199,200,201,202,203,204,205,207,209,210,211,212,213,214,216,218,219,220,221,222,223,224,227,229,231,232,234,235,237,238,239,240,241,242,243,244,245,246,247,248,251,252,253,254],"/usr/local/lib/python3.5/dist-packages/pkg_resources/_vendor/packaging/_structures.py":[34],"/usr/local/lib/python3.5/dist-packages/sympy/solvers/inequalities.py":[1,3,196,5,6,129,8,9,10,11,13,14,15,16,17,274,19,596,355,7,647,111,561,574,382],"/usr/local/lib/python3.5/dist-packages/mpmath/functions/factorials.py":[64,1,2,4,5,129,72,41,160,80,197,68,60,133],"/usr/local/lib/python3.5/dist-packages/sympy/matrices/immutable.py":[64,1,3,4,6,7,8,9,74,12,77,14,16,147,148,149,151,95,33,163,37,38,39,104,41,71,44,46,112,108,172,169,120,123,126,166],"/usr/local/lib/python3.5/dist-packages/sympy/integrals/transforms.py":[1,770,3,772,5,6,7,8,9,10,11,12,13,14,15,870,23,792,281,1306,1819,1565,1310,773,1560,33,34,291,293,1739,1063,40,809,1067,1070,815,776,1074,1763,1225,54,56,1338,315,61,1089,66,1603,1334,71,1656,1504,1741,1613,1614,1549,80,1336,83,1341,86,1623,1827,91,1736,702,354,1125,1126,1569,1389,957,1393,1396,958,1400,1771,1215,299,1557,1664,1472,1666,1667,1669,1345,1672,1559,1303,1676,397,1773,400,401,1326,404,1391,156,1562,1440,161,1381,164,1478,167,1196,174,1713,1714,1204,1206,1207,1208,1210,1055,1468,189,190,703,192,1474,1483,1222,199,200,201,759,1314,1231,208,1489,774,1443,212,213,1751,1496,1444,1755,1829,783,1616,1506,1507,1509,1512,1319,1516,289,1832,1776,296,1619,1065,1304,1748,762,1611,1278,1279],"/usr/local/lib/python3.5/dist-packages/sympy/interactive/__init__.py":[1,3,4],"/usr/lib/python3/dist-packages/nose/plugins/debug.py":[40,41,42,43],"/usr/local/lib/python3.5/dist-packages/lxml/html/_setmixin.py":[32,1,3,38,7,9,44,15,50,35,21,22,41,24,25,26,27,29],"/usr/local/lib/python3.5/dist-packages/sympy/strategies/tree.py":[1,3,4,5,6,8,41,10,110,133],"/usr/local/lib/python3.5/dist-packages/sympy/tensor/array/__init__.py":[208,201,203,204,205,206],"/usr/local/lib/python3.5/dist-packages/sympy/calculus/euler.py":[10,4,5,6,7],"/usr/local/lib/python3.5/dist-packages/pbr/version.py":[429,19,21,22,23,462,26,27,28,29,34,38,41,42,54,55,56,57,58,59,317,63,68,325,71,330,331,403,335,342,344,349,94,351,104,361,107,110,61,113,370,275,116,119,386,387,398,144,145,402,195,404,149,407,152,409,154,156,157,158,415,416,417,418,420,424,172,173,174,175,176,177,178,180,438,439,440,441,442,188,189,449,451,196,197,198,458,160,460,461,206,464,211,469,470,472,223,224,225,226,232,234,236,238,246,202],"/usr/local/lib/python3.5/dist-packages/mpmath/functions/theta.py":[864,1,3,796,417,585,988,910,911,756,216,987,828],"/media/androbin/Daten/git/phenny/modules/more.py":[129,130,131,132,5,7,8,10,11,13,14,16,21,23,24,26,27,28,30,31,33,35,36,38,39,40,42,44,45,47,49,50,51,53,56,57,59,64,65,67,68,69,70,72,74,75,77,78,80,81,84,86,88,89,91,92,95,97,99,101,103,104,105,107,109,110,111,113,114,116,118,119,123,125,127],"/usr/local/lib/python3.5/dist-packages/sympy/ntheory/__init__.py":[16,18,3,5,7,8,11,12,21],"/usr/local/lib/python3.5/dist-packages/pkg_resources/_vendor/packaging/requirements.py":[96,97,105,54,57,104,89,90,107,106],"/usr/local/lib/python3.5/dist-packages/sympy/series/order.py":[128,1,3,4,5,6,7,8,9,458,12,270,463,320,273,466,323,277,471,475,284,418,291,326,295,412,302,122,124,126],"/usr/local/lib/python3.5/dist-packages/urllib3/filepost.py":[1,2,4,5,7,8,9,11,14,21,41,59],"/usr/local/lib/python3.5/dist-packages/urllib3/request.py":[1,3,4,37,7,72,41,10,39,44,45,50,89,90,42],"/usr/local/lib/python3.5/dist-packages/sympy/utilities/__init__.py":[16,18,3,4,20,10,12,14],"/usr/local/lib/python3.5/dist-packages/sympy/concrete/__init__.py":[1,2],"/usr/local/lib/python3.5/dist-packages/sympy/solvers/polysys.py":[1,3,5,6,7,8,10,11,14,15,18,52,238,101],"/usr/local/lib/python3.5/dist-packages/mpmath/libmp/backend.py":[1,2,21,22,23,25,28,30,32,34,39,40,41,44,45,46,49,54,55,56,57,58,59,60,61,62,66,67,69,70,71,77,78,80,83,85,86,87,88,89,90,92,93,94,97,98,99],"/usr/local/lib/python3.5/dist-packages/sympy/strategies/branch/core.py":[1,2,4,69,6,9,42,107,79,30,22,91,62,53],"/usr/local/lib/python3.5/dist-packages/mpmath/libmp/six.py":[256,1,260,261,262,257,266,270,274,22,23,24,26,27,284,31,33,34,35,36,37,38,40,263,301,302,305,306,307,310,316,317,67,69,292,72,78,80,81,83,91,93,94,95,96,97,98,102,106,108,109,110,111,113,114,115,116,118,119,126,383,386,282,132,133,137,138,139,140,141,142,143,144,145,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,185,186,187,189,192,279,197,289,290,208,209,210,291,212,213,215,216,217,280,230,231,235,238,239,242,244],"/usr/local/lib/python3.5/dist-packages/sympy/core/operations.py":[128,1,3,4,5,262,7,8,11,432,21,414,408,25,410,27,412,29,30,31,33,418,36,6,38,39,296,41,42,43,44,429,430,413,48,433,50,51,415,53,438,55,56,436,58,59,444,61,62,64,54,454,455,459,368,37,420,442,421,352,46,434,112,369,371,374,375,378],"/usr/local/lib/python3.5/dist-packages/sympy/matrices/expressions/matpow.py":[1,3,4,5,6,7,10,12,78,19,23,27,51,31],"/usr/local/lib/python3.5/dist-packages/requests/__version__.py":[5,6,7,8,9,10,11,12,13,14],"/usr/local/lib/python3.5/dist-packages/sympy/ntheory/egyptian_fraction.py":[1,131,4,5,6,7,10,13,145,3,182,191,166],"/usr/local/lib/python3.5/dist-packages/sympy/polys/domains/field.py":[32,1,3,36,5,6,7,40,9,10,11,13,14,16,20,24,28,93,69],"/usr/local/lib/python3.5/dist-packages/sympy/printing/codeprinter.py":[1,258,3,4,5,6,7,8,9,10,11,14,17,20,21,23,26,29,30,31,34,35,36,37,39,297,46,310,183,333,340,344,346,348,350,352,355,104,365,360,372,379,383,197,261,425,430,431,432,433,434,435,436,437,438,439,440,441,442,443,444,445,446,447,448,449,450,451,452,453,454,455,456,457,458,459,460,461,462,331,220,229,234,239,244,251],"/media/androbin/Daten/git/phenny/modules/test/test_nsfw.py":[4,5,6,7,8,11,12,13,14,16,17,18,19,20,22,23,24,25,26],"/usr/local/lib/python3.5/dist-packages/chardet/mbcharsetprober.py":[34,37,39,61,57,90,53,45,30,31],"/usr/local/lib/python3.5/dist-packages/mpmath/rational.py":[1,2,3,5,51,7,8,9,236,11,140,13,142,222,17,18,20,21,22,24,27,29,31,32,33,155,36,38,168,41,44,48,179,181,10,53,192,72,12,203,83,14,217,218,219,220,221,94,223,224,225,226,227,105,106,107,108,237,110,238,118,124,127],"/media/androbin/Daten/git/phenny/modules/test/test_hs.py":[4,5,6,7,8,9,12,13,14,15,17,19,20,21,22,24,26,27,28,29,30,31,32,34,36,37,38,39,40,41,42,44,46,47,48,49,51,53,54,55,56],"/usr/local/lib/python3.5/dist-packages/sympy/core/__init__.py":[2,4,5,6,7,8,9,10,13,14,15,16,17,20,21,26,27,28,29,32,33,34],"/usr/local/lib/python3.5/dist-packages/sympy/sets/__init__.py":[1,3,4,5],"/usr/local/lib/python3.5/dist-packages/sympy/polys/domains/pythonintegerring.py":[1,3,5,6,65,72,76,11,12,14,15,16,18,19,20,21,23,88,26,30,80,39,43,47,52,56,84,60],"/usr/local/lib/python3.5/dist-packages/sympy/strategies/branch/tools.py":[1,3,4,6],"/media/androbin/Daten/git/phenny/metar.py":[299,16,273,274,19,20,21,280,25,26,27,28,29,30,31,32,289,290,291,36,37,38,39,40,41,42,43,44,302,48,49,50,51,52,53,54,58,59,60,61,62,63,64,65,69,70,71,72,73,76,79,80,81,82,83,84,85,86,87,88,89,91,272,275,120,276,277,141,281,282,283,165,166,167,284,170,171,172,173,174,175,176,177,178,181,182,184,187,188,190,191,193,194,195,198,199,200,201,202,209,210,211,212,214,216,217,220,227,294,231,232,233,234,295,236,237,247,248],"/usr/local/lib/python3.5/dist-packages/mpmath/ctx_fp.py":[1,3,4,5,7,9,10,12,16,18,19,22,23,24,26,28,30,32,33,34,35,37,39,40,42,43,44,45,46,47,48,51,53,54,59,60,61,63,70,71,72,73,74,75,76,77,78,79,80,81,82,84,86,89,92,95,100,107,108,110,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,144,148,151,154,155,157,162,172,181,193,196,199,215,218,224,226,232,234,237,241,242,244],"/usr/local/lib/python3.5/dist-packages/mpmath/calculus/__init__.py":[1,3,4,5,6],"/usr/local/lib/python3.5/dist-packages/sympy/printing/pretty/pretty.py":[1,3,1540,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,1556,21,22,23,25,26,2075,30,1797,33,34,37,38,39,990,42,43,44,45,46,47,2097,50,1587,2100,54,2107,61,2111,65,2082,1604,71,1548,1610,76,98,79,81,1618,2132,1038,601,1626,2141,97,1122,99,100,101,102,103,1128,105,1642,2151,112,2067,89,121,1215,1149,1153,130,139,1678,1812,2199,708,1690,155,670,1187,676,678,1706,1699,1709,686,1202,180,1566,201,2079,2052,1727,1728,1729,1731,1732,1654,1225,1570,1166,207,1827,213,2169,1753,219,732,2085,225,740,1254,231,1768,1771,1260,237,752,2088,1778,1235,1781,247,760,1275,253,1794,2091,772,261,1286,779,1804,269,1806,1809,787,1300,2094,1815,1818,796,1821,798,803,1320,1833,1315,1325,305,1844,675,1334,1852,1971,1858,324,1864,1875,341,1886,1898,1938,573,880,1910,1404,1919,909,910,911,912,914,2115,1292,68,2202,417,1955,1963,2034,1457,2009,243,1634,757,1984,970,2125,2000,163,472,985,2012,1502,2015,251,2024,2029,1519,1522,2049],"/usr/local/lib/python3.5/dist-packages/sympy/polys/constructor.py":[1,3,5,6,7,8,9,10,108,13,210,220,65],"/usr/local/lib/python3.5/dist-packages/sympy/integrals/singularityfunctions.py":[8,1,3,4,5],"/usr/lib/python3/dist-packages/nose/importer.py":[128,129,130,131,132,143,144,146,147,148,149,150,151,152,153,154,155,156,157,158,32,161,40,41,42,44,45,47,30,54,59,53,62,63,65,66,67,68,70,71,72,74,75,76,77,78,79,80,81,85,86,89,94,96,97,98,99,100,101,102,103,104,110,111,116,117,118,119,126,127],"/usr/local/lib/python3.5/dist-packages/sympy/functions/special/singularity_functions.py":[1,3,4,5,6,7,8,9,202,171,108,16,81,83,85,201,185],"/media/androbin/Daten/git/phenny/modules/test/test_queue.py":[1,2,4,5,7,9,10,11,13,14,15,16,18,20,21,23,24,26,27,29,30,32,33,35,36,38,39,40,42,44,45,46,47,49,50,51,53,54,55,56,58,59,60,61,63,64,65,66,68,69,70,72,73,74,76,77,78,80,81,82,84,85,86,88,89,91,92,93,95,96,98,99,100,102,103,105,106,107,109,110,112,113,114,116,117,119,120,121,123,124,126,127,128,130,131,133,134,135,137,138,140,141,142,144,145,147,148,149,151,152,153,154,156,157,158,160,161,163,164,165,167,168,170,171,172,173,174,176,178,179,180],"/usr/local/lib/python3.5/dist-packages/sympy/interactive/printing.py":[256,1,3,36,5,6,7,9,10,11,12,15,257,233,251,252,253,254,255],"/media/androbin/Daten/git/phenny/modules/urbandict.py":[65,68,5,70,7,8,9,10,13,14,15,80,81,18,84,23,24,89,90,27,28,93,69,32,16,35,38,17,41,42,71,29,46,47,25,56,57,58,31,74],"/usr/lib/python3/dist-packages/humanize/compat.py":[1,3,6],"/usr/lib/python3/dist-packages/humanize/filesize.py":[8,9,4,13,7],"/usr/local/lib/python3.5/dist-packages/sympy/strategies/tools.py":[1,34,3,4,5,7,25,46],"/usr/local/lib/python3.5/dist-packages/sympy/assumptions/refine.py":[1,240,3,4,7,233,234,235,236,173,238,237,48,241,75,219,239],"/usr/local/lib/python3.5/dist-packages/sympy/ntheory/generate.py":[195,4,5,198,7,8,11,140,13,14,493,765,17,24,90,676,613,39,42,44,173,111,48,53,374,184,442,831,273,575],"/usr/local/lib/python3.5/dist-packages/sympy/printing/jscode.py":[130,196,8,10,12,13,14,15,16,22,23,24,25,26,27,28,29,30,31,32,33,34,38,167,40,41,42,45,46,47,48,49,50,53,59,62,65,68,71,74,162,78,91,101,105,304,115,118,121,124,127],"/usr/local/lib/python3.5/dist-packages/mpmath/ctx_mp_python.py":[1025,3,5,524,28,29,542,31,33,34,35,36,39,44,45,47,1074,51,52,565,180,571,572,574,64,578,581,583,584,585,586,587,588,610,590,591,80,593,82,595,596,597,598,600,601,602,603,605,606,607,608,98,611,612,614,618,619,621,1137,1138,1139,1140,121,122,123,124,126,127,129,131,132,134,139,140,141,142,143,144,145,147,149,664,155,161,162,163,164,165,167,540,114,176,177,178,179,692,182,201,188,199,200,460,202,203,204,210,724,216,548,222,225,228,231,764,811,878,262,264,265,77,275,277,278,279,280,281,282,283,284,285,589,287,288,289,290,292,293,294,295,297,298,299,300,302,303,304,305,307,308,309,310,312,313,314,315,317,318,319,320,322,323,324,325,328,332,334,335,336,337,338,339,341,57,348,353,357,1084,362,364,366,367,368,370,373,374,375,376,378,379,381,384,387,394,397,400,406,559,412,418,424,427,429,1003,432,440,449,455,76,458,459,972,461,463,78,982,477,592,1002,491,1004,1007,1009,1010,1022,1021,510,1023],"/usr/local/lib/python3.5/dist-packages/chardet/euckrfreq.py":[41,43,193],"/usr/local/lib/python3.5/dist-packages/sympy/polys/rationaltools.py":[1,3,5,6,7,8,10,11],"/usr/local/lib/python3.5/dist-packages/sympy/integrals/meijerint.py":[512,51,1409,47,930,1565,685,1944,1939,407,836,281,282,27,28,285,30,31,32,33,546,35,36,549,38,39,40,41,42,43,300,45,46,303,304,561,1408,307,54,521,1720,698,863,572,63,1858,579,48,1270,34,719,1233,338,467,1551,2012,1885,37,1632,1595,444,1259,492,1391,368,1945,674,374,1915,1404,1407],"/usr/local/lib/python3.5/dist-packages/sympy/polys/domains/realfield.py":[1,3,5,6,7,8,10,11,12,14,15,16,18,20,22,23,24,26,27,29,31,35,39,43,47,48,49,50,52,53,54,56,61,64,68,77,80,83,86,89,95,99,103,107,112,116,120],"/usr/local/lib/python3.5/dist-packages/sympy/strategies/core.py":[1,2,4,6,8,10,15,81,83,89,91,93,31,96,98,100,38,17,29,48,72,62,36],"/usr/local/lib/python3.5/dist-packages/sympy/polys/domains/fractionfield.py":[1,66,3,5,6,129,8,9,74,11,12,13,78,15,141,17,18,20,86,121,90,94,133,99,70,39,42,82,46,125,113,50,117,54,137,57,60,106],"/usr/local/lib/python3.5/dist-packages/chardet/jisfreq.py":[322,44,47],"/media/androbin/Daten/git/phenny/modules/ethnologue.py":[5,7,8,9,10,12,14,22,23,24,25,26,27,28,29,30,31,32,34,43,44,45,47,117,118,119,120,122,123,61],"/usr/local/lib/python3.5/dist-packages/sympy/polys/domains/compositedomain.py":[16,1,18,3,5,6,8,10,11,12,14],"/usr/local/lib/python3.5/dist-packages/sympy/matrices/expressions/hadamard.py":[64,1,34,3,4,6,71,8,80,82,47,48,50,83,57,79,61,68],"/usr/local/lib/python3.5/dist-packages/sympy/simplify/epathtools.py":[32,1,3,4,6,227,9,139,144,131,284,119,122,28,157,30],"/usr/local/lib/python3.5/dist-packages/sympy/matrices/expressions/matadd.py":[64,1,3,4,6,7,8,9,10,75,12,13,14,16,82,107,110,27,28,30,39,43,108,109,46,111,112,49,114,115,52,73,56,74],"/usr/local/lib/python3.5/dist-packages/sympy/core/logic.py":[258,259,260,262,263,8,9,11,268,269,14,271,274,278,281,283,287,290,292,293,294,295,40,297,299,301,303,304,307,308,309,265,312,314,317,318,320,321,322,323,324,325,326,327,328,329,331,332,77,78,335,336,81,338,83,340,270,86,343,89,80,106,363,364,109,366,112,371,373,374,375,377,379,380,382,384,385,390,392,108,395,396,397,148,82,171,192,193,195,197,198,199,200,202,205,206,208,209,210,212,214,79,220,225,234,237,239,247,248,249,251,252,85,255],"/usr/local/lib/python3.5/dist-packages/sympy/polys/sqfreetools.py":[256,1,3,5,449,330,268,14,273,83,21,27,221,32,418,35,486,358,39,61,177,184,189,127],"/usr/local/lib/python3.5/dist-packages/chardet/euctwprober.py":[33,34,40,44,28,29,30,31],"/usr/local/lib/python3.5/dist-packages/sympy/functions/special/bsplines.py":[128,1,3,4,5,6,33,9],"/usr/lib/python3/dist-packages/nose/result.py":[103,81,82,89,88,25,26,91,92,90,94,95,96,38,39,40,41,106,43,44,109,110,93,48,49,50,51,52,105,104],"/usr/local/lib/python3.5/dist-packages/urllib3/util/retry.py":[1,2,3,4,5,6,7,9,273,17,20,277,23,24,27,285,289,292,294,257,301,304,307,310,312,313,314,317,319,320,331,335,336,337,339,340,341,342,343,344,345,347,349,351,401,380,382,383,384,385,387,388,394,279,142,144,145,147,150,152,153,154,155,157,158,159,160,162,166,167,168,169,170,171,172,173,175,176,177,178,179,180,181,182,183,184,186,187,189,190,203,217,233,243,251],"/usr/local/lib/python3.5/dist-packages/sympy/polys/polyquinticconst.py":[132,10,23,12,14,15,16,17,18,19,21,151,24,25,26,157,94,33,163,39,174,113,180,186],"/usr/local/lib/python3.5/dist-packages/sympy/utilities/timeutils.py":[1,3,5,6,39,40,41,10,11,78,45,14,8,58,46,43,55,56,49,59,42],"/usr/local/lib/python3.5/dist-packages/sympy/matrices/expressions/adjoint.py":[32,1,3,4,5,6,33,9,42,46,50,35,53,56,59,63],"/usr/local/lib/python3.5/dist-packages/idna/intranges.py":[34,38,6,8,10,31],"/usr/local/lib/python3.5/dist-packages/sympy/ntheory/residue_ntheory.py":[960,3,5,6,7,8,9,10,11,12,1058,16,662,87,152,1018,478,1104,809,1254,999,872,361,583,619,647,301,697,687,1000,745,1205,183,267,826,59,215],"/usr/local/lib/python3.5/dist-packages/six.py":[1,2,3,4,5,6,7,8,9,10,12,14,16,17,21,23,24,25,26,27,28,29,31,32,35,36,37,38,39,40,41,42,43,44,45,47,49,51,53,55,57,59,61,63,65,67,69,71,73,75,77,79,80,81,82,83,85,86,87,88,89,91,92,93,94,97,100,103,105,106,107,108,109,110,114,115,117,124,126,127,128,130,136,139,141,142,143,144,146,147,148,149,151,152,159,164,171,173,174,175,177,178,179,181,182,184,185,187,189,195,209,218,224,226,229,231,232,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,305,310,311,312,313,314,316,318,319,322,324,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,354,355,356,358,360,361,364,366,370,371,372,374,375,376,378,380,381,384,386,390,391,392,393,394,395,396,397,398,399,400,401,402,403,404,405,406,407,408,409,410,411,412,413,414,415,416,417,418,419,420,421,422,423,424,426,427,428,430,432,433,436,438,442,443,444,445,447,448,449,451,453,454,457,459,463,465,466,467,469,471,472,475,477,478,479,480,481,482,483,485,488,489,492,497,508,509,510,512,513,514,515,526,527,531,534,535,541,542,545,547,550,567,568,571,572,573,574,575,576,579,580,583,586,589,592,594,596,616,617,618,619,620,621,624,625,628,630,631,632,633,634,635,636,637,638,639,640,641,645,646,668,669,672,676,680,684,685,687,719,728,729,734,740,741,795,805,807,816,819,835,851,872,873,874,875,879,880,885,886,889,891],"/usr/local/lib/python3.5/dist-packages/sympy/sets/conditionset.py":[33,34,3,4,5,6,1,8,10,7,13,50,51,52,54,59],"/usr/local/lib/python3.5/dist-packages/sympy/printing/latex.py":[1280,3,5,1030,7,9,10,11,12,13,14,15,1552,529,18,19,20,21,23,24,26,27,29,1808,1541,1056,1968,34,35,36,1062,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,1978,78,79,1105,1620,1619,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,100,101,102,103,104,1641,106,107,108,109,2158,1913,112,1138,115,116,1535,1654,120,121,124,125,126,127,128,129,130,131,132,133,134,135,1672,1621,138,1047,1166,1645,1681,659,1173,1817,1689,1917,1180,1562,1300,1648,283,677,1702,167,1705,1194,1699,1708,1832,174,1711,1589,1201,1714,1651,1114,1717,1080,1208,187,1725,1215,1568,1730,197,1222,1738,1900,1229,1144,1059,1236,1805,1657,1752,1243,220,604,1763,1582,1256,1660,746,749,1491,1775,1529,755,1065,1272,249,1663,1578,1790,1279,768,1793,1282,1835,1796,262,1153,1068,778,268,1666,271,1799,1811,788,277,1814,280,281,559,796,287,804,1320,1493,811,813,1838,818,1587,309,823,1848,564,1988,1853,1077,834,1675,325,327,1864,567,1102,1933,849,1363,853,1367,857,861,350,864,1083,868,1381,1384,1897,1802,1086,876,1392,1905,885,1400,1879,891,1405,1941,1920,898,1924,1414,1929,906,1421,1937,917,1430,1604,1519,1843,1438,1951,1820,1441,930,1444,1094,1071,938,1074,645,947,442,956,1696,1867,1111,965,1994,1720,973,1669,2001,1490,1187,1492,981,1495,1956,1499,989,1502,1823,1506,1787,997,1361,488,1779,491,1516,1005,2030,613,510,1014,1909,1108,1623,1333,1159,1962,1022,1125],"/media/androbin/Daten/git/phenny/modules/slogan.py":[7,9,10,11,12,14,17,18,19,22,23,24,25,26,28,31,32,33,34,36,37,40,41,42,44,45,46,48],"/media/androbin/Daten/git/phenny/modules/test/test_slogan.py":[4,5,6,7,8,11,13,14,15,17,19,20,22,24,26,27,28,29,31,33,35,36,37,38],"/usr/local/lib/python3.5/dist-packages/sympy/core/compatibility.py":[256,513,515,5,6,8,9,10,512,601,602,290,291,292,518,295,296,665,301,302,303,304,179,606,521,180,607,95,64,65,67,68,69,70,71,72,75,332,333,78,81,84,85,86,88,89,90,91,860,93,862,182,608,610,612,614,615,616,617,618,619,620,627,628,629,642,643,644,646,648,650,140,337,655,656,657,658,707,663,664,196,666,669,670,673,675,681,683,690,691,692,181,694,457,696,697,699,190,703,603,195,708,709,198,455,456,76,458,460,461,463,725,249,331,604,483,484,485,307,490,492,245,502,247,248,340,250,251,252,509,605,511],"/usr/local/lib/python3.5/dist-packages/sympy/geometry/parabola.py":[65,322,6,8,9,10,11,12,13,14,15,16,17,82,211,20,86,272,172,111,370,246,137,63],"/usr/local/lib/python3.5/dist-packages/sympy/matrices/expressions/__init__.py":[1,3,4,5,6,7,8,10,11,12,13,14,15,16,17,18],"/usr/local/lib/python3.5/dist-packages/pkg_resources/_vendor/pyparsing.py":[343,1047,1048,1049,1050,1051,393,3428,1422,3507,3456,4664,3426,1375,1598,1599,1376,1602,1604,1605,1606,1607,1608,3148,1378,3152,1379,356,3163,3165,3166,357,293,359,2668,2669,2670,623,2672,2673,3698,361,4232,641,642,643,4228,4229,4230,647,648,649,4235,652,653,654,1389,3431,622,3435,671,672,673,675,678,3697,1341,625,381,3444,187,188,3433,192,193,194,195,1406,3455,2773,2774,2775,1388,390,736,737,738,2667,740,741,1404,3819,3820,3821,3822,1405,3827,3829,1407,1408,3429,385,386,644,3359,3360,3361,3362,3365,295,3427,3432,3376,3377,3378,3379,1332,1333,1334,1335,624,1338,3899,3900,3901,3902,1344,1348,1350,739,341,342,2391,344,345,1372,1373,350,351,352,353,354,355,1380,1381,3430,1383,360,1385,3434,1387,364,365,367,368,370,372,373,374,376,377,378,3963,3964,3965,3966,383,384,1409,1410,1412,3973,3974,392,1417,1420,1421,398,399,400,401,405,406,407,408,411,412,413,416,421,422,389,645,435,3508,3509,3510,3511,3512,3513,3514,3515,3516,3524,3525,3526,1377,646,3425,3457,486,679,1329,424,1619,1532,1533],"/usr/local/lib/python3.5/dist-packages/sympy/simplify/radsimp.py":[960,1,961,3,900,5,7,8,9,10,11,12,13,14,15,16,17,982,981,22,1015,984,409,986,987,990,517,993,997,998,1001,956,1005,878,983,1010,630,439,1016,1017,1020,1061,958],"/usr/local/lib/python3.5/dist-packages/sympy/polys/galoistools.py":[1,3,1540,5,6,1799,8,9,10,11,12,14,527,16,2321,771,279,2073,1564,1053,798,1030,558,2095,2096,2097,818,308,2101,1076,59,1856,325,838,2130,1363,1294,601,347,1379,616,875,364,111,1653,1398,1912,633,132,392,1516,907,1167,2194,661,150,1693,1349,2051,1443,420,2214,170,1197,944,1267,440,190,961,1988,1221,2247,457,715,1485,1486,1490,1238,218,1755,732,234,492,2287,1010,83,251,1142],"/media/androbin/Daten/git/phenny/icao.py":[7,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,371,372,373,374,375,376,377,378,379,380,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,400,401,402,403,404,405,406,407,408,409,410,411,412,413,414,415,416,417,418,419,420,421,422,423,424,425,426,427,428,429,430,431,432,433,434,435,436,437,438,439,440,441,442,443,444,445,446,447,448,449,450,451,452,453,454,455,456,457,458,459,460,461,462,463,464,465,466,467,468,469,470,471,472,473,474,475,476,477,478,479,480,481,482,483,484,485,486,487,488,489,490,491,492,493,494,495,496,497,498,499,500,501,502,503,504,505,506,507,508,509,510,511,512,513,514,515,516,517,518,519,520,521,522,523,524,525,526,527,528,529,530,531,532,533,534,535,536,537,538,539,540,541,542,543,544,545,546,547,548,549,550,551,552,553,554,555,556,557,558,559,560,561,562,563,564,565,566,567,568,569,570,571,572,573,574,575,576,577,578,579,580,581,582,583,584,585,586,587,588,589,590,591,592,593,594,595,596,597,598,599,600,601,602,603,604,605,606,607,608,609,610,611,612,613,614,615,616,617,618,619,620,621,622,623,624,625,626,627,628,629,630,631,632,633,634,635,636,637,638,639,640,641,642,643,644,645,646,647,648,649,650,651,652,653,654,655,656,657,658,659,660,661,662,663,664,665,666,667,668,669,670,671,672,673,674,675,676,677,678,679,680,681,682,683,684,685,686,687,688,689,690,691,692,693,694,695,696,697,698,699,700,701,702,703,704,705,706,707,708,709,710,711,712,713,714,715,716,717,718,719,720,721,722,723,724,725,726,727,728,729,730,731,732,733,734,735,736,737,738,739,740,741,742,743,744,745,746,747,748,749,750,751,752,753,754,755,756,757,758,759,760,761,762,763,764,765,766,767,768,769,770,771,772,773,774,775,776,777,778,779,780,781,782,783,784,785,786,787,788,789,790,791,792,793,794,795,796,797,798,799,800,801,802,803,804,805,806,807,808,809,810,811,812,813,814,815,816,817,818,819,820,821,822,823,824,825,826,827,828,829,830,831,832,833,834,835,836,837,838,839,840,841,842,843,844,845,846,847,848,849,850,851,852,853,854,855,856,857,858,859,860,861,862,863,864,865,866,867,868,869,870,871,872,873,874,875,876,877,878,879,880,881,882,883,884,885,886,887,888,889,890,891,892,893,894,895,896,897,898,899,900,901,902,903,904,905,906,907,908,909,910,911,912,913,914,915,916,917,918,919,920,921,922,923,924,925,926,927,928,929,930,931,932,933,934,935,936,937,938,939,940,941,942,943,944,945,946,947,948,949,950,951,952,953,954,955,956,957,958,959,960,961,962,963,964,965,966,967,968,969,970,971,972,973,974,975,976,977,978,979,980,981,982,983,984,985,986,987,988,989,990,991,992,993,994,995,996,997,998,999,1000,1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1011,1012,1013,1014,1015,1016,1017,1018,1019,1020,1021,1022,1023,1024,1025,1026,1027,1028,1029,1030,1031,1032,1033,1034,1035,1036,1037,1038,1039,1040,1041,1042,1043,1044,1045,1046,1047,1048,1049,1050,1051,1052,1053,1054,1055,1056,1057,1058,1059,1060,1061,1062,1063,1064,1065,1066,1067,1068,1069,1070,1071,1072,1073,1074,1075,1076,1077,1078,1079,1080,1081,1082,1083,1084,1085,1086,1087,1088,1089,1090,1091,1092,1093,1094,1095,1096,1097,1098,1099,1100,1101,1102,1103,1104,1105,1106,1107,1108,1109,1110,1111,1112,1113,1114,1115,1116,1117,1118,1119,1120,1121,1122,1123,1124,1125,1126,1127,1128,1129,1130,1131,1132,1133,1134,1135,1136,1137,1138,1139,1140,1141,1142,1143,1144,1145,1146,1147,1148,1149,1150,1151,1152,1153,1154,1155,1156,1157,1158,1159,1160,1161,1162,1163,1164,1165,1166,1167,1168,1169,1170,1171,1172,1173,1174,1175,1176,1177,1178,1179,1180,1181,1182,1183,1184,1185,1186,1187,1188,1189,1190,1191,1192,1193,1194,1195,1196,1197,1198,1199,1200,1201,1202,1203,1204,1205,1206,1207,1208,1209,1210,1211,1212,1213,1214,1215,1216,1217,1218,1219,1220,1221,1222,1223,1224,1225,1226,1227,1228,1229,1230,1231,1232,1233,1234,1235,1236,1237,1238,1239,1240,1241,1242,1243,1244,1245,1246,1247,1248,1249,1250,1251,1252,1253,1254,1255,1256,1257,1258,1259,1260,1261,1262,1263,1264,1265,1266,1267,1268,1269,1270,1271,1272,1273,1274,1275,1276,1277,1278,1279,1280,1281,1282,1283,1284,1285,1286,1287,1288,1289,1290,1291,1292,1293,1294,1295,1296,1297,1298,1299,1300,1301,1302,1303,1304,1305,1306,1307,1308,1309,1310,1311,1312,1313,1314,1315,1316,1317,1318,1319,1320,1321,1322,1323,1324,1325,1326,1327,1328,1329,1330,1331,1332,1333,1334,1335,1336,1337,1338,1339,1340,1341,1342,1343,1344,1345,1346,1347,1348,1349,1350,1351,1352,1353,1354,1355,1356,1357,1358,1359,1360,1361,1362,1363,1364,1365,1366,1367,1368,1369,1370,1371,1372,1373,1374,1375,1376,1377,1378,1379,1380,1381,1382,1383,1384,1385,1386,1387,1388,1389,1390,1391,1392,1393,1394,1395,1396,1397,1398,1399,1400,1401,1402,1403,1404,1405,1406,1407,1408,1409,1410,1411,1412,1413,1414,1415,1416,1417,1418,1419,1420,1421,1422,1423,1424,1425,1426,1427,1428,1429,1430,1431,1432,1433,1434,1435,1436,1437,1438,1439,1440,1441,1442,1443,1444,1445,1446,1447,1448,1449,1450,1451,1452,1453,1454,1455,1456,1457,1458,1459,1460,1461,1462,1463,1464,1465,1466,1467,1468,1469,1470,1471,1472,1473,1474,1475,1476,1477,1478,1479,1480,1481,1482,1483,1484,1485,1486,1487,1488,1489,1490,1491,1492,1493,1494,1495,1496,1497,1498,1499,1500,1501,1502,1503,1504,1505,1506,1507,1508,1509,1510,1511,1512,1513,1514,1515,1516,1517,1518,1519,1520,1521,1522,1523,1524,1525,1526,1527,1528,1529,1530,1531,1532,1533,1534,1535,1536,1537,1538,1539,1540,1541,1542,1543,1544,1545,1546,1547,1548,1549,1550,1551,1552,1553,1554,1555,1556,1557,1558,1559,1560,1561,1562,1563,1564,1565,1566,1567,1568,1569,1570,1571,1572,1573,1574,1575,1576,1577,1578,1579,1580,1581,1582,1583,1584,1585,1586,1587,1588,1589,1590,1591,1592,1593,1594,1595,1596,1597,1598,1599,1600,1601,1602,1603,1604,1605,1606,1607,1608,1609,1610,1611,1612,1613,1614,1615,1616,1617,1618,1619,1620,1621,1622,1623,1624,1625,1626,1627,1628,1629,1630,1631,1632,1633,1634,1635,1636,1637,1638,1639,1640,1641,1642,1643,1644,1645,1646,1647,1648,1649,1650,1651,1652,1653,1654,1655,1656,1657,1658,1659,1660,1661,1662,1663,1664,1665,1666,1667,1668,1669,1670,1671,1672,1673,1674,1675,1676,1677,1678,1679,1680,1681,1682,1683,1684,1685,1686,1687,1688,1689,1690,1691,1692,1693,1694,1695,1696,1697,1698,1699,1700,1701,1702,1703,1704,1705,1706,1707,1708,1709,1710,1711,1712,1713,1714,1715,1716,1717,1718,1719,1720,1721,1722,1723,1724,1725,1726,1727,1728,1729,1730,1731,1732,1733,1734,1735,1736,1737,1738,1739,1740,1741,1742,1743,1744,1745,1746,1747,1748,1749,1750,1751,1752,1753,1754,1755,1756,1757,1758,1759,1760,1761,1762,1763,1764,1765,1766,1767,1768,1769,1770,1771,1772,1773,1774,1775,1776,1777,1778,1779,1780,1781,1782,1783,1784,1785,1786,1787,1788,1789,1790,1791,1792,1793,1794,1795,1796,1797,1798,1799,1800,1801,1802,1803,1804,1805,1806,1807,1808,1809,1810,1811,1812,1813,1814,1815,1816,1817,1818,1819,1820,1821,1822,1823,1824,1825,1826,1827,1828,1829,1830,1831,1832,1833,1834,1835,1836,1837,1838,1839,1840,1841,1842,1843,1844,1845,1846,1847,1848,1849,1850,1851,1852,1853,1854,1855,1856,1857,1858,1859,1860,1861,1862,1863,1864,1865,1866,1867,1868,1869,1870,1871,1872,1873,1874,1875,1876,1877,1878,1879,1880,1881,1882,1883,1884,1885,1886,1887,1888,1889,1890,1891,1892,1893,1894,1895,1896,1897,1898,1899,1900,1901,1902,1903,1904,1905,1906,1907,1908,1909,1910,1911,1912,1913,1914,1915,1916,1917,1918,1919,1920,1921,1922,1923,1924,1925,1926,1927,1928,1929,1930,1931,1932,1933,1934,1935,1936,1937,1938,1939,1940,1941,1942,1943,1944,1945,1946,1947,1948,1949,1950,1951,1952,1953,1954,1955,1956,1957,1958,1959,1960,1961,1962,1963,1964,1965,1966,1967,1968,1969,1970,1971,1972,1973,1974,1975,1976,1977,1978,1979,1980,1981,1982,1983,1984,1985,1986,1987,1988,1989,1990,1991,1992,1993,1994,1995,1996,1997,1998,1999,2000,2001,2002,2003,2004,2005,2006,2007,2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018,2019,2020,2021,2022,2023,2024,2025,2026,2027,2028,2029,2030,2031,2032,2033,2034,2035,2036,2037,2038,2039,2040,2041,2042,2043,2044,2045,2046,2047,2048,2049,2050,2051,2052,2053,2054,2055,2056,2057,2058,2059,2060,2061,2062,2063,2064,2065,2066,2067,2068,2069,2070,2071,2072,2073,2074,2075,2076,2077,2078,2079,2080,2081,2082,2083,2084,2085,2086,2087,2088,2089,2090,2091,2092,2093,2094,2095,2096,2097,2098,2099,2100,2101,2102,2103,2104,2105,2106,2107,2108,2109,2110,2111,2112,2113,2114,2115,2116,2117,2118,2119,2120,2121,2122,2123,2124,2125,2126,2127,2128,2129,2130,2131,2132,2133,2134,2135,2136,2137,2138,2139,2140,2141,2142,2143,2144,2145,2146,2147,2148,2149,2150,2151,2152,2153,2154,2155,2156,2157,2158,2159,2160,2161,2162,2163,2164,2165,2166,2167,2168,2169,2170,2171,2172,2173,2174,2175,2176,2177,2178,2179,2180,2181,2182,2183,2184,2185,2186,2187,2188,2189,2190,2191,2192,2193,2194,2195,2196,2197,2198,2199,2200,2201,2202,2203,2204,2205,2206,2207,2208,2209,2210,2211,2212,2213,2214,2215,2216,2217,2218,2219,2220,2221,2222,2223,2224,2225,2226,2227,2228,2229,2230,2231,2232,2233,2234,2235,2236,2237,2238,2239,2240,2241,2242,2243,2244,2245,2246,2247,2248,2249,2250,2251,2252,2253,2254,2255,2256,2257,2258,2259,2260,2261,2262,2263,2264,2265,2266,2267,2268,2269,2270,2271,2272,2273,2274,2275,2276,2277,2278,2279,2280,2281,2282,2283,2284,2285,2286,2287,2288,2289,2290,2291,2292,2293,2294,2295,2296,2297,2298,2299,2300,2301,2302,2303,2304,2305,2306,2307,2308,2309,2310,2311,2312,2313,2314,2315,2316,2317,2318,2319,2320,2321,2322,2323,2324,2325,2326,2327,2328,2329,2330,2331,2332,2333,2334,2335,2336,2337,2338,2339,2340,2341,2342,2343,2344,2345,2346,2347,2348,2349,2350,2351,2352,2353,2354,2355,2356,2357,2358,2359,2360,2361,2362,2363,2364,2365,2366,2367,2368,2369,2370,2371,2372,2373,2374,2375,2376,2377,2378,2379,2380,2381,2382,2383,2384,2385,2386,2387,2388,2389,2390,2391,2392,2393,2394,2395,2396,2397,2398,2399,2400,2401,2402,2403,2404,2405,2406,2407,2408,2409,2410,2411,2412,2413,2414,2415,2416,2417,2418,2419,2420,2421,2422,2423,2424,2425,2426,2427,2428,2429,2430,2431,2432,2433,2434,2435,2436,2437,2438,2439,2440,2441,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2453,2454,2455,2456,2457,2458,2459,2460,2461,2462,2463,2464,2465,2466,2467,2468,2469,2470,2471,2472,2473,2474,2475,2476,2477,2478,2479,2480,2481,2482,2483,2484,2485,2486,2487,2488,2489,2490,2491,2492,2493,2494,2495,2496,2497,2498,2499,2500,2501,2502,2503,2504,2505,2506,2507,2508,2509,2510,2511,2512,2513,2514,2515,2516,2517,2518,2519,2520,2521,2522,2523,2524,2525,2526,2527,2528,2529,2530,2531,2532,2533,2534,2535,2536,2537,2538,2539,2540,2541,2542,2543,2544,2545,2546,2547,2548,2549,2550,2551,2552,2553,2554,2555,2556,2557,2558,2559,2560,2561,2562,2563,2564,2565,2566,2567,2568,2569,2570,2571,2572,2573,2574,2575,2576,2577,2578,2579,2580,2581,2582,2583,2584,2585,2586,2587,2588,2589,2590,2591,2592,2593,2594,2595,2596,2597,2598,2599,2600,2601,2602,2603,2604,2605,2606,2607,2608,2609,2610,2611,2612,2613,2614,2615,2616,2617,2618,2619,2620,2621,2622,2623,2624,2625,2626,2627,2628,2629,2630,2631,2632,2633,2634,2635,2636,2637,2638,2639,2640,2641,2642,2643,2644,2645,2646,2647,2648,2649,2650,2651,2652,2653,2654,2655,2656,2657,2658,2659,2660,2661,2662,2663,2664,2665,2666,2667,2668,2669,2670,2671,2672,2673,2674,2675,2676,2677,2678,2679,2680,2681,2682,2683,2684,2685,2686,2687,2688,2689,2690,2691,2692,2693,2694,2695,2696,2697,2698,2699,2700,2701,2702,2703,2704,2705,2706,2707,2708,2709,2710,2711,2712,2713,2714,2715,2716,2717,2718,2719,2720,2721,2722,2723,2724,2725,2726,2727,2728,2729,2730,2731,2732,2733,2734,2735,2736,2737,2738,2739,2740,2741,2742,2743,2744,2745,2746,2747,2748,2749,2750,2751,2752,2753,2754,2755,2756,2757,2758,2759,2760,2761,2762,2763,2764,2765,2766,2767,2768,2769,2770,2771,2772,2773,2774,2775,2776,2777,2778,2779,2780,2781,2782,2783,2784,2785,2786,2787,2788,2789,2790,2791,2792,2793,2794,2795,2796,2797,2798,2799,2800,2801,2802,2803,2804,2805,2806,2807,2808,2809,2810,2811,2812,2813,2814,2815,2816,2817,2818,2819,2820,2821,2822,2823,2824,2825,2826,2827,2828,2829,2830,2831,2832,2833,2834,2835,2836,2837,2838,2839,2840,2841,2842,2843,2844,2845,2846,2847,2848,2849,2850,2851,2852,2853,2854,2855,2856,2857,2858,2859,2860,2861,2862,2863,2864,2865,2866,2867,2868,2869,2870,2871,2872,2873,2874,2875,2876,2877,2878,2879,2880,2881,2882,2883,2884,2885,2886,2887,2888,2889,2890,2891,2892,2893,2894,2895,2896,2897,2898,2899,2900,2901,2902,2903,2904,2905,2906,2907,2908,2909,2910,2911,2912,2913,2914,2915,2916,2917,2918,2919,2920,2921,2922,2923,2924,2925,2926,2927,2928,2929,2930,2931,2932,2933,2934,2935,2936,2937,2938,2939,2940,2941,2942,2943,2944,2945,2946,2947,2948,2949,2950,2951,2952,2953,2954,2955,2956,2957,2958,2959,2960,2961,2962,2963,2964,2965,2966,2967,2968,2969,2970,2971,2972,2973,2974,2975,2976,2977,2978,2979,2980,2981,2982,2983,2984,2985,2986,2987,2988,2989,2990,2991,2992,2993,2994,2995,2996,2997,2998,2999,3000,3001,3002,3003,3004,3005,3006,3007,3008,3009,3010,3011,3012,3013,3014,3015,3016,3017,3018,3019,3020,3021,3022,3023,3024,3025,3026,3027,3028,3029,3030,3031,3032,3033,3034,3035,3036,3037,3038,3039,3040,3041,3042,3043,3044,3045,3046,3047,3048,3049,3050,3051,3052,3053,3054,3055,3056,3057,3058,3059,3060,3061,3062,3063,3064,3065,3066,3067,3068,3069,3070,3071,3072,3073,3074,3075,3076,3077,3078,3079,3080,3081,3082,3083,3084,3085,3086,3087,3088,3089,3090,3091,3092,3093,3094,3095,3096,3097,3098,3099,3100,3101,3102,3103,3104,3105,3106,3107,3108,3109,3110,3111,3112,3113,3114,3115,3116,3117,3118,3119,3120,3121,3122,3123,3124,3125,3126,3127,3128,3129,3130,3131,3132,3133,3134,3135,3136,3137,3138,3139,3140,3141,3142,3143,3144,3145,3146,3147,3148,3149,3150,3151,3152,3153,3154,3155,3156,3157,3158,3159,3160,3161,3162,3163,3164,3165,3166,3167,3168,3169,3170,3171,3172,3173,3174,3175,3176,3177,3178,3179,3180,3181,3182,3183,3184,3185,3186,3187,3188,3189,3190,3191,3192,3193,3194,3195,3196,3197,3198,3199,3200,3201,3202,3203,3204,3205,3206,3207,3208,3209,3210,3211,3212,3213,3214,3215,3216,3217,3218,3219,3220,3221,3222,3223,3224,3225,3226,3227,3228,3229,3230,3231,3232,3233,3234,3235,3236,3237,3238,3239,3240,3241,3242,3243,3244,3245,3246,3247,3248,3249,3250,3251,3252,3253,3254,3255,3256,3257,3258,3259,3260,3261,3262,3263,3264,3265,3266,3267,3268,3269,3270,3271,3272,3273,3274,3275,3276,3277,3278,3279,3280,3281,3282,3283,3284,3285,3286,3287,3288,3289,3290,3291,3292,3293,3294,3295,3296,3297,3298,3299,3300,3301,3302,3303,3304,3305,3306,3307,3308,3309,3310,3311,3312,3313,3314,3315,3316,3317,3318,3319,3320,3321,3322,3323,3324,3325,3326,3327,3328,3329,3330,3331,3332,3333,3334,3335,3336,3337,3338,3339,3340,3341,3342,3343,3344,3345,3346,3347,3348,3349,3350,3351,3352,3353,3354,3355,3356,3357,3358,3359,3360,3361,3362,3363,3364,3365,3366,3367,3368,3369,3370,3371,3372,3373,3374,3375,3376,3377,3378,3379,3380,3381,3382,3383,3384,3385,3386,3387,3388,3389,3390,3391,3392,3393,3394,3395,3396,3397,3398,3399,3400,3401,3402,3403,3404,3405,3406,3407,3408,3409,3410,3411,3412,3413,3414,3415,3416,3417,3418,3419,3420,3421,3422,3423,3424,3425,3426,3427,3428,3429,3430,3431,3432,3433,3434,3435,3436,3437,3438,3439,3440,3441,3442,3443,3444,3445,3446,3447,3448,3449,3450,3451,3452,3453,3454,3455,3456,3457,3458,3459,3460,3461,3462,3463,3464,3465,3466,3467,3468,3469,3470,3471,3472,3473,3474,3475,3476,3477,3478,3479,3480,3481,3482,3483,3484,3485,3486,3487,3488,3489,3490,3491,3492,3493,3494,3495,3496,3497,3498,3499,3500,3501,3502,3503,3504,3505,3506,3507,3508,3509,3510,3511,3512,3513,3514,3515,3516,3517,3518,3519,3520,3521,3522,3523,3524,3525,3526,3527,3528,3529,3530,3531,3532,3533,3534,3535,3536,3537,3538,3539,3540,3541,3542,3543,3544,3545,3546,3547,3548,3549,3550,3551,3552,3553,3554,3555,3556,3557,3558,3559,3560,3561,3562,3563,3564,3565,3566,3567,3568,3569,3570,3571,3572,3573,3574,3575,3576,3577,3578,3579,3580,3581,3582,3583,3584,3585,3586,3587,3588,3589,3590,3591,3592,3593,3594,3595,3596,3597,3598,3599,3600,3601,3602,3603,3604,3605,3606,3607,3608,3609,3610,3611,3612,3613,3614,3615,3616,3617,3618,3619,3620,3621,3622,3623,3624,3625,3626,3627,3628,3629,3630,3631,3632,3633,3634,3635,3636,3637,3638,3639,3640,3641,3642,3643,3644,3645,3646,3647,3648,3649,3650,3651,3652,3653,3654,3655,3656,3657,3658,3659,3660,3661,3662,3663,3664,3665,3666,3667,3668,3669,3670,3671,3672,3673,3674,3675,3676,3677,3678,3679,3680,3681,3682,3683,3684,3685,3686,3687,3688,3689,3690,3691,3692,3693,3694,3695,3696,3697,3698,3699,3700,3701,3702,3703,3704,3705,3706,3707,3708,3709,3710,3711,3712,3713,3714,3715,3716,3717,3718,3719,3720,3721,3722,3723,3724,3725,3726,3727,3728,3729,3730,3731,3732,3733,3734,3735,3736,3737,3738,3739,3740,3741,3742,3743,3744,3745,3746,3747,3748,3749,3750,3751,3752,3753,3754,3755,3756,3757,3758,3759,3760,3761,3762,3763,3764,3765,3766,3767,3768,3769,3770,3771,3772,3773,3774,3775,3776,3777,3778,3779,3780,3781,3782,3783,3784,3785,3786,3787,3788,3789,3790,3791,3792,3793,3794,3795,3796,3797,3798,3799,3800,3801,3802,3803,3804,3805,3806,3807,3808,3809,3810,3811,3812,3813,3814,3815,3816,3817,3818,3819,3820,3821,3822,3823,3824,3825,3826,3827,3828,3829,3830,3831,3832,3833,3834,3835,3836,3837,3838,3839,3840,3841,3842,3843,3844,3845,3846,3847,3848,3849,3850,3851,3852,3853,3854,3855,3856,3857,3858,3859,3860,3861,3862,3863,3864,3865,3866,3867,3868,3869,3870,3871,3872,3873,3874,3875,3876,3877,3878,3879,3880,3881,3882,3883,3884,3885,3886,3887,3888,3889,3890,3891,3892,3893,3894,3895,3896,3897,3898,3899,3900,3901,3902,3903,3904,3905,3906,3907,3908,3909,3910,3911,3912,3913,3914,3915,3916,3917,3918,3919,3920,3921,3922,3923,3924,3925,3926,3927,3928,3929,3930,3931,3932,3933,3934,3935,3936,3937,3938,3939,3940,3941,3942,3943,3944,3945,3946,3947,3948,3949,3950,3951,3952,3953,3954,3955,3956,3957,3958,3959,3960,3961,3962,3963,3964,3965,3966,3967,3968,3969,3970,3971,3972,3973,3974,3975,3976,3977,3978,3979,3980,3981,3982,3983,3984,3985,3986,3987,3988,3989,3990,3991,3992,3993,3994,3995,3996,3997,3998,3999,4000,4001,4002,4003,4004,4005,4006,4007,4008,4009,4010,4011,4012,4013,4014,4015,4016,4017,4018,4019,4020,4021,4022,4023,4024,4025,4026,4027,4028,4029,4030,4031,4032,4033,4034,4035,4036,4037,4038,4039,4040,4041,4042,4043,4044,4045,4046,4047,4048,4049,4050,4051,4052,4053,4054,4055,4056,4057,4058,4059,4060,4061,4062,4063,4064,4065,4066,4067,4068,4069,4070,4071,4072,4073,4074,4075,4076,4077,4078,4079,4080,4081,4082,4083,4084,4085,4086,4087,4088,4089,4090,4091,4092,4093,4094,4095,4096,4097,4098,4099,4100,4101,4102,4103,4104,4105,4106,4107,4108,4109,4110,4111,4112,4113,4114,4115,4116,4117,4118,4119,4120,4121,4122,4123,4124,4125,4126,4127,4128,4129,4130,4131,4132,4133,4134,4135,4136,4137,4138,4139,4140,4141,4142,4143,4144,4145,4146,4147,4148,4149,4150,4151,4152,4153,4154,4155,4156,4157,4158,4159,4160,4161,4162,4163,4164,4165,4166,4167,4168,4169,4170,4171,4172,4173,4174,4175,4176,4177,4178,4179,4180,4181,4182,4183,4184,4185,4186,4187,4188,4189,4190,4191,4192,4193,4194,4195,4196,4197],"/usr/local/lib/python3.5/dist-packages/requests/hooks.py":[17,18,34,23,25,26,27,13,14],"/usr/local/lib/python3.5/dist-packages/sympy/polys/domains/expressiondomain.py":[1,3,5,6,7,9,10,11,140,13,14,15,144,17,19,20,174,22,150,24,25,26,154,30,133,33,162,36,166,39,129,42,45,142,48,178,51,158,54,137,57,186,202,190,63,194,139,147,182,198,71,74,206,82,85,214,218,93,222,96,225,148,145,104,210,112,115,116,118,121,124,170,127],"/usr/local/lib/python3.5/dist-packages/sympy/solvers/deutils.py":[128,17,87,10,11,13,14,15],"/usr/local/lib/python3.5/dist-packages/sympy/integrals/trigonometry.py":[3,33,19,5,6,7,232,22,30,277],"/usr/local/lib/python3.5/dist-packages/sympy/ntheory/primetest.py":[4,197,70,113,8,9,426,11,15,49,19,245,292,6,222,351],"/usr/local/lib/python3.5/dist-packages/chardet/version.py":[8,9,6],"/usr/local/lib/python3.5/dist-packages/sympy/matrices/common.py":[5,7,2063,9,10,11,12,13,14,15,16,17,2067,20,21,22,23,24,25,27,858,30,31,34,35,36,39,40,2090,43,45,46,47,48,49,2098,51,1589,2102,1033,58,61,2144,2059,68,2230,584,73,74,76,610,1549,1104,81,2130,2132,1806,2136,527,94,607,608,1634,2147,2148,2150,1553,105,618,2151,2157,112,625,2153,1147,1148,642,2181,648,138,654,143,149,2244,160,624,163,1191,967,2220,174,2223,1713,2226,182,457,1723,2241,2242,1220,1733,2248,2253,2257,1753,220,2269,2284,1773,1265,761,762,251,1789,1289,2235,782,275,558,1304,1824,487,1185,1859,1590,1863,1865,337,1368,857,1882,1885,1887,1889,1891,1894,1898,363,878,879,1397,1915,1918,383,1923,900,901,903,1931,909,1934,912,408,1938,915,1942,920,1582,1861,417,930,1443,934,2094,1966,943,1608,1970,948,1974,953,956,960,1474,2146,1479,1481,1483,972,1486,975,1490,1495,1498,994,1507,2129,2024,2027,1516,1519,1008,1522,1585,1526,509,2133],"/usr/local/lib/python3.5/dist-packages/sympy/polys/polyerrors.py":[1,3,5,7,8,9,11,14,15,17,20,28,31,32,34,39,58,59,61,65,68,69,70,72,73,75,76,77,79,80,81,83,84,85,87,88,89,91,92,93,95,96,97,99,100,101,103,104,105,107,108,109,111,112,113,115,116,117,119,120,121,123,124,125,127,128,129,131,132,134,139,142,143,144,146,147,148,150,151,153,166,172,173,174,176,177,178],"/usr/local/lib/python3.5/dist-packages/sympy/functions/elementary/integers.py":[1,83,3,4,5,6,7,8,9,76,142,79,272,17,18,147,20,217,153,271,223,212,308,129,199,115,116,118,73,185,186,188],"/usr/local/lib/python3.5/dist-packages/chardet/universaldetector.py":[66,68,69,70,71,72,73,74,75,76,77,78,79,81,111,220,94,36,39,40,41,43,44,45,46,47,48,51],"/media/androbin/Daten/git/phenny/modules/apy.py":[5,7,8,9,10,11,12,13,14,17,18,19,22,23,26,27,29,30,31,32,35,43,44,48,49,50,52,53,55,56,60,61,62,63,64,66,68,71,72,75,77,78,79,80,81,83,84,85,86,87,88,89,90,91,92,93,94,95,97,98,99,100,103,105,106,108,109,113,114,116,117,118,119,120,121,123,125,126,127,128,131,133,135,136,138,139,143,144,146,147,148,149,151,152,153,154,155,156,157,158,160,161,162,163,166,168,170,171,173,174,175,179,180,182,183,185,187,188,189,190,193,195,197,198,200,201,202,206,207,209,210,212,214,215,216,217,220,222,224,225,227,228,229,230,234,236,237,239,241,242,243,244,247,249,250,252,253,257,304,305,306,307,310,312,314,315,317,318,319,323,324,326,327,328,329,332,334,335,338,339,340,342,343,345,346,347,351,352,353,354,355,357,358,359,360],"/usr/local/lib/python3.5/dist-packages/mock/mock.py":[2048,2049,2051,2054,2057,2060,685,2064,35,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,57,58,59,60,61,62,2111,2112,65,2114,67,68,69,2118,71,72,73,75,77,78,85,86,87,2136,89,2138,91,92,93,2142,95,2144,97,98,2148,2149,103,2152,2156,2157,2158,2160,114,2163,116,117,2166,2167,122,123,2178,2180,2181,134,135,136,137,138,2188,141,2192,145,147,2196,2414,150,2199,2200,153,2203,2204,2205,160,161,2211,165,166,2218,2221,2224,184,187,188,189,190,2245,203,376,2258,214,2263,240,241,243,244,245,2089,248,251,254,279,388,308,2442,2117,2116,2382,2446,351,352,2448,355,356,357,358,360,2108,364,365,366,367,2416,369,370,373,2422,2424,2425,378,379,380,383,384,385,2436,2438,2113,394,395,398,399,400,401,2115,405,406,407,408,409,410,411,413,414,415,416,767,2468,424,426,427,429,430,431,434,435,436,437,440,444,445,446,447,448,449,450,452,453,456,458,460,461,462,2125,466,2126,470,472,420,474,477,478,479,480,481,485,486,488,2538,492,493,494,2546,2547,500,2550,503,2552,505,506,507,508,509,511,514,515,517,519,520,521,523,524,525,526,527,529,530,532,533,535,536,537,541,554,2141,563,564,565,566,568,569,2143,572,573,574,575,577,579,580,581,582,583,586,587,588,591,592,593,595,596,599,600,603,604,606,607,608,611,613,614,617,618,2151,620,621,624,625,626,627,2135,635,636,637,638,639,643,646,648,649,650,652,654,655,656,657,658,659,661,662,664,666,667,668,671,680,684,2162,686,687,688,690,693,694,695,696,699,701,702,2165,705,706,708,709,710,715,716,717,719,721,728,731,732,733,734,736,737,738,739,740,741,743,744,745,748,751,753,755,756,757,758,759,760,761,763,764,765,469,768,773,774,775,776,777,781,800,801,803,804,808,811,812,815,822,823,824,825,829,831,834,835,836,837,840,2415,853,858,868,875,876,888,890,893,894,899,908,917,922,923,927,155,933,934,2145,156,940,498,943,944,499,948,951,961,962,963,964,502,970,986,1002,1010,1011,1017,1018,170,1022,1023,1024,1025,1026,1027,1028,1029,1030,1038,1040,1041,1042,1043,1045,1046,1047,1050,1053,1055,176,1058,1061,1062,1065,1066,1067,1068,1069,1070,1072,1073,1074,1075,1077,1078,1079,1080,1081,1082,1083,1084,1085,1086,1088,1089,1090,1092,1094,1095,1096,1098,1099,1101,1102,1103,1105,1106,1109,1110,1112,1114,1115,1116,1117,1118,1120,1121,1122,1123,1124,1126,1128,1130,1133,1134,1135,1139,1191,1195,1196,1197,1203,1204,1205,1206,1208,1209,1210,1211,1214,1216,1219,1221,1222,1224,1228,1238,1239,1240,1241,1242,1243,1244,1245,1246,1247,1248,1251,1252,1253,1254,1255,1257,1259,1261,1264,1265,1266,1267,1270,1271,1272,1273,1275,1276,1279,1280,1281,1284,2262,1286,1287,1289,1291,1292,1294,1295,1296,1297,1298,1299,1301,1302,1304,1305,1317,1318,1320,1321,1324,1325,1326,1328,1329,1331,1332,1333,1334,1336,1338,1341,1345,1931,1348,1350,1351,1352,1353,1356,1358,1360,1363,1365,1369,1371,1372,1373,1379,1383,1386,1393,1394,1395,1397,1408,1410,1414,1415,1416,1418,1419,1421,1456,1458,1459,1460,1461,1471,1474,1476,1479,1480,1482,1483,1484,1486,1490,1491,1492,1493,1498,1505,935,1517,1518,1519,1523,1524,1528,1529,1530,1545,1546,1547,1548,1552,1553,1285,1602,1603,463,619,1670,1671,1672,1673,1677,965,1704,1706,1717,1731,1742,1747,1773,1786,1791,1792,1795,1804,1810,1811,1812,1813,1814,1817,1830,1832,1833,1834,1835,1836,1837,1838,2107,1846,1847,1848,1849,1850,1851,1855,1863,1864,1865,1868,1870,1871,1872,1873,1874,1878,1879,1880,1881,1885,1886,1887,1888,1889,1890,1891,1892,1893,1894,1895,1896,1897,1898,1899,1900,2109,1904,1905,1906,1907,1909,1910,1912,1913,1914,1916,1917,1919,1920,1921,1922,1923,1927,1930,2110,1932,1937,1938,1939,1940,1941,1943,1944,1945,1946,1951,1952,1954,1955,1956,1960,1961,1962,1963,1964,1967,1968,1970,1971,1973,1974,1976,1977,1979,1982,1984,1985,1986,1990,1991,1992,2003,2013,2014,2025,2026,2027,2028,2030,2034,2035,2036,2037,2038,2039,2040,2041,2043,2044],"/usr/local/lib/python3.5/dist-packages/sympy/calculus/finite_diff.py":[198,417,18,20,21,22,25,412,413,286,414],"/usr/local/lib/python3.5/dist-packages/sympy/strategies/__init__.py":[32,33,24,26,27,28,29,30],"/media/androbin/Daten/git/phenny/modules/head.py":[8,10,11,12,13,14,15,16,18,19,20,24,27,28,31,33,34,35,36,38,40,46,49,51,52,53,54,55,58,62,64,65,66,67,68,69,70,71,72,73,74,75,76,77,79,80,81,83,84,85,86,89,90,91,93,98,99,101,102,104,106,112,114,116,117,118,119,120,123,124,126,128,131,134,139,140,141,142,144,146,147,148,149,150,151,152,154,155,158,160,164,168,169,170,171,172,174,175,179,180,181,184,185,187,190,196,197,201,204,205,214,215,216,217,218,219,220,221,223,226,237,239,240,241,242,245,248],"/usr/local/lib/python3.5/dist-packages/mpmath/visualization.py":[128,129,130,3,132,5,6,7,9,10,12,13,131,150,151,226,227,135,111,309,310,311,312,313,123,124,125,126,127],"/usr/local/lib/python3.5/dist-packages/sympy/functions/elementary/hyperbolic.py":[1,3,4,1238,6,8,9,855,12,1328,1039,784,273,786,259,276,1046,23,792,260,1052,730,31,33,291,36,774,295,948,1322,1228,178,46,743,48,1032,563,566,312,57,315,1084,931,318,63,321,325,1095,672,585,589,592,824,338,595,341,598,825,740,1111,347,862,357,608,609,612,613,614,359,616,874,107,108,365,694,880,872,371,629,745,1044,634,703,124,1149,127,640,1085,1156,647,650,653,1271,144,659,148,662,665,669,849,415,416,930,675,165,679,168,171,174,431,689,434,691,692,182,329,722,1210,1211,957,191,704,449,194,964,453,200,457,1015,460,974,463,976,840,210,547,212,725,982,548,218,475,478,1104,1147,484,742,581,489,1234,491,466,751,752,497,1269,1014,503,761,656,764,1278,1023],"/usr/local/lib/python3.5/dist-packages/mpmath/calculus/inverselaplace.py":[3,14,144,17,19,277,217,537,538,219,28,541,350,543,352,432,539,37,16,39,540,812,301,816,820,826],"/usr/local/lib/python3.5/dist-packages/sympy/simplify/sqrtdenest.py":[1,258,3,4,5,6,7,8,9,10,139,76,13,528,19,217,218,221,625,105,48,456,439,378,319],"/usr/local/lib/python3.5/dist-packages/sympy/printing/precedence.py":[1,3,132,5,9,10,11,12,13,14,15,16,17,18,19,20,21,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,56,62,68,74,80,91,98,103,104,105,106,107,108,109,113],"/usr/local/lib/python3.5/dist-packages/sympy/printing/printer.py":[256,193,194,68,197,70,263,72,201,74,76,205,206,79,208,196,215,217,199,261,224,231,233,235,257,255,243,244,248,187,189,254,191],"/usr/local/lib/python3.5/dist-packages/urllib3/contrib/__init__.py":[1],"/usr/local/lib/python3.5/dist-packages/chardet/compat.py":[32,33,34,22,25,31],"/usr/lib/python3/dist-packages/nose/plugins/skip.py":[57,59,60,61],"/usr/local/lib/python3.5/dist-packages/sympy/printing/conventions.py":[3,5,7,8,71,11,14],"/usr/local/lib/python3.5/dist-packages/sympy/polys/polyroots.py":[1,3,5,7,8,9,10,11,12,13,14,15,16,657,18,19,20,22,23,24,26,27,29,30,32,720,35,502,470,425,1074,367,49,663,245,118,790,189],"/usr/local/lib/python3.5/dist-packages/sympy/printing/str.py":[512,257,3,260,5,7,8,9,10,523,12,13,526,15,529,18,515,532,21,22,535,25,538,27,796,541,544,33,547,36,553,794,47,520,307,565,311,315,793,319,19,224,322,325,71,328,140,74,77,788,80,337,83,340,86,787,89,670,93,613,656,98,571,101,104,617,107,365,368,625,371,643,117,374,377,380,384,131,389,134,392,137,651,396,143,400,403,660,406,151,559,154,751,669,158,671,672,161,674,164,677,167,680,683,772,686,483,176,790,692,414,695,698,701,704,800,195,710,713,202,716,205,719,640,722,213,121,217,480,738,227,741,593,744,489,747,237,239,754,243,757,707,760,767],"/usr/local/lib/python3.5/dist-packages/mpmath/calculus/calculus.py":[1,2,4,5],"/usr/local/lib/python3.5/dist-packages/sympy/polys/rings.py":[2048,1,3,516,5,6,8,9,10,11,12,13,14,15,16,17,18,2051,20,23,24,25,538,27,28,29,30,31,544,33,34,547,2084,1061,550,553,556,2093,558,1885,569,1034,62,63,2112,1603,1889,1619,597,2134,1111,91,92,698,606,1631,614,618,530,1647,1140,2167,120,1794,635,2442,641,2412,857,1679,1682,667,670,2409,283,1188,710,173,1881,1204,1566,1718,695,1722,188,189,1726,191,704,706,708,1734,545,2248,713,825,1233,724,1755,2170,2273,1763,1318,2279,745,381,753,1269,1783,256,770,773,776,267,1292,270,1295,2323,280,2436,1819,288,291,294,1321,391,300,2141,304,1659,308,2397,311,1844,314,829,321,1347,837,1350,1351,841,1355,845,2157,849,2415,853,343,345,2191,861,2400,865,2403,356,1893,2406,359,873,362,1511,1388,877,2109,881,2418,2451,2421,887,2424,893,2430,900,903,1928,906,2445,2448,1939,2454,1944,1951,1958,1963,432,2026,2362,1975,440,1465,833,447,960,450,454,1991,458,462,1869,980,2006,186,422,1873,2024,489,1514,1522,2038,869,2045,1877],"/usr/lib/python3/dist-packages/nose/suite.py":[540,541,544,545,546,547,548,549,552,553,555,564,565,54,568,69,80,81,82,83,84,95,96,97,98,99,104,105,106,108,114,115,149,150,151,152,154,155,156,157,158,159,174,178,202,205,206,209,210,217,218,219,225,227,228,229,270,271,273,274,275,278,279,283,284,286,287,288,289,290,291,292,293,294,298,299,302,303,304,305,309,310,311,313,314,315,316,53,324,325,326,327,328,329,330,331,332,338,339,340,341,342,343,346,347,348,349,350,351,352,357,358,359,361,362,363,365,366,367,368,369,373,374,375,377,378,379,395,397,398,402,403,404,405,406,407,408,419,420,421,422,423,424,425,428,436,437,442,444,446,447,448,449,452,453,454,455,458,460,461,463,464,466,467,468,472,475,476,477,478,479,480,481,482,483,484,485,486,487],"/usr/local/lib/python3.5/dist-packages/sympy/plotting/experimental_lambdify.py":[107,388,389,394,11,13,15,16,17,18,387,404,405,406,495,666,411,412,541,417,418,582,369,426,427,370,174,431,432,433,434,181,438,183,440,315,190,575,562,395,454,327,328,329,439,76,77,334,82,467,344,345,350,351,352,353,354,355,356,357,358,359,360,361,106,363,364,616,381,241,114,371,246,247,248,249,250,380,362,382],"/usr/local/lib/python3.5/dist-packages/urllib3/util/url.py":[1,2,4,7,11,14,19,20,22,23,24,132,154,27,28,26,158,159,160,33,162,163,164,38,167,168,172,174,29,176,179,30,55,184,31,189,48,161,129,208,209,211,215,219,220,222,95,225,99,115,116,117,118,119,120,122,123,124,126],"/usr/local/lib/python3.5/dist-packages/mpmath/libmp/libmpc.py":[128,3,132,5,518,7,136,9,778,140,578,749,145,532,23,793,411,796,572,33,34,35,36,220,38,39,808,41,549,173,567,48,179,811,55,607,188,829,63,449,194,67,417,585,241,205,444,80,163,84,469,87,331,92,605,485,96,604,228,101,743,105,746,295,109,113,370,755,117,246,504,212,123,764,255],"/usr/lib/python3/dist-packages/nose/plugins/multiprocess.py":[224,225,226,227,231,233,234,235,238,223],"/usr/local/lib/python3.5/dist-packages/sympy/codegen/ffunctions.py":[64,65,68,69,70,7,8,9,11,12,14,21,22,25,26,27,30,31,32,35,36,37,40,41,42,71,45,46,47,50,51,52,54,62,63],"/usr/local/lib/python3.5/dist-packages/sympy/series/series.py":[1,3,6],"/usr/local/lib/python3.5/dist-packages/sympy/functions/__init__.py":[6,8,10,12,14,17,19,21,23,24,25,28,30,32,34,35,36,37,39,40,43,44,46,47,49],"/usr/local/lib/python3.5/dist-packages/sympy/logic/boolalg.py":[513,3,4,6,7,520,9,10,11,12,13,14,15,16,529,19,20,22,24,28,30,34,36,1061,38,40,860,44,48,49,51,54,1545,56,1082,522,343,1606,524,586,1104,82,85,86,87,88,90,93,96,528,100,103,104,105,106,107,108,109,110,111,1136,113,114,531,116,117,120,1658,1027,1168,112,674,198,678,625,626,687,115,1566,1214,1727,194,195,196,710,711,200,201,518,203,716,1234,217,550,1256,745,750,515,1783,248,249,250,252,254,255,257,1577,775,776,781,271,272,277,278,280,1305,282,285,286,288,1826,291,294,1327,1715,315,828,829,1354,744,342,855,344,346,348,1381,367,880,881,1404,388,1431,920,921,925,415,416,417,419,421,422,423,424,427,428,429,431,432,434,435,436,438,526,957,958,673,459,976,980,984,1498,933,993,1043,1513,1010,504,1529,506,508,510],"/usr/local/lib/python3.5/dist-packages/sympy/core/basic.py":[1108,1,2,3,4,1110,6,7,8,9,11,13,16,1041,530,1717,1710,1560,1028,1391,547,1030,1713,1031,1580,45,46,47,48,52,53,54,1033,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,1719,76,77,78,79,1104,81,82,83,84,846,86,87,89,92,1418,96,1026,99,1125,102,106,619,1645,110,111,112,113,1879,115,343,1657,1658,123,1660,125,1666,1667,1668,645,1670,1388,1675,1677,109,1680,1683,1172,878,1174,1176,1177,1647,1692,1181,1694,1183,1696,1904,1698,675,1188,677,1702,1192,1705,1194,875,1196,841,687,1712,177,179,180,181,182,183,1029,1909,1729,201,715,842,915,1123,723,1749,1750,1751,1752,1753,1757,1759,1762,1659,228,864,877,843,1661,894,246,251,252,305,861,897,1804,643,1373,282,1327,1025,1328,879,862,1319,1320,1322,1323,1324,1325,1326,303,304,1329,1330,307,1913,309,999,1849,1850,1851,316,1341,1854,1855,320,321,1903,1860,325,838,993,840,329,330,55,332,845,334,1871,1872,1873,1716,854,569,345,859,860,314,1374,863,1376,1377,998,872,1852,874,1387,1900,1901,1390,573,1392,1000,1907,318,1910,916,1914,1406,895,1408,1856,1411,1412,1413,1417,394,401,914,1859,1428,405,919,917,921,855,837,155,1446,1009,1448,1449,1451,1182,1865,1866,1184,75,403,1186,1486,1101,1187,1102,836,847,477,478,479,848,482,483,484,997,486,402,488,1022,1002,1003,1004,1005,1006,1007,1008,1708,501,503,1529,1534],"/usr/local/lib/python3.5/dist-packages/mpmath/ctx_mp.py":[768,258,4,5,7,145,9,11,13,15,784,131,1301,1302,1047,1307,284,90,87,38,39,41,43,46,1327,136,51,52,1333,54,56,564,1338,59,61,318,63,64,65,66,67,68,70,71,73,75,78,79,83,177,86,313,88,1113,346,271,92,94,96,912,148,100,101,102,97,104,105,618,103,108,109,112,113,114,115,372,62,118,119,1291,660,122,123,636,639,128,129,130,107,132,133,134,135,392,137,138,139,140,141,142,143,400,408,146,147,404,149,150,151,664,666,155,156,154,158,159,116,162,163,164,838,166,167,168,169,426,171,172,173,157,176,433,178,179,180,182,185,1211,117,193,450,308,161,457,206,1232,120,978,121,1240,152,219,165,263,91,231,124,144,1261,238,751,1264,1267,297,127,252,170,85,255],"/usr/local/lib/python3.5/dist-packages/sympy/parsing/sympy_parser.py":[512,1,3,516,5,518,257,520,9,522,779,12,13,782,15,16,17,530,19,532,21,790,791,792,794,795,540,797,542,517,800,545,547,549,806,807,552,41,519,812,813,558,559,560,561,562,513,310,641,539,608,11,54,584,524,586,589,591,592,593,82,595,597,87,88,601,606,96,627,610,612,529,103,360,617,106,615,620,621,111,515,113,882,883,116,885,886,887,889,891,686,637,126,789,640,897,642,643,645,649,650,535,533,909,911,912,913,914,915,916,917,918,921,618,623,538,534,112,930,619,625,681,170,427,684,430,541,688,691,526,195,630,710,455,418,531,638,480,523,739,894,536,594,382,528,809,682,510],"/media/androbin/Daten/git/phenny/modules/test/test_rule34.py":[4,5,6,7,8,9,12,14,15,16,18,20,21,23,24,25,26,28,30,31,33,34],"/media/androbin/Daten/git/phenny/modules/test/test_mylife.py":[4,5,6,7,8,11,12,13,15,17,18,20,22,23],"/usr/local/lib/python3.5/dist-packages/mpmath/function_docs.py":[1026,3,6153,9232,535,4123,346,5150,1569,3108,9767,6700,1069,7728,565,3258,3129,2620,63,3649,1090,8771,5705,2122,2126,952,594,9299,1054,5210,3675,8284,93,4190,3173,1633,1122,9315,6757,1638,956,9943,624,113,7283,1140,1654,5754,8828,1663,875,8470,9863,1672,649,5655,6284,1165,2193,6803,3737,4251,674,171,4780,7853,286,2742,9673,9400,4282,702,5315,1227,2256,8407,4312,3801,730,219,7388,6878,2650,1252,3822,1267,244,757,4347,963,3029,5891,6405,3334,44,1291,9996,3854,8237,785,7446,1303,10031,4382,2338,1315,7974,5417,1327,4912,1841,3384,6459,6972,830,322,9028,2590,9545,1357,5966,8589,3896,6995,8533,2393,3930,1205,4970,1387,7537,372,2934,376,1913,3451,9598,8043,7053,9102,5521,6034,2030,8091,927,930,2459,933,422,936,1962,940,8643,944,948,7165,4024,444,960,8130,6595,966,8136,969,1698,1490,6099,3150,983,7130,5083,2981,2529,995,5591,4824,6563,6126,8175,8181,4598,3576,1279,8701],"/usr/local/lib/python3.5/dist-packages/sympy/printing/julia.py":[257,456,391,10,12,13,14,15,16,17,18,405,22,23,24,25,26,27,28,29,30,31,32,289,370,419,36,37,38,39,40,41,42,301,46,285,49,50,51,309,54,55,56,313,60,61,62,63,64,65,66,228,72,396,204,80,184,210,84,625,88,217,412,92,221,306,293,96,400,325,100,359,340,106,235,365,317,488,242,118,297,249,362],"/usr/local/lib/python3.5/dist-packages/sympy/codegen/__init__.py":[1],"/usr/lib/python3/dist-packages/nose/plugins/cover.py":[164,182,263,183,173,271],"/media/androbin/Daten/git/phenny/test/test_tools.py":[33,34,3,36,5,6,31,9,11,12,19,20,21,22,23,24,25,27,29,30,37],"/usr/local/lib/python3.5/dist-packages/sympy/polys/domains/gmpyintegerring.py":[1,3,5,6,74,87,12,13,15,16,17,19,20,21,22,23,25,79,28,32,67,91,41,45,49,83,54,58,62],"/usr/local/lib/python3.5/dist-packages/mpmath/functions/orthogonal.py":[64,1,3,388,389,454,167,328,365,366,240,307,448,341,120,442,60,350],"/media/androbin/Daten/git/phenny/modules/archwiki.py":[32,33,43,36,37,35,39,41,11,44,13,46,17,18,19,14,22,25,27,30],"/media/androbin/Daten/git/phenny/modules/logger.py":[64,65,66,68,5,7,8,9,10,12,13,14,16,17,25,27,63],"/usr/local/lib/python3.5/dist-packages/sympy/deprecated/class_registry.py":[32,1,2,5,33,40,39,34,14,15,17,18,19,21,29,30,31],"/usr/local/lib/python3.5/dist-packages/sympy/release.py":[1],"/usr/local/lib/python3.5/dist-packages/sympy/printing/gtk.py":[8,1,3,4,5],"/media/androbin/Daten/git/phenny/modules/test/test_apertium_wiki.py":[1,2,3,4,5,8,10,11,12,14,15,17,18,19,21,23,24,26,27,29,30,32,33,35,37,38,40,41,43,44,46,48,49,50,52,53,55,56,58,60,61,62,64,65,67,68,70,72,73,75,76,78,79],"/usr/local/lib/python3.5/dist-packages/sympy/polys/domains/modularinteger.py":[1,130,3,5,7,8,9,11,13,14,15,17,19,21,24,179,155,30,33,164,39,42,172,176,51,158,54,57,36,181,67,161,75,78,141,86,89,152,97,100,167,108,111,112,114,122,170,149],"/usr/local/lib/python3.5/dist-packages/mpmath/calculus/polynomials.py":[48,1,2,9,10,46,47],"/usr/lib/python3/dist-packages/humanize/time.py":[131,5,7,8,9,43,108,13,16,11,24,152],"/media/androbin/Daten/git/phenny/modules/fcc.py":[5,7,8,9,11,14,15,19,20,22,23,24,26,27,28,29,31],"/usr/local/lib/python3.5/dist-packages/sympy/utilities/exceptions.py":[177,3,184,5,7,136,9,135,12,174,133],"/usr/local/lib/python3.5/dist-packages/sympy/geometry/__init__.py":[16,17,18,19,21,22,23,12,13,14],"/media/androbin/Daten/git/phenny/modules/test/test_wuvt.py":[18,4,5,6,7,8,9,12,13,15],"/usr/local/lib/python3.5/dist-packages/sympy/printing/lambdarepr.py":[384,1,305,3,4,261,7,136,300,11,13,270,301,273,276,150,279,25,27,285,289,290,291,292,293,294,295,296,169,298,299,44,173,302,175,176,264,306,179,308,309,310,55,312,185,315,189,318,64,267,326,200,73,311,77,369,80,210,83,303,91,95,352,97,228,307,104,361,235,304,365,111,368,359,242,339,118,297,249,378,127,252,381,255],"/usr/local/lib/python3.5/dist-packages/sympy/polys/domains/groundtypes.py":[64,1,66,3,68,5,65,72,9,10,11,13,15,75,67,21,27,69,40,7,79,54,55,56,58,59,60,62,63],"/media/androbin/Daten/git/phenny/modules/commit.py":[16,18,5,7,8,11,14],"/usr/local/lib/python3.5/dist-packages/sympy/tensor/array/dense_ndim_array.py":[1,2,4,6,135,8,9,10,75,140,13,15,18,132,155,159,161,99,164,70,102,177,137],"/usr/local/lib/python3.5/dist-packages/sympy/assumptions/__init__.py":[1,2,3],"/usr/local/lib/python3.5/dist-packages/mpmath/calculus/quadrature.py":[19,1,3,5,257,136,460,458,203,76,462,291,788,21,22,407,24,409,27,459,289,35,43,812,174,1007,385,456,307,25,245,23,764],"/usr/local/lib/python3.5/dist-packages/sympy/core/coreerrors.py":[1,3,6,7,10,11],"/usr/local/lib/python3.5/dist-packages/urllib3/_collections.py":[1,2,3,4,263,264,265,268,14,15,273,18,21,24,281,27,286,293,39,41,43,44,45,302,47,48,50,52,53,54,55,57,58,59,61,62,66,69,72,79,83,270,86,87,89,90,92,93,94,96,101,299,133,135,136,137,138,139,142,143,146,150,151,152,154,157,160,168,171,175,177,180,182,183,185,202,208,217,218,220,221,222,224,229,232,234,237,240,244,245,247,250],"/usr/local/lib/python3.5/dist-packages/urllib3/util/__init__.py":[16,1,3,4,5,6,22,28,53,21],"/usr/local/lib/python3.5/dist-packages/sympy/polys/orthopolys.py":[1,3,5,135,136,9,10,11,13,270,17,19,171,22,235,281,155,93,39,40,105,170,7,259,203,104,221,73,202,234,59,124,189],"/usr/local/lib/python3.5/dist-packages/sympy/polys/domains/gmpyrationalfield.py":[1,83,3,5,6,71,75,79,11,12,14,15,16,18,19,20,21,22,87,24,25,27,32,67,91,37,47,51,55,59,63],"/media/androbin/Daten/git/phenny/modules/test/test_wadsworth.py":[4,5,6,7,8,11,12,13,14,16,17,18,20,21],"/usr/local/lib/python3.5/dist-packages/sympy/polys/__init__.py":[1,3,5,6,7,9,10,11,13,14,15,17,18,19,21,22,23,25,26,27,29,30,31,33,34,35,37,38,39,41,42,43,45,46,47,49,50,51,53,54,55,57,58,59,61,62,63,65,66,67,69,70,71],"/usr/local/lib/python3.5/dist-packages/sympy/geometry/point.py":[1024,896,1286,1324,1039,18,787,20,22,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,41,44,313,1339,838,1097,1099,844,1101,846,909,848,952,338,1107,1110,599,608,242,107,109,111,627,1146,638,384,898,900,906,653,1309,1173,918,420,426,683,1197,285,433,1054,184,543,189,715,290,333,464,979,689,228,231,1001,295,237,1262,754,1235,245,248,505,251,254],"/usr/local/lib/python3.5/dist-packages/sympy/matrices/dense.py":[128,1,3,4,1302,6,7,8,9,10,11,12,13,14,15,16,17,18,131,20,1326,150,23,409,410,431,156,413,861,802,163,1024,39,680,28,299,44,46,43,904,818,947,1205,641,313,58,1083,556,279,1345,706,395,325,198,652,202,1183,333,1068,208,594,284,787,271,476,733,350,479,144,529,779,747,1346,1452,275,41,507,1419,1281],"/usr/local/lib/python3.5/dist-packages/sympy/matrices/expressions/matmul.py":[1,132,3,4,5,6,7,8,10,75,12,142,15,209,82,147,196,86,235,89,154,257,92,29,31,240,291,100,229,28,106,43,236,238,48,115,124,253,254],"/usr/local/lib/python3.5/dist-packages/sympy/strategies/rl.py":[65,75,4,5,7,8,41,139,12,77,89,115,150,87,39,91,28,126],"/usr/local/lib/python3.5/dist-packages/sympy/tensor/array/arrayop.py":[1,67,3,5,7,8,169,11,215,21],"/usr/local/lib/python3.5/dist-packages/sympy/ntheory/multinomial.py":[1,3,4,113,181,7,55,31],"/usr/local/lib/python3.5/dist-packages/sympy/assumptions/assume.py":[1,2,3,4,5,6,7,8,138,11,140,142,143,144,145,146,148,151,154,157,158,160,34,163,164,167,41,46,49,62,63,65,71,200,73,36,90,94,98,99,103,108,111,115],"/media/androbin/Daten/git/phenny/modules/test/test_more.py":[3,5,6,7,9,10,11,13,14,15,17,18,19,20,22,24,25,26,27,28,30,31,32,34,35,36,39,40,41,42,43,44,47,48,50,51,52,53,54,56,57,58,59,60,62,63,64,65,66,68,69,71,72,74,75,77,78,80,81,83,84,86,87,89,90,92,93,95,96,98,99,100,102,103,105,106,108,109,111,112,114,115,117,118,120,121,123,124,126,127,129,130,132,133,135,136,138,139,141,142,144,145,147,148,150,151,153,154,156,157,159,160,162,163,165,166,168,169,170,172,173,175,176,178,179,180,182,183,185,186,188,189,191,192,193],"/usr/local/lib/python3.5/dist-packages/sympy/simplify/trigsimp.py":[1,912,3,516,5,6,865,8,9,10,11,12,13,1156,15,16,920,18,19,20,21,22,24,921,591,28,29,735,736,864,866,1051,868,425,1095,428,726,820,1164],"/usr/local/lib/python3.5/dist-packages/sympy/functions/combinatorial/factorials.py":[1,514,3,4,5,262,7,648,9,11,140,13,14,655,16,408,18,19,276,21,663,280,644,538,284,669,670,133,288,33,865,6,806,858,174,178,854,822,521,184,188,192,525,331,197,353,329,518,203,204,462,207,80,593,82,851,398,591,90,91,92,861,95,97,749,651,293,529,764,747,387,365,272,677,659,375,251,460,253,254,533],"/usr/local/lib/python3.5/dist-packages/certifi/__init__.py":[1,3],"/usr/local/lib/python3.5/dist-packages/sympy/simplify/cse_opts.py":[32,3,4,6,7,8,9,12],"/usr/local/lib/python3.5/dist-packages/sympy/utilities/memoization.py":[16,1,3,4,7,24,25,28,13,15],"/usr/local/lib/python3.5/dist-packages/sympy/functions/elementary/trigonometric.py":[1,1538,3,4,5,6,7,8,9,10,11,12,13,14,1551,16,17,18,2563,1045,2583,25,26,1051,28,2394,30,1029,1057,2083,1060,1030,38,2087,2600,2091,1068,2094,1584,49,1586,1587,2612,53,2057,1592,2106,863,1596,2109,1599,2112,2400,66,1547,2764,1605,1608,1097,1611,1104,1617,1618,1107,1110,2302,1113,1116,93,1631,1121,1126,2577,1132,2158,2749,1138,2164,1147,636,637,126,2175,1664,1666,1667,1157,2497,1672,2156,650,1675,2097,1678,2413,656,2221,1682,1685,662,2711,1688,665,1691,1375,669,2767,672,1697,1698,676,2246,679,1192,1194,1197,2222,2589,1712,1203,2230,1209,2746,458,2178,2752,2359,2242,245,198,2761,2597,1911,1541,2254,2313,2260,1750,2263,1752,2266,2511,2269,1759,2272,2276,2616,2172,2565,2514,2404,243,1150,2294,1783,248,1787,254,1791,2603,2310,1800,1801,1803,2022,1806,2074,2606,1307,1308,2778,1669,1544,1329,1323,1840,817,1842,820,2056,823,1848,1332,826,1340,830,1589,1856,2370,1347,1862,1353,2517,2103,1356,2530,2382,1359,2360,1362,851,1365,2390,1370,860,2296,351,352,2239,1381,870,2407,2609,2410,1387,1427,365,2417,371,1396,1910,1399,377,380,2709,384,387,1926,391,905,394,907,397,910,1935,400,1944,1938,403,916,1941,406,1432,922,1947,410,1950,1953,1442,1443,1445,2471,2469,1450,1451,1964,1453,2485,2121,440,2491,1525,449,2379,452,2505,1482,1487,504,2002,2004,1493,2520,2010,2523,1500,2526,1957,1504,2018,1507,1510,1513,1516,1519,1522,499,1602,501,1528,1531,510,1535],"/usr/local/lib/python3.5/dist-packages/requests/packages.py":[1,6,7,10,11,12],"/usr/local/lib/python3.5/dist-packages/sympy/polys/rootoftools.py":[1,3,516,5,7,8,9,11,12,13,15,273,19,533,23,29,31,32,34,36,38,40,42,434,46,47,561,50,51,821,822,575,832,833,323,583,72,73,845,78,591,80,82,595,86,87,857,911,94,96,97,98,100,361,706,237,914,918,922,155,930,414,674,171,517,941,174,178,926,182,955,189,949,193,194,198,199,456,203,461,215,477,226,550,493,750,752,753,754,851,756,758,248],"/usr/local/lib/python3.5/dist-packages/chardet/langhebrewmodel.py":[194,195,196,53,198,199,190,197],"/usr/local/lib/python3.5/dist-packages/sympy/printing/pretty/stringpict.py":[361,342,471,503,465,407,204,13,15,17,18,21,150,345,24,217,26,335,28,348,351,354,379,357,39,389,105,426,301,111,48,178,52,501,169,56,88,378,251,381,429],"/usr/local/lib/python3.5/dist-packages/sympy/polys/polyclasses.py":[512,1,3,5,7,8,10,1035,12,13,1038,15,1041,19,1046,23,1049,27,547,1572,1061,1062,551,1576,41,1031,1580,562,1075,1076,566,1592,570,1595,575,64,1603,1332,585,1637,1588,1230,595,782,1623,89,603,1713,1119,1633,1123,613,1127,105,1627,1646,623,1555,531,1641,632,122,1659,126,639,1027,1651,1692,1668,645,1671,136,1162,1047,141,1680,657,146,1683,148,150,663,668,1695,1283,673,1698,1051,164,1701,167,1704,170,1735,1199,689,115,693,1743,697,1723,701,1217,706,1731,711,200,1739,1229,718,1485,1233,1234,1747,725,1750,1241,218,219,1245,222,223,1249,226,739,1253,1488,231,1559,746,236,240,753,467,1268,758,249,762,767,257,555,772,1494,262,263,777,266,270,1325,1297,274,278,1305,285,799,289,1237,293,297,1323,812,301,1551,816,904,820,1715,311,825,830,1343,1346,323,840,845,653,850,339,1365,855,344,1631,860,349,1563,865,354,1381,870,145,1384,875,1523,1656,883,373,886,1400,889,1403,1406,1429,1496,392,907,397,1422,1517,401,835,405,1430,1432,409,922,413,1548,1337,417,1599,421,425,1607,941,430,925,435,440,1466,444,957,1470,1474,451,1478,967,968,457,970,1362,973,462,976,979,1493,982,472,1498,335,478,991,1482,483,488,1002,1005,1520,497,1619,1011,1014,1449,504,1019,508,1023],"/media/androbin/Daten/git/phenny/modules/test/test_imdb.py":[4,5,6,7,8,9,12,13,14,15,17,19,20,21,22,23,25,27,28,29,30,31,32,33,35,37,38,39],"/usr/local/lib/python3.5/dist-packages/sympy/polys/polyconfig.py":[1,66,3,68,5,8,9,10,12,13,15,16,17,18,20,21,23,26,28,38,48,53,55,57,58,60],"/usr/local/lib/python3.5/dist-packages/urllib3/packages/__init__.py":[1,3,5],"/usr/local/lib/python3.5/dist-packages/sympy/assumptions/ask.py":[675,1,2,1027,4,5,6,7,9,10,12,13,270,941,272,18,19,20,1046,535,24,25,795,69,31,34,36,1457,1065,299,178,969,793,51,53,568,1323,570,828,1432,830,1343,1344,1025,1349,326,71,328,1460,1356,892,1341,1111,1368,601,603,860,862,699,357,359,360,1084,1134,367,625,627,1086,1431,1044,379,380,894,917,128,387,388,150,1159,648,650,395,1423,1425,1426,1427,1428,1429,1430,537,152,1433,1434,1179,1436,1181,1438,1439,1440,1441,1442,1435,1444,677,1446,1447,1448,1449,1450,1451,1452,1453,1454,943,1456,433,1458,1459,180,1455,1463,1464,1063,1466,1311,701,1214,1474,711,713,971,206,208,1528,1443,919,469,471,730,732,1445,994,996,233,431,235,495,497,126,297,248,1437,763,765],"/usr/local/lib/python3.5/dist-packages/sympy/codegen/ast.py":[128,385,131,132,133,134,135,137,394,23,25,28,29,30,31,32,34,291,292,176,68,70,71,73,204,205,120,339,221,99,102,360,105,362,107,111,112,115,116,119,376,123,124,127],"/usr/local/lib/python3.5/dist-packages/mock/__init__.py":[1,2,3,4],"/usr/local/lib/python3.5/dist-packages/chardet/codingstatemachine.py":[80,33,66,83,86,54,55,28,30,63],"/usr/local/lib/python3.5/dist-packages/sympy/polys/specialpolys.py":[1,194,3,68,5,6,7,9,10,12,14,268,210,19,270,278,23,88,164,282,27,29,286,69,33,290,36,37,294,231,106,107,274,302,306,147,117,118,247,31,298,309],"/usr/local/lib/python3.5/dist-packages/sympy/matrices/matrices.py":[1,3,4,5,6,7,8,9,10,11,12,15,16,17,18,19,20,21,22,23,478,25,538,27,4125,31,3617,39,1583,515,53,55,607,63,66,1094,72,524,74,587,355,2133,4299,2137,2063,606,2655,1126,3601,1640,4205,1533,3696,4213,118,2679,4216,532,4219,4220,4222,1665,1666,1668,1671,2700,1556,1682,1817,4250,2203,1692,3741,2719,162,1195,1708,2226,4217,704,4289,2243,4292,2166,4295,4215,717,4304,3283,1748,4310,1751,3800,4313,1754,220,4317,2270,2783,4322,4326,3816,3817,747,1773,3323,1794,1284,1797,3335,777,778,267,1804,4226,1807,272,785,1811,3349,1816,2841,1818,1819,1820,1822,287,1824,1825,1826,1828,1830,2344,1834,2861,1845,3894,824,1852,215,2613,1862,1865,1801,3917,339,2901,2313,2403,1894,1899,3437,1904,373,886,888,890,1915,2942,3973,2442,2967,2968,2969,922,1531,70,422,4007,2481,4022,440,1465,4311,3523,965,972,3234,465,467,469,1498,1891,1530,3038,3039,3040,2811,485,492,3567,501,1014,1016,1018,1019,508,1021],"/media/androbin/Daten/git/phenny/modules/test/test_remind.py":[4,6,7,8,9,12,14,15,16,17,18,20,21,22,24,25,26,27,28,30,31,32,33,34,35,37,38,39,40,41,43,44,45,46,47],"/usr/local/lib/python3.5/dist-packages/requests/sessions.py":[512,171,9,10,11,12,13,14,16,17,18,20,21,534,23,24,27,28,30,545,35,38,177,41,557,47,206,50,172,645,568,57,59,60,64,65,579,69,70,74,75,589,78,81,596,597,598,87,88,603,607,96,609,98,612,615,188,106,107,621,622,174,157,625,114,115,628,117,119,120,634,123,125,126,127,640,643,132,618,134,135,648,651,652,653,654,143,657,658,195,660,662,56,152,22,669,158,671,672,673,162,164,678,679,680,169,170,683,684,685,686,175,688,689,178,691,183,184,697,699,700,189,608,705,707,708,710,200,204,715,716,718,207,721,210,211,212,725,214,215,216,217,730,221,224,225,227,232,233,151,637,245,246,249,251,213,263,264,265,266,267,268,270,271,272,274,131,276,279,282,283,284,285,287,290,292,146,296,299,304,305,309,312,315,139,332,335,336,337,340,176,345,655,349,523,354,357,362,365,368,599,372,378,382,235,388,391,392,393,395,396,398,399,401,67,218,411,167,414,415,116,418,419,422,423,424,426,427,428,429,430,431,432,433,434,435,436,437,439,185,442,443,444,160,647,482,483,484,485,486,487,488,489,490,491,492,494,496,498,499,504,505,507,508,510],"/usr/local/lib/python3.5/dist-packages/sympy/polys/monomials.py":[1,3,260,5,390,135,8,9,10,11,396,13,493,448,278,407,281,410,156,413,416,289,419,292,422,284,7,428,176,392,306,438,60,394,318,192,279,451,332,160,204,461,248,346,476,93,478,226,391,362,109,376,505],"/usr/local/lib/python3.5/dist-packages/sympy/functions/special/elliptic_integrals.py":[1,3,5,6,129,8,9,10,11,12,205,14,245,145,82,131,78,203,88,155,93,69,161,354,315,261,273,232,7,364,206,53,73,312,55,314,255,266,85],"/usr/local/lib/python3.5/dist-packages/pkg_resources/__init__.py":[2368,2369,2370,451,2372,2373,3080,2668,3031,1421,2669,450,2627,3029,3095,3096,3033,3036,3076,3077,2628,3085,3042,739,742,3048,3049,3052,3053,3054,3056,3057,3059,3060,3061,3062,3064,738],"/media/androbin/Daten/git/phenny/modules/away.py":[4,6,7,9,11,13,14,15,16,18,19,20,21,22,24,26,27,28,32,33,34,35,37,39,40,41,45,46,47,48,51],"/usr/local/lib/python3.5/dist-packages/sympy/printing/python.py":[48,3,5,6,7,8,44,12,15,16,18,90,38,31],"/usr/local/lib/python3.5/dist-packages/sympy/core/function.py":[1537,427,1035,1037,1039,2648,1555,2588,1566,31,32,1569,34,35,36,37,38,39,40,41,42,43,44,45,47,1584,49,50,51,52,53,55,568,57,58,571,60,61,1086,63,577,1092,1093,1094,1095,1096,1097,1098,589,1102,269,82,84,87,88,1626,91,92,1118,1119,96,97,98,1123,101,276,273,1128,105,1134,1135,1136,1137,1061,1142,120,121,122,123,125,126,127,128,129,130,135,2188,141,142,1679,144,1682,147,148,1685,1174,151,1688,1177,1690,2203,1180,2206,1695,160,161,162,163,1700,165,1705,1197,1710,2224,1201,1116,1715,1204,181,694,1720,1723,1212,1726,1729,2243,1221,1223,1736,1575,1217,1065,717,1744,433,1115,1059,2261,216,1572,219,221,1758,549,263,225,1627,2171,2279,233,1771,237,238,240,753,244,757,246,759,249,250,251,764,2302,767,257,774,1287,777,778,779,780,781,782,783,785,275,788,278,791,1310,231,48,1317,1185,1840,1841,306,308,310,1339,317,565,1347,837,839,841,2385,1166,854,1290,1373,865,1378,1084,1387,1085,1206,1396,745,1401,1343,1514,1173,386,235,388,1175,1172,152,404,252,407,408,410,411,2202,241,426,1351,428,429,431,432,2367,435,437,448,449,451,452,1479,1186,462,1101,1187,1103,1507,1508,1510,1511,1512,1513,490,1516,1519,1520,1521,1523,500,254,1528,1108,1531,1533,1147],"/usr/lib/python3/dist-packages/nose/plugins/capture.py":[64,96,98,69,102,97,58,59,101],"/usr/local/lib/python3.5/dist-packages/sympy/calculus/singularities.py":[97,195,147,73,172,16,18,19,20,21,24,122],"/usr/local/lib/python3.5/dist-packages/sympy/core/core.py":[1,2,9,11,13,15,17,19,21,22,23,24,25,26,27,28,29,30,31,33,35,37,39,41,42,46,55,56,58,59,61,65,68,70,71,73,76,78,79,80,83,84,85,88,89,90,91,92,94,96,97,98,101,102,104],"/usr/local/lib/python3.5/dist-packages/mpmath/calculus/extrapolation.py":[897,2,3,4,517,6,1,9,10,898,14,527,728,1812,559,681,7,1836,1837,815,817,820,641,1721,266,1729,522,1091,969,970,971,972,719,2001,2002,724,725,599,856,1757,1248,98,99,1765,498,500,1783,888,893,894],"/usr/local/lib/python3.5/dist-packages/sympy/polys/domains/pythonrational.py":[1,3,5,7,8,10,11,12,13,14,16,17,277,281,282,47,156,285,261,165,39,41,135,45,46,221,287,264,52,181,54,57,58,223,60,61,62,192,267,49,74,208,81,210,270,87,93,37,96,226,99,229,102,273,105,237,240,59,242,250,253,126],"/usr/local/lib/python3.5/dist-packages/urllib3/__init__.py":[83,3,5,6,8,76,14,15,16,17,18,19,20,21,87,88,25,26,27,90,33,34,35,93,51,54,57,85],"/usr/local/lib/python3.5/dist-packages/requests/structures.py":[65,8,73,10,12,15,82,83,85,89,90,92,93,94,96,99,40,42,43,44,45,46,48,104,51,53,54,56,57,59,60,62,63],"/media/androbin/Daten/git/phenny/modules/test/test_weather.py":[64,68,66,75,4,5,6,7,8,73,74,11,13,14,15,17,19,20,21,22,23,24,72,67,70,41,42,43,45,46,49,50,51,53,55,56,58,60,61,62],"/usr/local/lib/python3.5/dist-packages/sympy/sets/contains.py":[1,3,4,7,28,29],"/usr/local/lib/python3.5/dist-packages/chardet/mbcsgroupprober.py":[32,33,34,35,36,37,38,41,42,30,31],"/usr/local/lib/python3.5/dist-packages/sympy/polys/domains/algebraicfield.py":[1,3,5,6,7,9,10,12,126,14,15,16,18,67,20,21,86,23,24,26,90,94,122,98,114,102,106,44,82,110,47,50,53,118,58,62],"/usr/local/lib/python3.5/dist-packages/sympy/series/__init__.py":[2,3,4,5,6,7,8,9,11,12,13,15,17,18,19,20],"/usr/local/lib/python3.5/dist-packages/sympy/simplify/powsimp.py":[588,1,3,5,6,7,8,9,10,11,12,13,486,591,17],"/usr/local/lib/python3.5/dist-packages/urllib3/util/timeout.py":[1,130,195,4,5,7,118,177,138,11,140,205,206,15,18,213,88,154,91,156,93,94,95,96,97,99,230,103,129,169,171,242,239,168,114,179,180,117,182,120,123,124],"/usr/lib/python3/dist-packages/nose/loader.py":[98,522,87,201,583,559,540,90,542,347,550,551,348,93,573,564,94,566,567,569,570,315,316,317,318,322,323,324,326,327,328,329,330,331,332,333,584,80,82,83,84,85,86,343,344,345,346,91,92,349,350,95,96,353,354,100,523,359,106,109,110,111,112,369,114,371,117,374,375,120,148,378,379,541,129,115,132,406,135,136,493,144,145,146,147,404,405,150,151,152,409,410,155,156,157,158,159,416,417,162,420,421,585,113,168,169,170,171,428,431,432,433,178,179,180,181,182,183,184,187,188,586,191,192,160,197,198,161,356,202,418,589,210,547,213,164,473,474,475,476,122,165,481,123,486,487,488,124,338,494,211,340,341],"/usr/local/lib/python3.5/dist-packages/sympy/simplify/__init__.py":[6,7,11,13,15,17,19,21,23,25,27,29,31],"/usr/local/lib/python3.5/dist-packages/urllib3/connection.py":[1,2,3,4,5,6,7,8,9,10,11,268,13,14,15,274,259,276,277,278,23,280,25,282,284,286,31,37,39,257,301,302,48,50,52,55,56,313,314,317,62,319,320,65,66,67,324,325,70,275,258,311,332,333,312,338,326,346,349,350,95,97,354,355,356,101,104,106,107,108,111,368,113,370,371,318,122,125,127,132,133,260,321,136,137,139,140,141,143,323,148,149,150,152,154,155,159,165,166,167,169,328,287,279,331,322,208,209,211,213,214,215,217,218,220,221,222,226,228,337,246,250,251,252,253,254,255],"/usr/local/lib/python3.5/dist-packages/sympy/utilities/enumerative.py":[1,2,579,646,307,119,130,782,911,851,469,727,344,414,95,416,973,1129,425,682,107,1004,109,499,373,438,105,123,735,149],"/media/androbin/Daten/git/phenny/modules/test/test_vtluugwiki.py":[4,5,6,7,8,9,12,14,15,16,18,19,21,22,23,25,27,28,30,31,33,34,36,37,39,41,42,44,45,47,48,50,52,53,54,56,57,59,60,62,64,65,66,68,69,71,72,74,76,77,79,80,82,83],"/usr/local/lib/python3.5/dist-packages/sympy/geometry/entity.py":[299,132,135,138,143,530,21,154,23,408,25,26,27,28,29,30,31,546,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,58,522,64,480,66,323,438,497,468,471,472,346,220,96,102,105,236,109,241,302,233,121,250,379,124,127],"/usr/local/lib/python3.5/dist-packages/sympy/parsing/sympy_tokenize.py":[23,25,27,29,31,32,33,35,36,37,38,40,41,42,43,44,47,48,51,52,55,56,58,59,60,61,63,64,65,66,67,68,69,70,71,72,73,74,77,79,81,83,84,86,87,92,93,94,95,97,98,99,101,102,105,106,107,108,109,110,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,133,134,141,142,143,144,151,152,154,157,158,161,162,165,172,193,198,200,201,202,203,205,213,214,215,216,217,225,227,228,229,230,231,232,233,234,236,237,238,240,241,244,249,251,254,257,259,262,265,285,286,289,306,307,308,309,310,312,313,314,317,318,320,341,342,343,344,345,346,348,350,353,355,358,371,374,387,388,389,390,391,392,394,395,396,397,399,403,415,416,417,427,428,429,432,433,434,435,436,442,444,446],"/usr/local/lib/python3.5/dist-packages/sympy/core/evaluate.py":[1,2,5,6,8,12,15],"/usr/local/lib/python3.5/dist-packages/sympy/solvers/bivariate.py":[1,3,4,5,6,7,8,9,10,11,12,13,14,15,43,18,308,117,166,81],"/usr/local/lib/python3.5/dist-packages/sympy/assumptions/ask_generated.py":[89,7,9,10,11,14],"/usr/local/lib/python3.5/dist-packages/sympy/core/facts.py":[512,513,514,516,517,519,520,523,524,525,528,529,531,534,535,536,537,538,49,50,52,54,55,58,63,64,66,69,70,71,73,78,85,86,88,89,90,91,92,93,95,98,118,119,120,121,122,125,128,129,130,131,135,138,166,167,168,169,170,171,172,173,177,178,179,181,182,184,185,186,188,189,193,194,195,196,199,200,201,202,204,205,208,209,211,212,214,217,240,241,242,244,245,247,248,255,256,257,260,287,289,290,291,293,295,296,297,298,299,301,302,304,306,308,310,312,314,316,318,319,321,324,325,329,334,335,336,341,343,345,347,349,350,351,352,358,359,361,364,365,367,368,372,373,378,407,409,412,416,418,420,422,423,425,426,427,428,429,434,435,436,437,440,446,449,452,453,454,455,456,458,459,462,463,464,465,466,469,470,475,478,479,483,486,491,492,493,497,498,503],"/usr/local/lib/python3.5/dist-packages/sympy/utilities/iterables.py":[862,1,3,4,861,8,9,778,11,908,14,877,18,531,277,22,2073,154,855,669,927,1697,674,1956,883,555,684,1069,430,1839,1032,2254,94,1718,1463,1720,1721,1722,1311,1597,2110,864,1675,846,197,710,1736,886,1719,1998,848,1873,594,852,85,983,856,868,218,860,858,478,1906,865,866,1723,740,869,870,849,872,874,875,850,624,881,370,1651,2238,1951,759,889,1148,853],"/usr/local/lib/python3.5/dist-packages/sympy/strategies/util.py":[1,3,5,7,8,9,10,12,13,14,15,17],"/usr/local/lib/python3.5/dist-packages/sympy/core/mul.py":[1,1026,3,4,5,7,8,1033,10,11,12,13,14,1040,529,1283,20,21,22,23,24,537,26,541,30,31,33,1058,36,1061,1064,1065,1066,1314,1068,557,1071,560,888,1630,9,1080,180,1083,1290,1086,181,576,608,1090,1093,1094,609,1096,1097,1088,1207,588,1102,1103,1616,1105,1106,1107,596,597,1110,1111,1624,601,90,1627,92,605,94,1119,96,1633,1122,1636,613,1127,1128,1130,1131,620,890,1134,1135,1136,1137,1138,1132,1095,1642,533,1668,618,1217,649,535,653,1172,1685,1113,1176,1690,1179,1180,669,1182,1183,1185,1186,602,1191,1192,1193,684,175,176,177,178,179,1716,542,182,183,184,1055,1215,1216,1312,1219,1285,1222,1223,200,1225,204,205,207,1232,209,1235,212,1749,1750,215,218,1231,220,598,223,1248,1744,1250,739,1252,1253,1255,1257,236,1261,238,1775,1264,242,243,244,894,1270,759,761,1786,1787,1788,765,766,896,1282,771,261,262,1287,1288,265,266,267,1069,272,1311,276,1308,286,287,288,1072,290,293,1320,297,391,1325,1067,1037,1337,826,324,1352,1353,1354,1079,1356,845,892,1359,1251,1081,1266,886,376,377,378,379,380,381,382,383,384,385,386,387,388,773,903,1087,394,712,912,593,891,1224,1271,1173,418,419,420,421,422,424,937,538,428,429,1267,1276,439,440,1268,1202,443,889,1174,449,1355,453,455,457,1038,459,1317,463,464,612,466,1358,470,471,591,481,994,763,893,485,486,487,764,425,1108],"/usr/local/lib/python3.5/dist-packages/lxml/html/__init__.py":[1316,1739,1543,1705,522,1547,524,770,526,1554,546,1046,1559,1455,1565,32,34,547,548,37,38,39,40,43,44,45,46,861,48,561,50,871,1076,54,55,56,1081,58,59,60,62,1717,64,65,66,523,68,69,70,71,73,587,76,1122,1615,80,440,1618,595,596,86,88,89,90,91,92,93,95,96,97,98,99,100,101,102,103,104,618,107,621,1646,1725,624,625,114,1654,1145,1147,720,1149,126,1301,1153,642,619,1156,645,1158,1160,1162,550,652,653,654,655,656,657,1171,148,149,127,153,1062,1696,1185,1187,1188,1190,167,681,682,683,684,685,686,689,690,693,182,695,696,549,699,700,192,1574,703,704,1568,707,196,1706,199,1224,204,715,716,1058,718,719,1232,721,722,738,1238,633,1241,1242,463,638,734,1661,736,1249,1921,1253,737,639,1201,1770,1259,1772,1773,125,240,753,754,755,756,1269,759,760,1273,1274,1151,622,1525,455,768,571,258,773,774,1665,1068,1290,267,1295,277,217,1304,132,878,623,284,1754,589,1776,1315,292,1885,129,476,300,813,477,1672,1732,1334,1850,314,249,1853,1419,327,333,551,1165,1361,1699,1880,1369,1883,1884,349,1886,1563,1889,1595,870,1383,872,1084,875,876,877,1390,1905,1906,1907,1908,1398,1911,1401,381,1407,1920,1409,1515,1412,389,1462,393,395,1602,1684,1427,475,406,409,1582,525,1439,416,929,763,422,1777,613,242,943,1634,1220,439,952,500,637,1470,1471,965,1771,968,588,970,460,461,77,979,469,761,420,1498,79,988,762,1502,1482,766,482,251,484,1509,1003,494,495,496,497,83,1524,812,1014,1193,501,505,1533],"/usr/local/lib/python3.5/dist-packages/mpmath/matrices/calculus.py":[176,1,147,464,53,211,7,205,350,5],"/usr/local/lib/python3.5/dist-packages/sympy/simplify/combsimp.py":[1,3,4,5,6,7,9,10,11,15,477,478],"/media/androbin/Daten/git/phenny/modules/test/test_lastfm.py":[4,5,6,7,8,9,12,13,14,16,17,18,20,22,23,24,25,26,27,29,31,32,33,34,35,36,37],"/usr/local/lib/python3.5/dist-packages/sympy/functions/special/mathieu_functions.py":[19,65,2,131,4,197,6,7,8,9,74,83,12,17,67,21,26,188,236,238,140,179,181,122,124,245],"/usr/local/lib/python3.5/dist-packages/mpmath/functions/zetazeros.py":[16,18,20,55,66,67,136,173,190,200,202,306,316,340,341,432,447,465,475,547,633,634,635,636,637,638,639,640,641,642,643,644,645,646,647,648,649,650,651,652,653,654,655,656,657,658,659,660,661,662,663,664,665,666,667,668,669,670,671,672,673,674,675,676,677,678,679,680,681,682,683,684,685,686,687,688,689,690,691,692,693,694,695,696,697,698,699,700,701,702,703,704,705,706,707,708,709,710,711,712,713,714,715,716,717,718,719,720,721,722,723,724,725,726,727,728,729,730,731,732,733,734,735,736,737,738,739,740,741,742,743,744,745,746,747,748,749,750,751,752,753,754,755,756,757,758,759,760,761,762,763,764,765,766,767,768,769,770,771,772,773,774,775,776,777,778,779,780,781,782,783,784,785,786,787,788,789,790,791,792,793,794,795,796,797,798,799,800,801,802,803,804,805,806,807,808,809,810,811,812,813,814,815,816,817,818,819,820,821,822,823,824,825,826,827,828,829,830,831,832,833,834,835,836,837,838,839,840,841,842,843,844,845,846,847,848,849,850,851,852,853,854,855,856,857,858,859,860,861,862,863,864,865,866,867,868,869,870,871,872,873,874,875,876,877,878,879,880,881,882,883,884,885,886,887,888,889,890,891,892,893,894,895,896,897,898,899,900,901,902,903,904,905,906,907,908,909,910,911,912,913,914,915,916,917,918,919,920,921,922,923,924,925,926,927,928,929,930,931,932,933,934,935,936,937,938,939,940,941,942,943,944,945,946,947,948,949,950,951,952,953,954,955,956,957,958,959,960,961,962,963,964,965,966,967,968,969,970,971,972,973,974,975,976,977,978,979,980,981,982,983,984,985,986,987,988,989,990,991,992,993,994,995,996,997,998,999,1000,1001,1002,1003,1004,1005,1006,1008,1009,1010,1011,1012,1013,1014,1015,1016,1017,1018,1019],"/usr/local/lib/python3.5/dist-packages/sympy/utilities/magic.py":[1,3,5],"/usr/local/lib/python3.5/dist-packages/sympy/series/series_class.py":[32,64,91,4,37,6,8,9,10,11,14,15,17,51,84,22,58,27,42],"/usr/local/lib/python3.5/dist-packages/sympy/polys/rootisolation.py":[1,3,5,521,11,14,23,1796,26,29,33,36,558,74,1378,1892,1114,1630,1636,1809,616,118,1656,1657,1659,638,135,1900,144,1846,1684,155,1699,1190,1194,1708,1198,1713,1718,1722,700,1725,1729,201,1739,1747,728,1828,730,731,732,733,735,736,737,738,1763,742,743,744,745,1770,1831,748,749,750,751,916,754,243,756,757,1783,760,761,762,763,1788,766,255,768,769,772,773,774,775,1801,778,779,780,781,1496,786,787,788,789,1814,792,793,794,795,798,799,800,801,290,1755,804,805,806,807,810,811,812,813,815,816,817,818,1819,821,822,823,824,826,827,828,829,832,833,834,835,837,838,839,840,843,844,845,846,848,849,850,851,854,855,856,857,1882,859,860,861,862,865,866,867,868,870,871,872,873,1767,876,877,878,879,881,882,883,884,887,888,889,890,892,893,894,895,898,899,900,901,904,905,906,907,910,911,912,913,404,917,918,919,923,924,925,926,927,928,929,930,931,932,933,934,935,424,937,938,939,940,941,942,943,944,945,755,948,448,964,1484,1272,472,1836,1505,1772,490,1874,936,767,1824],"/media/androbin/Daten/git/phenny/modules/test/test_fcc.py":[4,5,6,7,8,11,12,13,14,16,18,19,20,21,22,23,25,27,29,30,31,32],"/usr/local/lib/python3.5/dist-packages/sympy/functions/elementary/miscellaneous.py":[1,258,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,21,601,409,33,630,35,36,37,39,40,41,43,50,646,654,440,570,571,572,586,574,591,324,325,456,458,460,461,462,463,464,465,466,467,468,469,470,471,472,473,474,475,476,477,478,479,480,481,482,483,484,357,486,657,594,168,651,118,631,632,634,382,597],"/usr/local/lib/python3.5/dist-packages/mpmath/ctx_base.py":[1,3,5,6,7,8,9,10,11,12,13,14,15,16,17,19,21,22,89,24,25,26,27,28,29,30,31,32,33,34,35,36,37,39,40,92,42,43,45,46,47,48,49,50,52,53,54,55,56,57,59,62,64,67,70,75,334,80,337,83,340,86,343,344,345,346,347,348,349,350,351,353,98,107,116,122,381,128,413,164,431,434,288,458,95,215,478,479,492,493,494],"/usr/local/lib/python3.5/dist-packages/sympy/series/formal.py":[1,3,1156,5,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,924,29,1182,928,944,546,932,936,1166,903,940,1071,560,392,948,960,1178,952,186,956,703,832,833,1163,964,583,328,652,1038,459,974,335,1017,981,343,601,1115,350,1043,1160,354,229,358,999,1153,363,752,920,915,629,1048,153,276,916],"/usr/local/lib/python3.5/dist-packages/sympy/strategies/traverse.py":[1,2,4,5,23,8,18,28,13],"/usr/local/lib/python3.5/dist-packages/sympy/plotting/plot_implicit.py":[65,74,203,24,26,92,29,30,31,33,34,35,36,37,38,39,168,28,42,43,44,46,202],"/usr/local/lib/python3.5/dist-packages/sympy/printing/preview.py":[1,3,4,5,6,7,8,10,11,15,17,18,19,21,23,24,25,26,27],"/media/androbin/Daten/git/phenny/modules/wadsworth.py":[16,18,6,8,10,11,14,15],"/usr/lib/python3/dist-packages/humanize/__init__.py":[1,3,4,5,6,8,9,10],"/usr/local/lib/python3.5/dist-packages/sympy/sets/sets.py":[1,2050,3,5,1542,7,8,9,10,11,12,14,15,16,17,18,19,20,1966,22,23,24,27,1053,2078,543,58,2082,547,37,38,39,40,42,555,44,45,558,47,48,49,50,52,565,2102,567,2104,57,570,1083,60,61,62,575,2112,65,578,579,580,1606,585,1890,867,526,1844,92,187,1895,110,623,624,1896,626,2067,116,629,632,633,634,636,638,1984,130,644,646,1671,1283,1676,1167,2072,1171,150,1175,156,1699,1188,1701,1703,551,170,1709,1198,627,692,628,1211,1727,707,308,1733,711,1225,1826,718,1831,1059,1757,1758,1759,1761,738,1251,1764,2054,1254,1768,1916,1883,1771,749,1774,1777,1780,245,1783,248,762,1789,1793,43,1796,641,1800,265,269,813,1411,46,286,901,1824,816,290,1828,806,807,809,1834,811,812,1837,1881,221,1328,1841,306,307,1332,822,1847,312,564,1850,1339,828,830,1855,833,1346,835,2057,1349,993,841,842,843,844,334,848,824,817,340,1252,1882,655,1885,1886,1887,1888,865,2106,315,359,872,1897,1898,1900,877,1393,882,1913,378,63,1404,1407,384,899,918,1926,1415,2079,1421,2029,814,403,406,495,1008,582,935,1180,846,430,310,2121,756,447,1472,1473,1475,1447,759,2001,2002,2003,2004,561,2006,2007,2012,1786,2015,1505,2019,1509,2024,1513,2062,679,1517,765,1520,2033,1523,2036,1193,2041,477,2044,509,2047],"/usr/local/lib/python3.5/dist-packages/sympy/matrices/expressions/funcmatrix.py":[1,3,4,5,6,39,8,43,46,50,35,30,31],"/usr/local/lib/python3.5/dist-packages/sympy/tensor/index_methods.py":[134,49,177,11,273,13,15,16,17,20,85,23,24,27],"/media/androbin/Daten/git/phenny/modules/calc.py":[67,68,70,9,11,12,13,78,79,16,17,19,14,22,23,25,26,27,28,33,34,38,81,40,39,47,48,50,51,52,53,54,55,56,57,58,60,61,21],"/usr/local/lib/python3.5/dist-packages/sympy/polys/domains/polynomialring.py":[128,1,3,132,5,6,65,8,9,79,11,12,13,15,144,17,18,83,20,87,152,120,75,95,91,104,71,44,47,99,136,140,51,55,111,148,59,124,62],"/usr/local/lib/python3.5/dist-packages/mpmath/matrices/eigen.py":[545,34,36,485,38,39,41,42,663,44,781,664,149,247,376,484,187,188,605,780],"/usr/local/lib/python3.5/dist-packages/sympy/calculus/__init__.py":[19,1,3,4,17,7,8,11,13,14,15],"/media/androbin/Daten/git/phenny/modules/test/test_away.py":[1,2,3,5,7,8,9,10,11,13,14,15,16,18,19,20,21,22,24,25,26,27,28,30,31,32,33,34,35,37,38,39,40,41,42],"/usr/lib/python3/dist-packages/humanize/number.py":[35,4,6,7,8,9,12,81,54,55,56,57,60,95],"/usr/local/lib/python3.5/dist-packages/sympy/polys/compatibility.py":[1,1091,3,1028,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,1058,206,211,1060,1030,222,224,225,226,227,228,229,230,232,235,238,241,244,1065,247,250,1067,260,263,266,268,270,1069,272,274,276,1032,279,281,283,285,287,289,291,293,295,297,300,1074,302,305,307,310,312,315,317,320,322,325,327,329,331,334,336,1080,339,341,1025,343,345,348,351,353,355,358,361,363,365,368,371,374,377,1087,381,384,386,388,391,394,396,398,401,403,406,408,411,413,416,418,1094,424,426,1037,433,435,438,440,442,444,1098,446,449,451,454,1072,456,459,461,1101,465,468,472,479,481,483,486,1105,488,491,494,498,502,504,1108,506,508,511,513,516,1040,520,524,527,534,542,545,548,551,554,1050,558,560,563,566,569,572,576,1042,579,583,586,590,593,597,600,603,607,609,616,618,625,628,631,634,1078,637,640,643,646,649,652,655,658,661,664,667,1045,670,673,676,680,683,687,693,700,703,707,715,724,727,1047,731,733,736,740,745,749,751,753,756,764,772,781,786,791,799,803,807,810,814,1084,817,1035,821,824,828,831,835,838,842,844,847,851,853,856,859,863,865,867,869,872,875,879,882,885,888,892,895,899,901,904,906,908,910,912,914,916,918,920,922,924,926,1063,928,930,932,935,938,941,945,948,951,954,956,959,961,963,966,968,971,973,976,979,981,983,985,988,990,992,994,997,999,1002,1005,1008,1010,1012,1015,1017,1020,1022],"/usr/local/lib/python3.5/dist-packages/sympy/polys/domains/characteristiczero.py":[1,3,5,6,8,9,10,12,14],"/usr/local/lib/python3.5/dist-packages/sympy/tensor/array/sparse_ndim_array.py":[128,1,2,4,118,6,8,9,10,160,13,79,112,18,158,86,154,15,126,182],"/usr/local/lib/python3.5/dist-packages/sympy/polys/densebasic.py":[640,1,3,5,6,391,8,9,10,1815,12,14,1432,1748,1454,1175,920,1049,282,1263,410,901,544,1479,1307,36,666,808,519,1836,1074,1198,1841,434,1716,1664,1561,137,57,698,315,573,1598,832,160,1346,197,455,1865,778,333,1230,591,953,978,723,84,1109,343,1780,857,58,223,480,1634,187,614,744,234,107,61,497,882,372,1527,1144,761,1402,1697,1022,255],"/usr/local/lib/python3.5/dist-packages/sympy/matrices/sparse.py":[848,1,386,3,4,389,6,7,8,9,10,11,12,13,15,16,17,914,771,20,1070,150,153,539,412,287,534,1219,419,164,40,1065,42,257,430,392,949,1161,912,697,415,828,701,556,322,1035,198,327,1096,844,202,588,1250,653,336,466,291,724,341,345,271,93,481,610,613,487,104,1280,847,909,1128,370,1011,759,504,852,842,254],"/media/androbin/Daten/git/phenny/test/__init__.py":[2,3],"/media/androbin/Daten/git/phenny/opt/__init__.py":[1],"/usr/local/lib/python3.5/dist-packages/sympy/core/cache.py":[192,1,2,107,4,197,6,7,9,203,205,63,92,146,206,150,87,110,196,26,91,86,93,96,98,99,38,40,41,193,43,44,109,46,47,112,182,183,184,187,106,65],"/usr/local/lib/python3.5/dist-packages/pkg_resources/extern/__init__.py":[28,29,30],"/usr/local/lib/python3.5/dist-packages/sympy/printing/rust.py":[256,259,438,358,263,10,429,344,276,444,287,34,36,37,38,39,434,351,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,362,623,372,375,378,381,384,387,390,393,396,399,482,402,457,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,218,219,220,221,459,224,225,226,227,228,229,230,231,232,233,236,338,244,247,468,250,253,511],"/usr/local/lib/python3.5/dist-packages/sympy/functions/special/bessel.py":[1,3,5,6,7,8,9,10,11,12,13,14,15,16,274,1585,853,279,282,1109,31,1433,1277,292,774,807,552,855,554,555,44,557,46,1328,904,563,513,1333,1590,1289,56,564,571,60,778,1483,66,684,580,582,327,1296,329,586,1639,332,1357,590,1588,1104,568,82,595,270,983,87,996,71,1295,860,606,1509,1121,610,615,1128,1127,1616,744,370,599,1163,374,1622,1436,379,1149,382,387,1158,392,906,791,908,1602,913,150,1431,152,153,1434,155,51,670,672,675,678,1009,1608,425,427,428,430,688,200,1460,1310,1634,1466,287,330,447,192,782,451,196,1142,1478,456,1452,203,461,208,1272,466,1187,1587,213,471,1021,731,476,733,990,1317,736,993,1107,1275,740,1446,519,252,747,988,1106,751,753,851,756,254,681,1016,249,1274,251,508,765,510,511],"/usr/local/lib/python3.5/dist-packages/sympy/core/multidimensional.py":[64,96,99,5,6,97,8,9,42,139,12,55,88,89,106,101],"/usr/local/lib/python3.5/dist-packages/sympy/core/decorators.py":[64,67,132,133,134,8,10,11,12,77,78,15,19,84,21,86,89,25,26,27,28,29,30,95,32,98,91,36,6,40,41,42,125,45,61,90,121,122,31,124,74,62],"/usr/local/lib/python3.5/dist-packages/sympy/core/assumptions.py":[277,278,282,285,286,287,288,290,291,292,293,296,297,298,299,300,301,302,305,306,307,310,313,314,315,316,318,319,320,321,322,323,324,325,327,328,329,330,331,332,333,335,336,338,339,340,341,342,343,346,347,350,351,352,353,354,355,356,357,358,359,362,363,364,365,152,153,155,156,157,160,163,165,166,167,168,169,170,171,172,173,175,176,178,179,181,182,183,185,186,188,190,191,193,195,197,198,199,202,203,204,207,211,212,214,216,217,218,219,221,222,223,225,226,228,230,233,235,238,241,242,243,244,245,246,247,249,250,253],"/usr/local/lib/python3.5/dist-packages/sympy/calculus/util.py":[640,1,2,3,4,5,6,7,8,10,11,642,14,1133,658,404,857,1049,797,799,160,674,424,691,1211,957,1091,708,965,712,1227,461,1166,81,855,270,727,729,732,613,615,617,749,1007,753,882,884],"/usr/local/lib/python3.5/dist-packages/sympy/core/numbers.py":[1,3,4,5,6,7,8,10,11,12,13,14,15,16,17,20,21,22,23,24,2073,2074,2075,28,29,31,2080,33,35,38,2089,2090,2091,2092,2098,1715,2107,2108,2109,2110,2111,2112,2114,2115,2117,2060,1719,2126,2127,2128,2131,2132,2135,90,91,2144,1723,2049,103,104,2153,2154,108,549,111,2161,2162,2164,123,686,704,2070,134,2184,2071,2189,2191,2072,2196,149,2199,2201,155,2204,160,2216,2217,2219,2220,2222,2077,176,1736,179,180,181,183,184,185,186,187,188,189,190,191,192,193,195,198,199,200,201,202,203,204,205,206,209,210,2273,227,228,2278,2284,2285,2287,2290,1615,2294,2295,2298,2299,2301,2303,2304,2305,2307,929,386,3760,391,3805,393,2364,394,2367,2370,2375,2445,2386,2390,2394,2398,2414,371,2425,2431,2432,2434,2435,389,2438,2439,392,2441,2442,397,1718,1775,2463,2465,2466,2467,2468,2469,2470,2472,2474,2478,2480,2482,440,1618,2498,2502,2503,2505,2507,2512,1443,78,2528,2529,2531,2532,3494,2534,2536,2540,2542,2544,2547,2550,2551,2552,507,2559,518,519,520,521,523,526,528,2581,2582,2584,2585,2587,2589,2591,2593,2595,2597,555,3166,2620,574,582,585,2636,2637,590,2639,2640,593,2642,3171,2644,597,598,600,2649,604,608,611,3174,616,617,619,621,622,624,626,1811,628,629,630,105,632,2685,2687,2688,2689,2690,643,2693,2695,2696,2156,2698,1815,2701,654,656,2705,2718,2720,673,2722,675,2725,679,2731,684,2734,688,692,1481,696,2069,2752,2754,712,1484,719,726,727,2776,729,2778,1487,732,2781,2782,2784,740,1489,746,752,758,1492,763,768,774,2823,2826,2830,2831,2833,2834,2836,2839,2840,2841,2844,2845,2848,1158,2862,2876,2885,2888,2890,2893,2896,2899,2909,2911,2912,2913,2914,2916,2918,2919,2921,1511,2924,2928,2941,2943,2957,2975,928,2977,933,934,935,937,939,941,2691,3000,3002,3005,3008,1260,3044,3047,3051,3052,3054,3055,3057,3060,3074,3083,3092,1540,1541,3106,3109,3111,3114,3591,1068,3118,1071,1073,1076,1077,1079,1080,1083,1546,1086,1089,1090,1092,1096,1100,1104,1105,1106,1108,1110,1113,3162,3163,3164,3165,1118,3167,3168,3169,3170,1123,3172,3173,1126,3175,3177,3179,3180,1133,3182,3185,1140,3189,1143,3193,1146,1148,3197,1151,3201,3203,3206,3209,3210,3212,1165,3216,3217,3219,1172,3221,3223,3226,1179,1181,3231,3232,3233,3234,3236,3239,1198,3613,1207,3266,3268,3269,3270,3271,3273,3275,3276,3278,3281,1814,3285,1239,3288,1242,3291,3295,1249,1250,1258,1259,3308,1261,1262,3313,1266,3316,3318,3319,3320,3322,1275,3324,1278,3327,1281,3335,3338,1295,3350,3353,1309,1310,1311,1314,1316,1317,1318,1319,1320,1323,3374,1337,1338,1247,1340,1613,1343,1347,3398,1352,1355,1358,3409,3413,3416,591,3420,3443,3445,3446,3447,3448,3449,3450,3451,3453,3455,3458,3462,3465,3468,3474,3478,3483,1605,3488,1442,3491,1444,1445,1446,1448,1450,1607,1452,1453,1454,1455,1608,1458,1609,1268,1474,1475,1476,3525,3527,3528,3529,3530,3531,3532,3533,1486,3535,3537,1490,3540,1493,1494,1495,3544,1497,3547,1500,1274,3550,1507,3556,1510,3559,1512,3562,1515,1516,1517,1518,1519,1520,1521,1522,1524,3326,1537,3586,3588,3589,3590,1543,3592,3593,3594,1547,3596,1549,3598,1552,3601,3604,3609,1565,1567,3619,3623,3626,1579,1591,1593,1594,1596,1598,1601,1603,3653,3655,3656,3657,3658,3659,1612,3661,3663,3666,1620,3669,3675,1632,3681,1634,3686,1647,1653,3709,3711,3712,3713,3714,3715,3717,3719,3722,3329,3728,3734,3739,1695,1696,1698,1701,1704,1710,1712,3762,3763,3764,3765,3766,3767,1720,3769,3771,3774,1727,3778,3781,1734,3784,1739,3796,3797,3798,3799,3801,3803,1757,3809,3812,3816,3820,3823,3826,3829,3830,3832,3835,1793,3845,3846,3849,3852,3855,3859,1812,3861,3862,3863,3865,3866,3867,3868,3869,1826,1836,1844,1845,1847,1851,1871,1873,1875,1881,1885,3387,1911,1912,1915,1916,1917,1918,1496,1939,1941,1942,1943,1945,1947,1949,1950,1952,1956,1958,1966,1967,1971,1972,1973,1976,1977,1979,1980,1982,1986,1987,1989,1991,1994,1997,1998,2000,2723,2006,2007,2008,2009,2013,677,2028,2029,2030,2032,2033,2034,2035,2040,2182],"/usr/local/lib/python3.5/dist-packages/sympy/printing/__init__.py":[1,3,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22],"/usr/local/lib/python3.5/dist-packages/sympy/solvers/__init__.py":[27,17,34,19,22,32,24,10,11,29,15],"/usr/local/lib/python3.5/dist-packages/sympy/printing/tree.py":[56,1,71,4,39],"/media/androbin/Daten/git/phenny/wiki.py":[1,2,3,4,5,6,8,9,10,17,18,19,20,21,25,26,27,28,30,31,32,34,35,36,37,38,39,41,42,43,44,46,47,49,51,52,53,55,58,61,64,66,67,70,72,73,75,78,80,82,83,84,85,87,88,89,90,92,93,95,96,98,101,103,104,106,108,111,112,114,116,117,118,120,121,123,125,126,127,128,129,130,131,132,133,135,136,137,139,140,144,147,149,150],"/usr/local/lib/python3.5/dist-packages/mpmath/functions/functions.py":[1,3,517,263,44,10,11,270,16,18,19,20,277,22,24,25,26,27,28,285,30,31,32,289,34,219,36,293,294,39,40,42,300,45,46,47,304,49,308,54,55,312,58,316,61,68,69,70,71,72,73,74,75,76,333,78,335,81,82,84,85,87,88,90,93,96,99,102,259,105,620,621,115,628,629,118,121,21,129,132,135,108,398,147,148,155,163,48,172,29,182,560,452,453,33,332,204,77,35,79,220,37,481,482,38,237,238,495,507,252],"/usr/local/lib/python3.5/dist-packages/sympy/matrices/expressions/determinant.py":[1,3,4,35,7,41,80,19,21,56,57,60,31],"/usr/local/lib/python3.5/dist-packages/sympy/polys/numberfields.py":[512,1,3,5,1032,1033,10,11,1071,13,1074,18,814,920,25,27,29,31,160,33,35,37,39,41,1066,811,812,45,46,47,432,49,306,51,820,53,54,1079,57,671,394,821,48,1078,582,583,974,1068,464,217,95,50,997,295,317,370,501,1065,889,382],"/usr/local/lib/python3.5/dist-packages/sympy/geometry/ellipse.py":[128,662,134,7,9,138,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,1178,27,28,29,30,415,32,35,293,1446,168,556,942,1267,283,1340,189,1342,319,1088,193,262,451,582,737,1157,1486,1294,1363,341,221,528,483,1510,1414,913,362,1383,1132,1008,115,117,1471,831,1532,784,981],"/usr/local/lib/python3.5/dist-packages/requests/certs.py":[17,14,15],"/usr/local/lib/python3.5/dist-packages/sympy/tensor/__init__.py":[3,4,5,6],"/usr/local/lib/python3.5/dist-packages/requests/exceptions.py":[8,9,12,15,17,19,20,21,22,23,24,25,28,29,32,33,36,37,40,41,44,50,53,57,60,61,64,65,68,69,72,73,76,77,80,81,84,85,88,89,92,93,96,97,100,101,104,105,110,111,112,115,116,117,120,121,122],"/media/androbin/Daten/git/phenny/modules/vtluugwiki.py":[32,33,43,36,37,35,39,41,11,44,13,46,17,18,19,14,22,25,27,30],"/usr/local/lib/python3.5/dist-packages/sympy/solvers/ode.py":[8200,2573,1283,6679,1567,7202,4359,1800,6845,5176,4665,4155,8259,6214,3657,7754,6731,7437,3153,7250,5716,7478,1725,7779,2159,2494,4728,4224,1665,5252,6792,7305,5414,2699,7824,6293,5789,3244,1709,694,7869,7363,3782,4304,1797,6368,4833,5861,230,231,3305,234,236,7405,238,2799,240,3880,243,244,245,246,247,249,250,7551,252,6954,254,255,257,258,259,260,261,263,264,265,266,2827,268,269,7950,301,6419,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,5933,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,7904,2370,323,324,325,326,327,328,329,330,331,335,2898,4435,7508,1880,1884,5469,7006,3439,4976,370,3390,7030,233,7999,661,387,388,8077,6025,6541,237,2961,239,4511,241,7592,4021,3511,5053,6590,1988,7109,8141,7635,7638,1495,2520,3835,6614,5613,3054,253,8019,6644,6134,5111,7160,4090],"/usr/local/lib/python3.5/dist-packages/sympy/polys/heuristicgcd.py":[1,3,4,5,7,9,122],"/usr/local/lib/python3.5/dist-packages/requests/utils.py":[259,716,262,9,266,11,12,13,14,15,16,17,18,19,20,21,534,23,24,26,27,28,642,798,799,32,33,34,521,37,794,39,42,615,419,304,561,422,675,265,863,572,833,779,583,780,842,843,844,846,781,850,851,854,100,90,861,607,272,866,99,868,101,796,103,379,875,364,616,154,157,104,629,268,420,532,191,892,524,533,640,641,130,635,644,390,705,620,791,695,619,792,427,622,410,704,538,160,417,674,163,164,244,166,168,169,170,839,428,686,431,177,178,179,692,182,841,185,698,867,189,190,816,192,449,194,195,196,710,711,712,713,518,459,683,546,717,424,721,211,724,706,676,730,219,733,800,737,738,739,740,741,715,639,745,496,497,339,500,510,515,425,680,506,507,508,509,784,511],"/usr/local/lib/python3.5/dist-packages/urllib3/poolmanager.py":[1,2,3,4,261,6,7,8,9,10,11,12,13,16,19,277,278,279,281,289,290,262,299,301,49,54,57,266,352,258,78,79,80,83,84,89,90,95,96,99,100,101,103,259,111,112,116,117,377,379,380,22,260,402,147,149,151,152,153,410,155,154,159,160,162,165,425,170,180,181,439,188,189,191,192,193,195,197,204,206,121,217,220,221,222,223,224,225,227,229,236,237,238,240,242,264,250,253,254,255],"/usr/local/lib/python3.5/dist-packages/sympy/functions/elementary/exponential.py":[1,3,4,5,6,7,8,9,10,11,12,525,14,15,16,17,786,531,533,535,516,175,541,542,31,544,33,546,35,521,550,551,41,519,815,304,564,311,312,570,571,831,65,523,328,588,78,81,851,228,91,860,517,528,101,785,104,361,110,376,633,383,641,394,401,451,150,152,153,155,159,674,421,684,431,72,178,182,695,441,698,189,446,704,707,197,710,199,456,713,208,120,467,469,527,478,783,484,485,486,487,488,745,490,752,553],"/media/androbin/Daten/git/phenny/modules/nsfw.py":[16,5,7,9,10,11,12,13,14],"/usr/local/lib/python3.5/dist-packages/chardet/mbcssm.py":[571,258,262,263,264,265,266,267,270,272,273,274,275,276,534,538,539,28,541,542,543,544,545,546,219,548,549,550,551,552,553,554,555,556,557,558,559,560,561,562,563,566,312,569,570,428,316,317,318,319,320,321,68,69,70,329,331,76,333,334,79,568,540,99,104,105,106,107,108,109,110,113,115,116,373,118,119,377,378,379,382,384,385,386,387,388,439,64,155,572,159,160,161,162,163,166,497,168,169,170,171,172,429,430,431,432,433,434,437,73,440,441,442,443,117,75,332,77,208,547,212,78,216,218,335,220,221,222,479,483,484,485,486,487,488,489,492,494,495,496,424,498,213],"/usr/local/lib/python3.5/dist-packages/sympy/polys/partfrac.py":[1,354,3,5,6,7,8,421,10,11,12,13,15,16,17,213,214,152,191],"/usr/local/lib/python3.5/dist-packages/chardet/enums.py":[65,75,5,71,8,73,74,11,12,13,14,17,21,22,23,24,25,26,27,28,29,32,35,36,37,38,41,44,45,46,47,76,72,50,53,54,55,56,57,59],"/usr/local/lib/python3.5/dist-packages/sympy/polys/domains/finitefield.py":[64,1,86,3,5,6,7,8,73,10,11,13,14,15,17,19,20,22,23,25,26,28,90,94,99,77,81,41,44,47,52,56,60],"/media/androbin/Daten/git/phenny/modules/test/test_clock.py":[4,5,6,7,8,9,12,13,15,17,18,19,21,22,24,26,28,29,30,31,32,34,36,37,38,40,41,42,44,46,48,49,50,51,52,54,56,57,58,60,62,63,64,66,68,69,70,72,74,75,76,78,80,81,82,84,86,87,88,89,90,92,94,95,96,97,98],"/usr/local/lib/python3.5/dist-packages/chardet/__init__.py":[24,19,20,21],"/usr/local/lib/python3.5/dist-packages/sympy/integrals/manualintegrate.py":[256,1025,1108,861,1034,87,898,1038,527,18,19,1049,21,23,792,25,26,27,28,30,543,32,34,35,36,37,38,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,1076,64,322,67,68,69,70,865,72,329,586,1100,1104,593,659,1166,343,600,868,71,570,862,863,1043,609,610,612,869,614,615,872,1084,871,876,878,623,625,626,115,629,630,633,1177,635,636,893,1150,640,897,642,643,1158,647,648,649,653,654,655,401,1170,579,660,661,1113,663,664,665,669,670,671,1092,675,1088,169,171,1096,690,182,1080,184,74,66,709,199,207,803,728,1030,463,733,1061,226,230,886,504,1017,251,1021],"/usr/local/lib/python3.5/dist-packages/sympy/series/approximants.py":[1,3,5,6,7,9,10],"/usr/lib/python3/dist-packages/nose/plugins/prof.py":[72,57,73,71],"/usr/local/lib/python3.5/dist-packages/sympy/strategies/branch/__init__.py":[1,2,4],"/usr/lib/python3/dist-packages/nose/util.py":[263,520,521,522,267,524,526,527,272,529,530,276,277,278,279,129,264,306,307,308,309,310,311,312,313,266,318,319,320,321,322,323,337,338,339,340,270,342,343,271,613,614,273,619,620,274,622,623,624,446,447,405,448,130,132,150,393,407,140,397,398,399,152,403,404,661,406,151,664,409,410,411,154,163,164,184,187,188,189,190,191,192,449,195,663,504,470,471,479,481,408,483,484,485,486,502,503,153,505,506],"/usr/local/lib/python3.5/dist-packages/mpmath/matrices/linalg.py":[561,193,291,100,177,252,234,107,588,109,111,505,113,579,419,391,377,324,538,284,158],"/usr/local/lib/python3.5/dist-packages/sympy/ntheory/continued_fraction.py":[1,211,5,166,95],"/usr/local/lib/python3.5/dist-packages/sympy/simplify/traversaltools.py":[8,1,3,5],"/usr/local/lib/python3.5/dist-packages/sympy/utilities/source.py":[18,3,36,5,7,10],"/media/androbin/Daten/git/phenny/modules/test/test_wikipedia.py":[4,5,6,7,8,9,12,14,15,16,18,19,21,22,23,25,27,28,30,31,33,34,36,37,39,41,42,44,45,47,48,50,52,53,54,56,57,59,60,62,64,65,66,68,69,71,72,74,76,77,79,80,82,83],"/usr/local/lib/python3.5/dist-packages/mpmath/functions/expintegrals.py":[384,1,66,3,133,134,76,12,322,398,336,275,276,24,356,411,329,95,96,225,226,251,100,101,294,39,363,108,109,174,175,287,54,55,419,186,187],"/media/androbin/Daten/git/phenny/modules/weather.py":[8,10,11,12,13,14,16,19,20,22,23,25,27,28,30,31,33,34,35,36,39,57,58,60,61,63,64,65,66,67,68,69,70,71,72,73,76,78,79,82,84,88,89,90,97,101,102,104],"/usr/local/lib/python3.5/dist-packages/urllib3/util/wait.py":[1,36,33,9,13,15,17,18,22,23,24,25,26,29],"/usr/local/lib/python3.5/dist-packages/sympy/functions/elementary/complexes.py":[512,1,3,4,5,262,7,8,265,10,11,12,13,14,527,272,563,531,153,21,790,535,792,516,539,542,172,58,176,859,162,550,92,519,61,46,559,48,49,53,51,55,350,9,57,826,571,70,522,830,319,578,267,68,884,582,353,72,587,1165,336,81,82,340,1166,87,344,347,604,861,606,607,609,98,59,1125,913,105,108,365,111,369,787,628,374,361,633,378,63,746,638,789,1046,151,1164,834,142,144,145,147,149,69,665,154,667,925,414,1093,416,417,418,155,420,326,679,682,159,157,688,177,691,180,437,182,695,116,698,187,700,957,959,706,75,709,673,712,201,716,930,719,208,721,163,212,164,215,956,676,730,733,223,736,67,739,165,323,167,1006,954,1015,760,264,85],"/usr/local/lib/python3.5/dist-packages/mpmath/math2.py":[6,8,9,10,13,14,15,16,17,18,19,20,21,22,23,25,27,28,39,40,42,43,93,50,51,53,54,59,60,64,65,614,97,74,75,76,98,78,79,80,81,83,84,85,87,88,89,91,92,605,606,607,96,609,610,611,612,101,102,615,104,617,106,619,620,621,623,624,113,626,531,629,630,631,632,633,635,124,618,638,640,135,146,157,158,160,636,625,170,627,180,181,182,183,185,186,187,188,190,637,193,196,198,217,231,233,551,239,242,248,95,304,305,306,307,308,309,310,311,312,313,315,337,608,357,364,365,366,367,368,369,370,371,372,375,376,377,378,379,380,381,382,383,384,386,392,406,410,425,613,440,588,458,459,460,461,462,463,464,465,466,467,468,469,470,471,472,473,474,475,476,477,478,479,480,481,482,483,484,485,486,487,488,489,490,491,492,493,494,495,496,497,498,499,501,503],"/usr/local/lib/python3.5/dist-packages/sympy/polys/domains/rationalfield.py":[1,3,5,6,7,9,11,12,13,15,17,18,20,21,23,28],"/usr/local/lib/python3.5/dist-packages/sympy/core/singleton.py":[1,3,5,6,7,10,79,80,83,85,86,175,157,159,96,97,162,99,164,163,165,167,171,107,173,111,112,113,114,115,117,120,123],"/usr/local/lib/python3.5/dist-packages/urllib3/packages/six.py":[1,23,25,26,27,28,29,31,32,36,37,38,40,41,42,43,44,45,47,75,77,80,82,83,86,88,89,91,92,93,94,97,100,103,105,106,107,108,109,110,114,115,117,118,119,124,126,127,128,130,136,139,141,142,143,144,146,147,148,149,151,152,159,160,161,164,171,173,174,175,177,178,179,181,182,184,185,186,187,189,190,191,195,196,198,199,200,201,202,203,205,206,207,209,216,218,224,226,229,231,232,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,303,308,309,310,311,312,314,316,317,320,322,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,350,351,352,354,356,357,360,362,366,367,368,370,371,372,374,376,377,380,382,386,387,388,389,390,391,392,393,394,395,396,397,398,399,400,401,402,403,404,405,406,407,408,409,410,411,412,413,414,415,416,417,418,420,421,422,424,426,427,430,432,436,437,438,439,441,442,443,445,447,448,451,453,457,459,460,461,463,465,466,469,471,472,473,474,475,476,477,479,482,483,486,491,502,503,504,506,507,508,509,520,521,525,528,529,535,536,539,541,544,561,562,565,566,567,568,569,570,573,574,577,578,580,583,586,588,590,610,611,612,613,614,615,618,619,622,624,625,626,627,628,629,630,631,632,633,634,635,639,640,662,663,666,670,674,678,679,681,706,712,713,715,721,722,776,786,788,797,800,812,828,849,850,851,852,856,857,862,863,866,868],"/usr/local/lib/python3.5/dist-packages/sympy/plotting/intervalmath/__init__.py":[1,2],"/usr/local/lib/python3.5/dist-packages/sympy/printing/defaults.py":[16,1,3,12,20],"/usr/local/lib/python3.5/dist-packages/sympy/series/residues.py":[8,9,4,6,12],"/media/androbin/Daten/git/phenny/modules/greeting.py":[2,3,4,5,7,8,137,138,11,12,142,16,17,22,23,25,26,27,28,30,31,32,33,34,35,37,39,40,43,44,48,49,50,51,53,54,9,57,58,59,60,62,63,64,66,139,68,69,71,72,74,75,76,77,78,79,81,14,118,119,120,55,122],"/usr/local/lib/python3.5/dist-packages/chardet/big5freq.py":[384,43,46],"/usr/local/lib/python3.5/dist-packages/chardet/big5prober.py":[34,35,41,45,28,29,30,31],"/usr/local/lib/python3.5/dist-packages/sympy/functions/elementary/piecewise.py":[512,1,3,4,5,6,8,522,11,12,14,15,16,17,19,21,26,28,287,33,35,46,50,51,52,55,437,87,89,90,92,94,95,97,98,99,101,105,106,107,109,110,114,115,119,125,126,127,128,129,138,146,147,148,149,150,152,153,155,161,162,165,167,181,441,186,444,189,192,195,198,202,462,465,477,479,480,481,483,484,486,487,489,491,493,494,495,496,497,500,503,504,505,506,510],"/usr/local/lib/python3.5/dist-packages/sympy/geometry/polygon.py":[2304,1,3,4,5,6,7,8,9,10,11,12,13,15,16,17,18,19,21,24,2330,2335,1824,291,2340,1286,2345,391,1583,2352,1332,568,2363,1854,320,2272,1602,1035,2117,1095,1097,1099,1357,1878,1116,1634,356,1382,2152,1641,1132,1645,1902,1135,113,1138,115,1557,2178,1155,961,1930,654,627,1454,1432,1177,2207,1700,1702,939,1966,431,1203,1205,443,2080,2243,1220,711,203,1997,1744,1315,982,480,1249,740,1512,1770,2027,237,1266,979,247,2054,1407,1534],"/media/androbin/Daten/git/phenny/modules/test/test_urbandict.py":[4,5,6,7,8,9,12,14,15,16,18,20,21,22,23,24,25,26,28,30,31,32,33],"/usr/lib/python3/dist-packages/nose/plugins/base.py":[98,100,101,102],"/usr/local/lib/python3.5/dist-packages/sympy/core/exprtools.py":[1,898,3,900,5,6,7,8,9,10,11,780,13,14,15,16,17,18,915,788,21,853,279,24,282,27,284,669,31,393,803,804,421,806,881,552,412,794,1196,686,862,777,786,799,906,654,1333,449,1093,12,714,844,206,847,791,850,398,783,280,865,482,868,975,747,876,402,808,911,886,892,255],"/usr/local/lib/python3.5/dist-packages/sympy/polys/fields.py":[1,3,5,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,24,25,283,542,31,544,36,37,295,553,298,43,304,264,307,310,312,570,315,576,321,323,325,327,330,587,334,525,338,597,527,95,97,98,100,358,259,402,275,148,277,389,138,142,145,286,269,153,156,158,163,261,166,30,182,440,453,203,205,479,230,492,272,240,243,247,248,250],"/usr/local/lib/python3.5/dist-packages/sympy/printing/rcode.py":[259,9,11,13,14,15,16,17,18,300,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,72,73,74,75,78,79,80,81,82,83,84,85,86,89,90,271,93,96,104,107,110,113,116,119,123,138,151,410,155,159,162,165,168,171,174,211,223,228,232,239,245,253],"/usr/local/lib/python3.5/dist-packages/requests/api.py":[129,98,75,97,101,71,72,11,13,143,16,115,88,57,58,61],"/usr/lib/python3/dist-packages/nose/pyversion.py":[129,131,133,70,136,154,152,89,90,91,92,118,94,110,49,50,51,52,53,54,56,58],"/usr/local/lib/python3.5/dist-packages/sympy/matrices/__init__.py":[17,19,25,5,6,23,9,21,15],"/usr/local/lib/python3.5/dist-packages/sympy/concrete/expr_with_intlimits.py":[1,3,4,133,6,234,9,10,170,14,15],"/usr/lib/python3/dist-packages/nose/plugins/doctests.py":[192,193,194,195,188,189,190,191],"/media/androbin/Daten/git/phenny/test/test_metar.py":[3,5,6,7,10,11,12,13,14,15,16,17,19,20,22],"/usr/local/lib/python3.5/dist-packages/sympy/polys/domains/pythonfinitefield.py":[16,1,3,5,6,8,10,11,12,14],"/usr/local/lib/python3.5/dist-packages/sympy/printing/fcode.py":[131,388,300,317,270,143,18,20,238,22,281,24,25,26,27,28,29,30,31,32,304,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,308,53,54,55,56,185,59,60,61,62,63,64,65,66,285,70,71,72,73,74,439,78,288,81,92,228,101,104,107,274,110,189,113,242,116,245,553,120,254],"/usr/local/lib/python3.5/dist-packages/requests/__init__.py":[66,67,69,70,71,75,76,118,83,84,110,86,87,90,91,93,94,95,97,98,99,100,101,102,103,41,43,44,45,46,111,112,49,50,51,54,55,121,58,59,61,62,63],"/usr/local/lib/python3.5/dist-packages/chardet/gb2312freq.py":[281,42,44],"/usr/local/lib/python3.5/dist-packages/mpmath/functions/hypergeometric.py":[1105,1,2,895,4,261,1089,257,265,1095,194,399,273,269,1110,345,479,1060,813,998,977,744,681,999,237,241,1348,245,56,249,58,59,253,309],"/usr/local/lib/python3.5/dist-packages/mpmath/matrices/__init__.py":[1,2],"/usr/local/lib/python3.5/dist-packages/sympy/polys/fglmtools.py":[1,3,105,5,6,89,8,72,131,76,85],"/usr/local/lib/python3.5/dist-packages/sympy/simplify/cse_main.py":[65,2,3,36,5,6,7,8,9,10,139,418,13,93,417,115,269,28,29],"/media/androbin/Daten/git/phenny/proto.py":[4,6,7,9,15,24,25,27,28,30,34,37,40,41,43,44,46,49,50,53,54,55],"/usr/local/lib/python3.5/dist-packages/sympy/external/importtools.py":[1,3,4,5,129,11,12,15,144,146,19,20,21,22,132,27,133,32,33,34,35,111,112,113,114,116,119,143],"/usr/local/lib/python3.5/dist-packages/sympy/functions/special/beta_functions.py":[1,99,4,103,10,107,110,3,84,85,86,88],"/usr/local/lib/python3.5/dist-packages/sympy/polys/domains/mpelements.py":[1,3,5,7,8,11,13,15,16,17,19,21,22,152,26,86,29,30,31,33,35,36,38,40,43,45,46,48,49,51,52,56,57,58,59,60,61,62,63,64,66,67,68,70,71,77,79,82,84,85,24,87,88,89,91,92,93,94,96,99,111,114,118,122],"/usr/local/lib/python3.5/dist-packages/sympy/functions/combinatorial/__init__.py":[1,2],"/usr/local/lib/python3.5/dist-packages/sympy/plotting/plot.py":[768,770,1074,772,774,519,300,1036,1733,1045,1040,1048,531,1044,533,535,1028,537,27,28,29,30,287,32,33,34,35,36,37,550,1063,1064,809,555,44,818,559,48,1073,306,51,567,824,313,826,59,320,651,325,70,840,1097,330,849,338,343,1113,346,1141,863,864,354,1083,1638,871,872,364,370,1299,372,702,374,376,819,383,1088,902,393,23,450,654,656,657,658,660,1430,25,664,154,156,666,414,415,417,710,1075,424,668,427,685,431,432,689,434,701,446,191,704,706,1526,198,204,802,791,209,1059,212,216,219,733,735,736,749,680,758,251],"/usr/local/lib/python3.5/dist-packages/requests/_internal_utils.py":[37,38,39,40,9,11,14,19,20,22,25,27,30],"/usr/local/lib/python3.5/dist-packages/requests/adapters.py":[43,263,9,266,11,12,525,14,15,16,17,18,19,20,21,22,23,24,388,26,27,29,30,31,288,34,35,36,38,40,41,42,299,301,46,47,48,49,307,308,53,319,55,56,52,58,59,309,320,323,495,311,76,269,81,338,340,341,342,313,346,347,350,352,272,273,106,107,108,274,110,111,112,113,114,285,117,118,120,276,122,123,124,126,405,128,282,132,364,407,493,144,286,366,25,408,409,411,231,157,158,159,161,162,164,423,426,428,429,430,413,432,433,434,435,436,437,438,439,440,492,279,201,290,213,215,218,221,222,224,228,230,337,489,431,235,236,237,239,498,501,504,508,253],"/usr/local/lib/python3.5/dist-packages/requests/models.py":[513,515,746,8,521,10,11,12,526,17,530,19,20,21,22,25,26,27,29,30,31,34,35,548,683,39,948,883,42,43,559,48,49,50,51,564,565,566,55,56,57,607,60,61,574,575,65,578,67,581,70,584,585,75,588,589,590,591,80,82,599,91,604,93,95,96,97,610,615,105,618,109,531,629,532,635,638,641,875,534,652,621,660,663,673,943,170,171,882,174,687,177,179,180,182,702,194,707,709,714,719,724,633,216,219,220,223,224,225,226,227,229,230,743,744,233,234,235,236,237,238,239,240,241,243,246,761,763,765,768,770,772,774,264,777,779,77,820,280,282,284,286,288,560,291,293,845,295,297,810,300,301,814,304,305,306,307,308,309,823,52,315,828,317,830,320,321,322,323,324,325,326,327,328,329,331,844,333,334,335,848,568,851,825,856,347,741,912,354,868,357,360,745,365,573,880,881,370,371,884,375,892,381,894,388,393,397,398,400,401,915,916,405,408,855,866,926,69,928,929,931,420,934,423,424,937,430,431,433,946,947,436,437,438,73,440,441,442,444,816,451,452,454,76,462,463,464,467,468,78,472,847,337,935,594,495,498,505,508,511],"/usr/local/lib/python3.5/dist-packages/chardet/sbcharsetprober.py":[33,34,35,36,37,70,39,77,53,124,29,30,63],"/usr/local/lib/python3.5/dist-packages/chardet/charsetgroupprober.py":[32,33,49,65,39,57,28,29,85],"/usr/local/lib/python3.5/dist-packages/sympy/printing/ccode.py":[256,262,12,14,16,17,18,19,20,21,22,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,299,45,46,47,48,49,50,435,52,54,55,308,62,63,64,65,66,69,71,336,338,270,352,353,354,355,356,357,362,107,108,109,110,111,112,369,115,116,117,118,119,120,276,122,123,126,127,129,363,133,390,136,393,139,396,142,399,145,387,148,366,152,367,414,368,411,420,166,423,405,170,430,431,371,287,193,196,199,202,290,205,208,121,408,379,402,240,417,562,245,249],"/media/androbin/Daten/git/phenny/modules/test/test_iso639.py":[6,7,8,9,10,11,14,16,17,19,20,22,23,25,26,30,31,32,33,34,35,36,37,38,39,42,45,46,48,50,51,52,54,55,56,57,60,61,62,64,65,66,68,69,70,71,72,74,76,77,79,80,81,82,83,85,87,88,90,91,92,93,94,95,96,97],"/usr/lib/python3/dist-packages/nose/case.py":[129,130,131,132,133,134,291,264,305,268,141,280,148,149,150,152,281,155,156,285,30,160,161,162,163,164,37,38,39,40,41,42,43,174,173,46,49,50,179,180,286,52,186,168,60,61,181,302,65,203,70,71,201,75,204,34,206,205,35,284,267,207,198,100,101,102,103,104,105,167,239,240,241,242,243,244,169,276,273,277],"/usr/local/lib/python3.5/dist-packages/urllib3/exceptions.py":[1,2,8,9,10,13,14,15,18,19,20,21,22,24,29,30,31,32,33,35,40,41,42,45,46,47,50,51,52,55,56,57,61,66,74,76,77,79,80,82,85,86,88,94,95,96,99,104,105,108,109,110,115,116,117,120,121,122,125,126,127,130,131,132,135,136,137,140,141,143,150,151,152,153,156,157,158,161,162,163,166,167,168,171,172,173,176,177,178,181,182,183,186,190,191,194,195,196,199,203,204,207,214,215,218,223,224,225,228,229,232,237,238,239,244,245,246],"/usr/local/lib/python3.5/dist-packages/sympy/concrete/gosper.py":[1,2,83,4,5,6,7,8,11,159],"/usr/lib/python3/dist-packages/nose/plugins/xunit.py":[192,193,191],"/usr/local/lib/python3.5/dist-packages/sympy/solvers/solvers.py":[1024,1027,1035,1036,13,15,17,1042,19,20,21,22,25,26,27,28,29,30,32,1057,34,36,38,39,40,41,42,43,45,46,47,48,1073,50,947,52,53,55,56,57,60,65,70,71,2120,77,1037,80,1976,1108,1977,1039,2656,1121,1122,1123,2032,2492,1133,111,112,113,114,1043,116,1141,1142,119,1656,1044,122,123,124,125,1045,2496,132,2016,1158,925,364,1173,1174,1175,1177,1178,1136,1053,1148,115,1054,2028,1993,1208,1209,3018,1056,1219,1220,1222,1224,1226,1143,1058,349,1144,121,1147,1252,1404,1262,1264,1064,1139,1065,1338,129,2318,1227,2853,2357,1334,823,312,825,826,1339,1342,1857,3394,835,839,840,845,847,824,851,852,2040,855,2394,861,862,352,354,355,356,358,359,360,874,876,877,367,885,887,2424,1401,1402,892,893,1406,1407,1408,897,900,901,902,833,904,905,2442,913,916,917,918,921,924,2461,926,929,931,1964,942,943,1970,1971,1972,1973,950,951,952,953,954,2491,1980,1981,958,960,966,1992,969,1994,1996,975,976,2001,2002,2007,2008,2010,2011,2012,2013,990,991,992,2017,2018,2019,2021,2022,2023,2024,2025,2026,1004,1005,1006,2031,1008,1009,1363,1014,1015,1016,1017,1018,1019,1021,1022,1023],"/usr/local/lib/python3.5/dist-packages/sympy/series/limitseq.py":[1,3,5,6,7,8,9,58,12,102,109],"/usr/lib/python3/dist-packages/nose/selector.py":[35,37,40,41,42,43,44,45,53,54,56,66,67,70,71,72,74,75,78,79,87,88,89,92,93,94,95,96,100,101,112,113,114,115,116,117,118,119,120,121,122,123,125,126,127,130,131,136,137,140,144,145,148,149,150,152,153,158,159,163,165,166,167,170,171,172,174,175,183,184,187,188,189,190,192,193,218,220,221,222,223,224,225,228,229,230,231,232,233,234,235,236,237],"/usr/local/lib/python3.5/dist-packages/urllib3/util/request.py":[1,2,4,5,7,8,11,12,77,82,84,92,95],"/usr/local/lib/python3.5/dist-packages/chardet/utf8prober.py":[35,36,38,76,44,49,53,57,28,29,30,31],"/usr/local/lib/python3.5/dist-packages/sympy/matrices/expressions/dotproduct.py":[1,3,4,5,6,9,27,29,46],"/usr/local/lib/python3.5/dist-packages/sympy/solvers/solveset.py":[1920,258,773,9,10,651,12,13,14,15,16,17,18,21,23,24,26,27,924,29,30,31,928,33,34,932,37,422,1905,554,1949,305,1330,521,1339,1340,32,579,329,397,121,217,1888,1121,294,486,110,113,1879,1017,379],"/media/androbin/Daten/git/phenny/modules/test/test_greeting.py":[3,5,6,7,8,10,12,13,14,15,16,18,19,21,23,25,26,28,29,31,32,33,34,36,37,38,39,41,43,45,46,48,50,51],"/media/androbin/Daten/git/phenny/modules/__init__.py":[1,2,4],"/usr/local/lib/python3.5/dist-packages/sympy/polys/domains/domainelement.py":[1,3,17,5,7,8,15],"/usr/local/lib/python3.5/dist-packages/sympy/functions/special/polynomials.py":[1025,130,1003,132,979,780,7,9,778,11,12,13,14,15,16,17,18,19,20,21,22,279,24,981,924,415,34,731,37,1105,39,424,41,647,173,46,687,807,819,933,1195,54,1080,826,1217,782,1078,1244,201,716,386,525,593,514,595,597,902,343,216,719,1116,1082,917,677,408,485,1126,487,489,679,983,1234,878,880,211,628,885,1014,1193,532,639,341],"/media/androbin/Daten/git/phenny/test/test_irc.py":[3,5,6,7,8,9,12,13,14,16,17,18,19,21,22,23,24,26,27,28,29,32,43,44,45,47,48,49,51,53,54,56,57,58,61,63,64,66,68,70,72,74,75,77,78,79,80,81,82,84,85,87,89,91,93,95,96,98],"/usr/local/lib/python3.5/dist-packages/sympy/ntheory/partitions_.py":[1,2,195,37,7,8,9,10,123,13,141],"/media/androbin/Daten/git/phenny/bot.py":[8,10,139,12,13,14,15,16,17,18,19,21,279,25,26,27,39,115,55,23,57,58,59,60,61,62,64,194,11,201,56,211,212,213,214,215,216,217,218,219,220,221,222,223,225,227,235,243,124],"/usr/local/lib/python3.5/dist-packages/sympy/series/sequences.py":[256,1,3,4,5,6,7,9,10,11,12,13,14,15,16,17,18,19,788,278,792,772,27,28,30,31,33,546,550,689,296,554,46,561,54,59,437,64,833,578,835,69,74,781,595,84,602,912,101,796,871,108,274,114,371,640,642,390,392,907,396,400,404,408,155,283,168,681,685,942,431,944,433,773,181,441,698,445,449,707,453,801,714,201,458,978,218,79,222,806,749,239,243,1014,504,506],"/usr/local/lib/python3.5/dist-packages/sympy/functions/special/__init__.py":[1,2,3,4,5,6,7,8,9,11],"/media/androbin/Daten/git/phenny/modules/test/test_wiktionary.py":[5,6,7,8,9,10,13,15,16,17,19,21,22,24,26,27,28,30,32,33,34,35,36,38,40,41,42,43,44,45,46,48,49,50,51],"/usr/local/lib/python3.5/dist-packages/sympy/interactive/session.py":[32,1,3,5,279,7,8,172,247,140,17,23,88,313,314,315,29,312],"/usr/local/lib/python3.5/dist-packages/requests/cookies.py":[512,514,515,516,262,520,428,10,523,12,13,14,15,17,18,20,21,279,536,537,26,47,542,287,91,36,38,39,40,41,43,300,46,93,49,50,52,55,56,316,66,67,69,70,72,331,79,80,82,83,345,85,87,344,89,271,349,351,352,353,98,357,103,105,110,112,113,115,119,532,126,127,130,132,133,136,535,322,142,143,144,402,147,409,415,417,418,419,422,423,169,426,172,437,347,188,190,75,202,377,472,219,228,166,236,338,245,503,529,253,511],"/usr/local/lib/python3.5/dist-packages/requests/status_codes.py":[3,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,25,26,27,28,29,30,31,32,33,34,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,72,73,74,75,76,77,78,79,80,81,82,85,87,88,89,90,91],"/usr/local/lib/python3.5/dist-packages/chardet/cp949prober.py":[34,35,47,43,28,29,30,31],"/usr/local/lib/python3.5/dist-packages/sympy/series/limits.py":[96,1,98,3,4,5,6,7,8,9,10,11,12,13,14,16,83,52,121,130],"/media/androbin/Daten/git/phenny/modules/queue.py":[256,257,258,259,4,6,7,9,10,11,268,13,301,16,17,18,19,20,21,22,23,24,25,26,27,265,30,31,32,33,34,35,292,37,38,39,40,291,298,263,44,45,302,47,50,51,52,309,54,55,56,57,266,223,61,62,64,65,267,68,69,71,73,74,75,77,78,79,80,81,83,87,89,90,91,92,94,97,99,300,103,104,106,114,275,116,117,118,120,276,122,123,124,125,299,277,278,279,145,146,149,150,152,156,282,158,159,160,161,162,283,164,165,166,168,169,199,172,174,157,178,182,183,184,186,287,188,189,191,288,195,196,197,289,200,201,203,204,290,208,209,210,211,212,213,214,218,219,293,224,225,226,227,294,231,232,236,237,238,239,240,244,245,247,248,249,250,251,255],"/usr/local/lib/python3.5/dist-packages/chardet/langcyrillicmodel.py":[320,65,322,323,328,321,314,329,330,331,332,141,84,302,278,282,283,284,285,286,287,327,291,292,293,294,103,296,295,300,301,46,303,304,305,309,310,311,312,313,122,318,319],"/usr/local/lib/python3.5/dist-packages/sympy/geometry/util.py":[480,290,67,566,369,12,13,15,16,81,18,628,21,214,152,26],"/usr/local/lib/python3.5/dist-packages/mpmath/libmp/libmpi.py":[4,773,6,8,781,273,22,473,516,27,86,29,517,800,801,802,803,292,518,806,39,41,299,44,47,520,818,54,61,62,64,97,73,82,342,855,100,90,96,353,98,99,612,358,102,617,363,622,626,630,125,128,642,131,278,656,665,669,928,929,931,932,454,934,935,680,426,429,285,432,689,436,440,698,374,710,199,305,214,804,474,475,476,480,38,748,493,752,758],"/media/androbin/Daten/git/phenny/modules/test/test_calc.py":[5,6,7,8,11,13,14,15,17,18,19,20,22,23,24,25,27,28,29,30,32,33,34,35,37,38,39,40],"/media/androbin/Daten/git/phenny/modules/test/test_tfw.py":[5,6,7,8,9,10,13,15,16,17,19,21,22,23,24,26,28,29,30,31,32,33,35,37,38,39,40,41,42,44,46,47,48,49,50,51,53,55,56,57,58,59,60,62,64,65,66,67,68,69,70],"/usr/local/lib/python3.5/dist-packages/sympy/polys/domains/domain.py":[1,3,5,7,8,10,11,12,14,15,17,18,19,532,21,22,23,25,26,539,28,29,31,32,33,34,35,36,37,38,39,41,42,519,44,45,46,48,515,50,51,53,54,58,59,63,66,523,69,72,75,78,82,340,86,344,89,527,348,500,529,360,106,364,368,372,379,384,491,389,394,535,399,403,407,411,415,160,419,164,423,427,173,431,177,435,181,439,185,443,189,447,193,451,197,455,201,459,205,463,209,467,213,471,217,475,222,479,226,483,230,487,235,238,496,244,507,511],"/usr/local/lib/python3.5/dist-packages/sympy/deprecated/__init__.py":[10,12],"/usr/local/lib/python3.5/dist-packages/pbr/__init__.py":[1],"/usr/lib/python3/dist-packages/nose/plugins/manager.py":[128,178,262,263,264,265,167,272,273,274,169,149,249,88,89,111,93,94,95,96,99,184,166,295,168,124,106,107,301,302,285,177,114,123,118,105,120,121,250,251,252,253,254],"/usr/local/lib/python3.5/dist-packages/urllib3/util/response.py":[1,2,4,69,38,7,15,78,79,81,18,54,58,59,65,61,62,63],"/usr/local/lib/python3.5/dist-packages/sympy/simplify/ratsimp.py":[1,3,4,5,6,7,8,9,33,11],"/usr/local/lib/python3.5/dist-packages/sympy/core/rules.py":[48,50,3,5,54,8,57,63],"/usr/local/lib/python3.5/dist-packages/sympy/geometry/exceptions.py":[8,1,3,6,7],"/media/androbin/Daten/git/phenny/web.py":[131,132,6,8,9,10,11,12,13,14,15,16,17,18,19,22,23,25,26,28,30,31,33,36,37,38,39,40,42,44,47,48,49,50,53,55,56,57,58,59,60,62,63,65,66,69,70,72,74,75,76,77,79,80,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,101,103,104,106,107,108,109,111,119,121],"/media/androbin/Daten/git/phenny/modules/tfw.py":[9,11,12,13,14,15,18,21,22,26,28,29,30,32,33,34,39,43,44,47,48,49,50,52,53,54,56,58,92,93,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,170,172,174,176,178,181,182,184,185,186,187,188,190,192,193,194,195,196,199,201,202,205,208,210,212,213,215],"/usr/local/lib/python3.5/dist-packages/sympy/core/mod.py":[1,3,4,149,7,24,153,26,143],"/usr/local/lib/python3.5/dist-packages/chardet/escsm.py":[129,131,132,133,134,135,136,28,243,170,174,175,176,177,178,179,180,181,182,185,187,188,189,62,191,192,66,67,68,69,70,71,74,76,77,78,79,80,81,226,230,231,232,233,234,237,239,240,241,242,115,244,190,119,120,121,122,123,124,125,126],"/usr/local/lib/python3.5/dist-packages/sympy/matrices/expressions/inverse.py":[68,1,34,3,4,37,6,33,10,7,45,49,35,53,56,67,71,60,90],"/usr/local/lib/python3.5/dist-packages/sympy/functions/special/spherical_harmonics.py":[192,1,3,4,5,6,7,8,9,10,11,12,205,14,16,210,195,142,219,161,168,233,144,263,300,241,187,298],"/usr/local/lib/python3.5/dist-packages/chardet/jpcntx.py":[31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,116,117,118,119,120,121,123,131,143,170,173,180,183,184,188,192,212,213],"/usr/local/lib/python3.5/dist-packages/chardet/langbulgarianmodel.py":[224,225,213,227,72,226,209,53,214,215,216,217,218,222,223],"/usr/local/lib/python3.5/dist-packages/chardet/langturkishmodel.py":[192,52,183,187,188,189,190,191],"/usr/local/lib/python3.5/dist-packages/sympy/core/containers.py":[256,260,7,9,11,12,13,14,143,17,146,48,263,44,46,47,176,49,50,52,236,116,58,59,61,64,65,67,225,75,206,208,120,83,90,92,221,97,228,102,103,232,105,108,111,240,244,248,252],"/usr/local/lib/python3.5/dist-packages/sympy/plotting/__init__.py":[1,2,3,4,5],"/usr/local/lib/python3.5/dist-packages/sympy/logic/inference.py":[1,2,4,5,6,9,214,215,216,90,221,261,224,278,227,230,38,235,236,238,114,184],"/usr/local/lib/python3.5/dist-packages/sympy/external/__init__.py":[16,18],"/usr/local/lib/python3.5/dist-packages/idna/idnadata.py":[3,5,38,40,55,57,66,68,72,74,82,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,371,372,373,374,375,376,377,378,379,380,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,400,401,402,403,404,405,406,407,408,409,410,411,412,413,414,415,416,417,418,419,420,421,422,423,424,425,426,427,428,429,430,431,432,433,434,435,436,437,438,439,440,441,442,443,444,445,446,447,448,449,450,451,452,453,454,455,456,457,458,459,460,461,462,463,464,465,466,467,468,469,470,471,472,473,474,475,476,477,478,479,480,481,482,483,484,485,486,487,488,489,490,491,492,493,494,495,496,497,498,499,500,501,502,503,504,505,506,507,508,509,510,511,512,513,514,515,516,517,518,519,520,521,522,523,524,525,526,527,528,529,530,531,532,533,534,535,536,537,538,539,540,541,542,543,544,545,546,547,548,549,550,551,552,553,554,555,556,557,558,559,560,561,562,563,564,565,566,567,568,569,570,571,572,573,574,575,576,577,578,579,580,581,582,583,584,585,586,587,588,589,592,1572,1583,1574,1577,1575],"/media/androbin/Daten/git/phenny/modules/search.py":[57,68,69,70,77,8,76,10,11,12,13,14,15,80,17,82,84,78,22,56,24,79,94,96,81,18,46,48,49,51,53,72,20,58,59,60,74],"/usr/local/lib/python3.5/dist-packages/sympy/plotting/pygletplot/__init__.py":[2,4,6,7],"/usr/local/lib/python3.5/dist-packages/sympy/strategies/branch/traverse.py":[1,3,5,7,8,10,15],"/usr/local/lib/python3.5/dist-packages/sympy/polys/domains/integerring.py":[1,3,5,6,7,40,9,11,13,14,15,17,19,20,21,23,24,26,35,31],"/usr/local/lib/python3.5/dist-packages/sympy/printing/pretty/pretty_symbology.py":[1,3,5,6,7,9,12,13,15,17,18,25,31,32,42,43,557,46,49,75,78,79,82,85,87,88,91,95,96,107,115,116,118,120,123,124,125,128,129,130,133,134,135,136,137,138,139,140,141,142,146,147,148,149,150,151,152,153,154,157,158,159,163,164,165,166,168,169,170,172,173,176,177,179,180,182,183,185,186,187,189,190,191,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,220,221,222,223,227,228,229,230,231,232,233,234,241,242,243,244,245,246,247,249,250,251,253,254,255,257,258,259,260,262,263,267,268,275,276,282,283,289,290,292,293,294,296,297,299,302,303,306,307,311,379,387,397,398,399,404,408,409,410,411,412,413,414,415,416,417,418,419,420,421,422,428,429,430,431,432,433,434,435,436,437,438,439,440,441,442,443,444,447,448,449,450,454,468,469,470,471,474,475,476,477,478,479,480,481,482,483,484,485,486,490,501],"/usr/local/lib/python3.5/dist-packages/mpmath/ctx_iv.py":[1,3,5,7,533,534,23,536,25,27,29,30,31,36,39,41,42,44,50,57,60,63,65,70,75,81,87,91,94,95,96,97,99,103,106,115,123,124,125,126,127,128,130,132,134,135,137,535,140,142,143,144,145,146,147,149,156,161,164,169,174,179,184,188,192,196,202,206,220,221,223,224,226,227,228,230,233,234,236,243,249,255,263,268,270,271,272,273,274,276,277,279,280,281,282,283,284,289,291,293,294,295,296,297,298,299,300,301,302,303,304,306,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,330,331,332,333,334,335,336,337,338,339,340,342,343,354,356,358,359,370,371,373,374,375,377,381,382,384,385,386,387,389,394,395,396,397,398,400,401,403,405,409,410,411,412,415,416,417,418,419,422,423,426,427,430,431,433,442,456,459,462,472,478,481,484,489,502,505,508],"/usr/local/lib/python3.5/dist-packages/urllib3/util/ssl_.py":[1,2,3,4,6,7,264,9,267,12,13,14,15,272,271,274,19,20,21,278,153,25,282,283,284,285,329,260,326,219,207,262,38,39,324,42,43,44,45,303,304,328,50,51,180,311,312,313,191,279,194,203,196,198,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,90,91,220,257,208,269,195,259,275,322,254],"/usr/local/lib/python3.5/dist-packages/requests/compat.py":[64,65,66,67,68,69,9,11,13,20,23,26,28,29,30,31,37,56,57,58,59,60,61,62],"/usr/local/lib/python3.5/dist-packages/sympy/printing/repr.py":[128,131,6,8,137,10,11,12,13,14,141,17,18,21,24,158,161,164,134,167,176,173,48,180,30,184,188,61,53,193,67,70,73,202,76,79,56,82,85,145,107,109,112,115,118],"/usr/local/lib/python3.5/dist-packages/sympy/plotting/intervalmath/lib_interval.py":[1,3,4,5,390,201,10,331,288,24,58,352,436,152,79,93,158,224,416,36,235,180,310,377,122,266,255],"/usr/local/lib/python3.5/dist-packages/sympy/integrals/integrals.py":[1,652,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,986,28,29,31,33,354,651,139,81,104,974,78,993,1113,1138,565,964,1300],"/usr/local/lib/python3.5/dist-packages/chardet/chardistribution.py":[192,193,171,132,133,70,199,139,224,84,151,152,100,218,28,30,32,34,36,170,177,40,41,42,43,44,46,113,114,158,105,120,217,61],"/usr/local/lib/python3.5/dist-packages/sympy/concrete/products.py":[1,3,4,5,6,7,8,9,10,11,204,333,14,397,208,338,195,481,228,233,199,236,189,329,187,202,191],"/usr/local/lib/python3.5/dist-packages/urllib3/response.py":[1,2,3,4,5,6,7,9,10,343,14,15,16,17,19,533,22,535,24,538,175,540,29,542,517,32,549,550,551,556,557,558,559,560,513,566,55,568,57,58,571,60,61,63,64,65,66,580,69,70,71,586,76,592,596,526,598,599,600,601,602,603,604,605,607,611,612,616,617,618,108,621,110,111,113,114,115,116,118,119,122,123,124,125,126,127,128,130,131,132,133,134,136,139,140,142,143,146,147,148,622,150,151,152,155,158,161,539,625,626,174,541,176,178,179,181,543,190,194,202,206,208,219,220,226,227,230,234,239,240,245,246,248,250,256,257,258,260,264,265,266,273,276,278,283,284,285,287,289,298,300,301,302,324,328,567,569,344,346,367,368,371,374,375,377,378,383,384,385,393,394,395,403,404,405,406,408,410,413,415,582,431,432,433,435,436,438,439,441,450,452,453,454,459,460,461,462,463,464,465,466,467,468,471,474,475,478,482,489,491,493,494,500,509,597],"/usr/lib/python3/dist-packages/nose/plugins/deprecated.py":[40,42,43,44],"/usr/local/lib/python3.5/dist-packages/sympy/utilities/runtests.py":[1283,1285,1035,1804,13,1038,15,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,35,36,37,38,39,41,2092,137,570,61,62,2295,320,65,69,72,908,842,844,1037,2129,1875,1878,1294,1622,87,1882,92,1885,1887,1888,101,105,1085,1392,1512,1879,2281,1913,2239,124,64,131,132,645,902,135,136,905,906,907,140,141,143,1425,146,2286,157,158,159,133,2213,2075,1701,1308,2220,1712,1714,2230,2249,901,2235,134,1269,1985,1986,2244,1916,247,2256,248,2083,2263,1247,1062,1256,489,1771,1772,1774,1116,1777,1525,1782,1527,1784,249],"/media/androbin/Daten/git/phenny/tools.py":[8,10,11,12,13,14,15,16,17,18,19,20,22,280,26,30,32,34,40,41,43,44,45,46,47,49,50,51,56,57,58,75,76,78,79,81,82,84,85,87,88,90,91,92,94,95,96,97,99,100,101,103,104,105,106,107,109,110,112,113,114,116,117,121,122,123,124,131,132,134,135,137,139,141,144,145,146,148,150,151,152,156,157,160,164,166,167,169,171,172,174,175,177,178,180,182,183,185,187,188,192,194,195,197,199,200,201,202,203,207,210,212,213,215,227,228,229,230,232],"/usr/local/lib/python3.5/dist-packages/sympy/core/alphabets.py":[1,6],"/usr/lib/python3/dist-packages/nose/plugins/failuredetail.py":[33,35,36],"/media/androbin/Daten/git/phenny/modules/test/test_search.py":[4,5,6,7,8,11,13,15,16,18,19,21,22,24,26,27,29,30,31,32,34,36,37,38,40,41,43,44,45],"/media/androbin/Daten/git/phenny/modules/hs.py":[5,7,8,9,11,12,13,14,17,18,19,23,24,26,27,28,29,31,32,33,34,35,36,38,40,43,44,46,47,49,50,51,52,54,55,56,58,59,61],"/usr/local/lib/python3.5/dist-packages/sympy/polys/domains/__init__.py":[1,3,5,6,7,9,10,11,13,14,15,17,18,19,21,22,23,25,26,27,29,30,31,33,34,35,37,38,39,41,42,43,45,46,47,49,50,51,53,54,55,57,58,59,61,62,63,65,66,67,69,70,72,73,75,76,78,79,81,83,86,87,90,91,95,97,99,100,101,102,103],"/usr/local/lib/python3.5/dist-packages/sympy/polys/polytools.py":[6683,1,514,3,1028,5,2055,9,10,11,12,13,14,15,6655,17,4114,19,4022,21,2582,5655,3096,1049,4122,4123,2076,4125,30,31,32,33,34,35,4132,37,550,3626,2603,2093,48,50,51,52,54,55,57,6694,59,4106,62,63,2114,438,6726,584,4108,1610,6651,6175,3682,5441,3663,4177,4180,6666,2135,6232,527,4703,6077,610,5222,246,5223,104,106,3179,108,109,111,6256,1128,3701,2678,4215,1149,2174,4117,640,2391,131,3206,6176,3720,5740,5113,6283,6284,5997,5265,1170,147,1684,4121,153,1647,159,3233,674,6307,6308,165,4262,1191,4029,171,4269,1540,3586,5649,5813,6672,695,4639,1212,4103,1726,191,5854,2753,3779,4129,4809,6792,5735,716,2253,4968,1746,1571,1236,213,3798,6639,6607,2782,3758,4099,737,6589,5861,3817,5719,236,3309,1262,3965,4848,6333,242,2291,2292,2805,2294,5155,249,6231,4735,3836,6397,6255,1792,1283,6358,5381,777,2828,3117,472,3346,275,3860,1813,4374,4569,4671,1307,799,2851,4900,5039,3879,6535,6659,305,819,4153,1847,1338,6879,3902,4405,2880,2369,322,839,761,6857,4222,1872,3409,339,2901,6680,855,344,1369,5466,349,1424,354,5689,359,1896,4457,2922,875,3949,3438,3952,4072,3955,1086,2422,1400,891,1917,2945,387,3461,4998,3975,5512,6059,4930,6611,1773,1936,3985,2966,2455,6041,6475,3995,4509,3486,928,3739,4005,5542,6663,1450,2987,5572,6691,4015,944,6643,1973,2486,4539,3146,1473,963,4036,3013,6647,970,4043,4770,6095,6608,6609,4050,467,2007,2520,4057,476,2213,4064,4432,3042,2019,4068,5798,6630,3559,1512,4073,4075,4483,493,1007,2545,5411,4364,2039,5112,3065,5602,4604,3925],"/usr/local/lib/python3.5/dist-packages/requests/auth.py":[11,86,286,8,73,10,75,12,13,14,15,80,17,82,19,20,21,22,217,24,25,79,28,222,95,100,101,103,92,108,109,111,72,292,117,266,127],"/usr/local/lib/python3.5/dist-packages/urllib3/util/connection.py":[1,2,3,4,7,130,17,18,20,23,26,27,36,37,50,51,53,58,60,61,62,63,64,67,69,70,71,73,74,88,89,92,93,96,101,102,103,104,107,109,110,112,118,119,120,121,125,126,127],"/usr/local/lib/python3.5/dist-packages/pkg_resources/_vendor/six.py":[185,187],"/media/androbin/Daten/git/phenny/modules/clock.py":[8,10,11,12,13,14,15,16,17,18,19,20,21,22,23,25,27,30,31,33,34,36,37,38,40,42,43,44,46,48,49,83,84,87,89,92,94,95,97,98,100,101,102,103,105,107,110,112,120,122,129,137,146,147,148,149,151,161,162,172,174,176,177,178,185,187,188,189,190,193,198,201,206,209,214,217,222,225,226,228,229,230,231,233,235,236,238,239,240,241,242,243,244,245,247,248,249,251,254,255,257,259,261,262,264,266,269,270,272,273,274,275,277,279,280,282,283,284,285,287,288,289,290,292,293,295,297,299,300,302,304,306,307,308,310,316,317,318,320,327,328,330,331,333,335,336,337,338,339,341,342,344,346,347,348,349,350,351,352,353,354,355,357,359,360,361,362,364,367,368,369,370,371,372,373,374,375,376,377,378,379,381,382,385,438,461,462,465,470,473,478,480],"/usr/local/lib/python3.5/dist-packages/sympy/simplify/hyperexpand.py":[1280,513,1285,1286,1290,2434,15,1050,797,798,1055,1056,1058,1115,554,1393,1329,819,1845,1334,1335,1337,59,61,62,64,577,67,1092,69,70,1097,74,1100,78,79,80,81,82,1108,85,1682,1111,1499,603,604,606,1119,1120,1633,1122,100,614,870,1385,618,1131,914,879,624,881,1141,1142,1001,1144,1940,891,1130,895,2178,2179,390,65,1104,621,1424,1944,658,1126,916,1943,664,68,666,924,1182,1190,1389,1187,1188,934,1137,1449,682,684,1712,1242,956,1098,958,800,1398,1479,712,459,1229,1488,1234,1235,981,982,984,473,730,1359,988,989,734,991,480,481,483,911,997,1904,489,1002,1004,493,1133,497,1010,2046,1014,1015,1017,506,2175,510,1109],"/usr/local/lib/python3.5/dist-packages/sympy/sets/fancysets.py":[1,3,4,5,6,7,8,9,10,11,12,269,14,15,272,18,1155,277,279,280,282,47,800,292,295,632,810,1067,44,46,559,48,561,50,563,777,56,1151,1340,1290,64,70,812,1494,268,75,1230,1361,83,84,270,86,1380,95,1168,612,613,614,616,274,1144,1145,1147,1148,125,1150,383,1152,387,1156,1414,136,791,1167,144,1169,151,159,930,163,167,210,171,1196,1197,1198,1200,1480,1461,954,1482,1319,963,1222,1234,201,1226,203,972,1485,206,1488,209,978,1491,213,982,123,977,807,1261,499,204,127,765,1483],"/usr/lib/python3/dist-packages/nose/tools/nontrivial.py":[35,36,20,21,24,25,26,27,28,29,30,31]}} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..b6aaca6bb --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +*.pyc +.DS_Store +*~ +*.swp +__pycache__ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..50b505df1 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,11 @@ +language: python +sudo: false +cache: pip +python: +- 3.5 +- 3.6 +- 3.7 +install: +- pip install -r requirements.txt +script: +- LANG=en_US.UTF-8 nosetests diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..e745b71c1 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,16 @@ +FROM python:3 + +WORKDIR /usr/src/app + +COPY requirements.txt /usr/src/app/ +RUN pip install --no-cache-dir -r requirements.txt + +COPY . /usr/src/app/ + +RUN groupadd -r -g 200 bot \ + && useradd -mr -g bot -u 200 bot +USER bot + +VOLUME ["/home/bot/.phenny"] + +CMD ["/usr/src/app/phenny"] diff --git a/README.md b/README.md new file mode 100644 index 000000000..a5818a333 --- /dev/null +++ b/README.md @@ -0,0 +1,31 @@ +# phenny +[![Build Status](https://travis-ci.org/mutantmonkey/phenny.png?branch=master)](https://travis-ci.org/mutantmonkey/phenny) + +This is phenny, a Python IRC bot. Originally written by Sean B. Palmer, it has +been ported to Python3. + +This version comes with many new modules, IPv6 support, TLS support, and unit +tests. + +Compatibility with existing phenny modules has been mostly retained, but they +will need to be updated to run on Python3 if they do not already. All of the +core modules have been ported, removed, or replaced. + +## Requirements +* Python 3.4+ +* [python-requests](http://docs.python-requests.org/en/latest/) + +## Installation +1. Run `./phenny` - this creates a default config file +2. Edit `~/.phenny/default.py` +3. Run `./phenny` - this now runs phenny with your settings + +Enjoy! + +## Testing +You will need the Python3 versions of `python-nose` and `python-mock`. To run +the tests, simply run `nosetests3`. + +## Authors +* Sean B. Palmer, http://inamidst.com/sbp/ +* mutantmonkey, http://mutantmonkey.in diff --git a/README.txt b/README.txt deleted file mode 100644 index 4edf7c015..000000000 --- a/README.txt +++ /dev/null @@ -1,10 +0,0 @@ -Installation &c. - -1) Run ./phenny - this creates a default config file -2) Edit ~/.phenny/default.py -3) Run ./phenny - this now runs phenny with your settings - -Enjoy! - --- -Sean B. Palmer, http://inamidst.com/sbp/ diff --git a/__init__.py b/__init__.py index 5699583aa..deeffadae 100755 --- a/__init__.py +++ b/__init__.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 """ __init__.py - Phenny Init Module Copyright 2008, Sean B. Palmer, inamidst.com @@ -7,56 +7,81 @@ http://inamidst.com/phenny/ """ -import sys, os, time, threading, signal -import bot - -class Watcher(object): - # Cf. http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/496735 - def __init__(self): - self.child = os.fork() - if self.child != 0: - self.watch() - - def watch(self): - try: os.wait() - except KeyboardInterrupt: - self.kill() - sys.exit() - - def kill(self): - try: os.kill(self.child, signal.SIGKILL) - except OSError: pass - -def run_phenny(config): - if hasattr(config, 'delay'): - delay = config.delay - else: delay = 20 - - def connect(config): - p = bot.Phenny(config) - p.run(config.host, config.port) - - try: Watcher() - except Exception, e: - print >> sys.stderr, 'Warning:', e, '(in __init__.py)' - - while True: - try: connect(config) - except KeyboardInterrupt: - sys.exit() - - if not isinstance(delay, int): - break - - warning = 'Warning: Disconnected. Reconnecting in %s seconds...' % delay - print >> sys.stderr, warning - time.sleep(delay) - -def run(config): - t = threading.Thread(target=run_phenny, args=(config,)) - if hasattr(t, 'run'): - t.run() - else: t.start() - -if __name__ == '__main__': - print __doc__ +import os +import signal +import sys +import threading +import time + + +class Watcher(object): + # Cf. http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/496735 + def __init__(self): + self.child = os.fork() + if self.child != 0: + signal.signal(signal.SIGTERM, self.sig_term) + self.watch() + + def watch(self): + try: + os.wait() + except KeyboardInterrupt: + self.kill() + sys.exit() + + def kill(self): + try: + os.kill(self.child, signal.SIGKILL) + except OSError: + pass + + def sig_term(self, signum, frame): + self.kill() + sys.exit() + + +def run_phenny(config): + if hasattr(config, 'delay'): + delay = config.delay + else: + delay = 20 + + def connect(config): + import bot + p = bot.Phenny(config) + + ssl_context = p.get_ssl_context(config.ca_certs) + if config.ssl_cert and config.ssl_key: + ssl_context.load_cert_chain(config.ssl_cert, config.ssl_key) + p.run(config.host, config.port, config.ssl, config.ipv6, None, + ssl_context) + + try: + Watcher() + except Exception as e: + print('Warning:', e, '(in __init__.py)', file=sys.stderr) + + while True: + try: + connect(config) + except KeyboardInterrupt: + sys.exit() + + if not isinstance(delay, int): + break + + msg = "Warning: Disconnected. Reconnecting in {0} seconds..." + print(msg.format(delay), file=sys.stderr) + time.sleep(delay) + + +def run(config): + t = threading.Thread(target=run_phenny, args=(config,)) + if hasattr(t, 'run'): + t.run() + else: + t.start() + + +if __name__ == '__main__': + print(__doc__) diff --git a/bot.py b/bot.py index 453dbc4d7..4925acaf8 100755 --- a/bot.py +++ b/bot.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 """ bot.py - Phenny IRC Bot Copyright 2008, Sean B. Palmer, inamidst.com @@ -9,221 +9,236 @@ import sys, os, re, threading, imp import irc +import tools home = os.getcwd() def decode(bytes): - try: text = bytes.decode('utf-8') - except UnicodeDecodeError: - try: text = bytes.decode('iso-8859-1') - except UnicodeDecodeError: - text = bytes.decode('cp1252') - return text + if type(bytes) == str: + return bytes + try: + text = bytes.decode('utf-8') + except UnicodeDecodeError: + try: + text = bytes.decode('iso-8859-1') + except UnicodeDecodeError: + text = bytes.decode('cp1252') + except AttributeError: + return bytes + return text class Phenny(irc.Bot): - def __init__(self, config): - args = (config.nick, config.name, config.channels, config.password) - irc.Bot.__init__(self, *args) - self.config = config - self.doc = {} - self.stats = {} - self.setup() - - def setup(self): - self.variables = {} - - filenames = [] - if not hasattr(self.config, 'enable'): - for fn in os.listdir(os.path.join(home, 'modules')): - if fn.endswith('.py') and not fn.startswith('_'): - filenames.append(os.path.join(home, 'modules', fn)) - else: - for fn in self.config.enable: - filenames.append(os.path.join(home, 'modules', fn + '.py')) - - if hasattr(self.config, 'extra'): - for fn in self.config.extra: - if os.path.isfile(fn): - filenames.append(fn) - elif os.path.isdir(fn): - for n in os.listdir(fn): - if n.endswith('.py') and not n.startswith('_'): - filenames.append(os.path.join(fn, n)) - - modules = [] - excluded_modules = getattr(self.config, 'exclude', []) - for filename in filenames: - name = os.path.basename(filename)[:-3] - if name in excluded_modules: continue - # if name in sys.modules: - # del sys.modules[name] - try: module = imp.load_source(name, filename) - except Exception, e: - print >> sys.stderr, "Error loading %s: %s (in bot.py)" % (name, e) - else: - if hasattr(module, 'setup'): - module.setup(self) - self.register(vars(module)) - modules.append(name) - - if modules: - print >> sys.stderr, 'Registered modules:', ', '.join(modules) - else: print >> sys.stderr, "Warning: Couldn't find any modules" - - self.bind_commands() - - def register(self, variables): - # This is used by reload.py, hence it being methodised - for name, obj in variables.iteritems(): - if hasattr(obj, 'commands') or hasattr(obj, 'rule'): - self.variables[name] = obj - - def bind_commands(self): - self.commands = {'high': {}, 'medium': {}, 'low': {}} - - def bind(self, priority, regexp, func): - print priority, regexp.pattern.encode('utf-8'), func - # register documentation - if not hasattr(func, 'name'): - func.name = func.__name__ - if func.__doc__: - if hasattr(func, 'example'): - example = func.example - example = example.replace('$nickname', self.nick) - else: example = None - self.doc[func.name] = (func.__doc__, example) - self.commands[priority].setdefault(regexp, []).append(func) - - def sub(pattern, self=self): - # These replacements have significant order - pattern = pattern.replace('$nickname', re.escape(self.nick)) - return pattern.replace('$nick', r'%s[,:] +' % re.escape(self.nick)) - - for name, func in self.variables.iteritems(): - # print name, func - if not hasattr(func, 'priority'): - func.priority = 'medium' - - if not hasattr(func, 'thread'): - func.thread = True - - if not hasattr(func, 'event'): - func.event = 'PRIVMSG' - else: func.event = func.event.upper() - - if hasattr(func, 'rule'): - if isinstance(func.rule, str): - pattern = sub(func.rule) - regexp = re.compile(pattern) - bind(self, func.priority, regexp, func) - - if isinstance(func.rule, tuple): - # 1) e.g. ('$nick', '(.*)') - if len(func.rule) == 2 and isinstance(func.rule[0], str): - prefix, pattern = func.rule - prefix = sub(prefix) - regexp = re.compile(prefix + pattern) - bind(self, func.priority, regexp, func) - - # 2) e.g. (['p', 'q'], '(.*)') - elif len(func.rule) == 2 and isinstance(func.rule[0], list): - prefix = self.config.prefix - commands, pattern = func.rule - for command in commands: - command = r'(%s)\b(?: +(?:%s))?' % (command, pattern) - regexp = re.compile(prefix + command) - bind(self, func.priority, regexp, func) - - # 3) e.g. ('$nick', ['p', 'q'], '(.*)') - elif len(func.rule) == 3: - prefix, commands, pattern = func.rule - prefix = sub(prefix) - for command in commands: - command = r'(%s) +' % command - regexp = re.compile(prefix + command + pattern) - bind(self, func.priority, regexp, func) - - if hasattr(func, 'commands'): - for command in func.commands: - template = r'^%s(%s)(?: +(.*))?$' - pattern = template % (self.config.prefix, command) - regexp = re.compile(pattern) - bind(self, func.priority, regexp, func) - - def wrapped(self, origin, text, match): - class PhennyWrapper(object): - def __init__(self, phenny): - self.bot = phenny - - def __getattr__(self, attr): - sender = origin.sender or text - if attr == 'reply': - return (lambda msg: - self.bot.msg(sender, origin.nick + ': ' + msg)) - elif attr == 'say': - return lambda msg: self.bot.msg(sender, msg) - return getattr(self.bot, attr) - - return PhennyWrapper(self) - - def input(self, origin, text, bytes, match, event, args): - class CommandInput(unicode): - def __new__(cls, text, origin, bytes, match, event, args): - s = unicode.__new__(cls, text) - s.sender = origin.sender - s.nick = origin.nick - s.event = event - s.bytes = bytes - s.match = match - s.group = match.group - s.groups = match.groups - s.args = args - s.admin = origin.nick in self.config.admins - s.owner = origin.nick == self.config.owner - return s - - return CommandInput(text, origin, bytes, match, event, args) - - def call(self, func, origin, phenny, input): - try: func(phenny, input) - except Exception, e: - self.error(origin) - - def limit(self, origin, func): - if origin.sender and origin.sender.startswith('#'): - if hasattr(self.config, 'limit'): - limits = self.config.limit.get(origin.sender) - if limits and (func.__module__ not in limits): - return True - return False - - def dispatch(self, origin, args): - bytes, event, args = args[0], args[1], args[2:] - text = decode(bytes) - - for priority in ('high', 'medium', 'low'): - items = self.commands[priority].items() - for regexp, funcs in items: - for func in funcs: - if event != func.event: continue - - match = regexp.match(text) - if match: - if self.limit(origin, func): continue - - phenny = self.wrapped(origin, text, match) - input = self.input(origin, text, bytes, match, event, args) - - if func.thread: - targs = (func, origin, phenny, input) - t = threading.Thread(target=self.call, args=targs) - t.start() - else: self.call(func, origin, phenny, input) - - for source in [origin.sender, origin.nick]: - try: self.stats[(func.name, source)] += 1 - except KeyError: - self.stats[(func.name, source)] = 1 + def __init__(self, config): + args = (config.nick, config.name, config.channels, config.password) + irc.Bot.__init__(self, *args) + self.config = config + self.doc = {} + self.stats = {} + self.setup() + + def setup(self): + self.variables = {} + + filenames = [] + if not hasattr(self.config, 'enable'): + for fn in os.listdir(os.path.join(home, 'modules')): + if fn.endswith('.py') and not fn.startswith('_'): + filenames.append(os.path.join(home, 'modules', fn)) + else: + for fn in self.config.enable: + filenames.append(os.path.join(home, 'modules', fn + '.py')) + + if hasattr(self.config, 'extra'): + for fn in self.config.extra: + if os.path.isfile(fn): + filenames.append(fn) + elif os.path.isdir(fn): + for n in os.listdir(fn): + if n.endswith('.py') and not n.startswith('_'): + filenames.append(os.path.join(fn, n)) + + modules = [] + excluded_modules = getattr(self.config, 'exclude', []) + for filename in filenames: + name = os.path.basename(filename)[:-3] + if name in excluded_modules: continue + # if name in sys.modules: + # del sys.modules[name] + try: module = imp.load_source(name, filename) + except Exception as e: + print("Error loading %s: %s (in bot.py)" % (name, e), file=sys.stderr) + else: + if hasattr(module, 'setup'): + module.setup(self) + self.register(vars(module)) + modules.append(name) + + if modules: + print('Registered modules:', ', '.join(modules), file=sys.stderr) + else: print("Warning: Couldn't find any modules", file=sys.stderr) + + self.bind_commands() + + def register(self, variables): + # This is used by reload.py, hence it being methodised + for name, obj in variables.items(): + if hasattr(obj, 'commands') or hasattr(obj, 'rule'): + self.variables[name] = obj + + def bind_commands(self): + self.commands = {'high': {}, 'medium': {}, 'low': {}} + + def bind(self, priority, regexp, func): + print(priority, regexp.pattern.encode('utf-8'), func) + # register documentation + if not hasattr(func, 'name'): + func.name = func.__name__ + if func.__doc__: + if hasattr(func, 'example'): + example = func.example + example = example.replace('$nickname', self.nick) + else: example = None + self.doc[func.name] = (func.__doc__, example) + self.commands[priority].setdefault(regexp, []).append(func) + + def sub(pattern, self=self): + # These replacements have significant order + pattern = pattern.replace('$nickname', re.escape(self.nick)) + return pattern.replace('$nick', r'%s[,:] +' % re.escape(self.nick)) + + for name, func in self.variables.items(): + # print name, func + if not hasattr(func, 'priority'): + func.priority = 'medium' + + if not hasattr(func, 'thread'): + func.thread = True + + if not hasattr(func, 'event'): + func.event = 'PRIVMSG' + else: func.event = func.event.upper() + + if hasattr(func, 'rule'): + if isinstance(func.rule, str): + pattern = sub(func.rule) + regexp = re.compile(pattern) + bind(self, func.priority, regexp, func) + + if isinstance(func.rule, tuple): + # 1) e.g. ('$nick', '(.*)') + if len(func.rule) == 2 and isinstance(func.rule[0], str): + prefix, pattern = func.rule + prefix = sub(prefix) + regexp = re.compile(prefix + pattern) + bind(self, func.priority, regexp, func) + + # 2) e.g. (['p', 'q'], '(.*)') + elif len(func.rule) == 2 and isinstance(func.rule[0], list): + prefix = self.config.prefix + commands, pattern = func.rule + for command in commands: + command = r'(%s)\b(?: +(?:%s))?' % (command, pattern) + regexp = re.compile(prefix + command) + bind(self, func.priority, regexp, func) + + # 3) e.g. ('$nick', ['p', 'q'], '(.*)') + elif len(func.rule) == 3: + prefix, commands, pattern = func.rule + prefix = sub(prefix) + for command in commands: + command = r'(%s) +' % command + regexp = re.compile(prefix + command + pattern) + bind(self, func.priority, regexp, func) + + if hasattr(func, 'commands'): + for command in func.commands: + template = r'^%s(%s)(?: +(.*))?$' + pattern = template % (self.config.prefix, command) + regexp = re.compile(pattern) + bind(self, func.priority, regexp, func) + + def wrapped(self, origin, text, match): + class PhennyWrapper(object): + def __init__(self, phenny): + self.bot = phenny + + def __getattr__(self, attr): + sender = origin.sender or text + if attr == 'reply': + return (lambda msg: + self.bot.msg(sender, origin.nick + ': ' + msg)) + elif attr == 'say': + return lambda msg: self.bot.msg(sender, msg) + elif attr == 'do': + return lambda msg: self.bot.action(sender, msg) + return getattr(self.bot, attr) + + return PhennyWrapper(self) + + def input(self, origin, text, bytes, match, event, args): + class CommandInput(str): + def __new__(cls, text, origin, bytes, match, event, args): + s = str.__new__(cls, text) + s.sender = decode(origin.sender) + s.nick = decode(origin.nick) + s.event = event + s.bytes = bytes + s.match = match + s.group = match.group + s.groups = match.groups + s.args = args + s.admin = s.nick in self.config.admins + s.owner = s.nick == self.config.owner + return s + + return CommandInput(text, origin, bytes, match, event, args) + + def call(self, func, origin, phenny, input): + try: func(phenny, input) + except tools.GrumbleError as e: + self.msg(origin.sender, str(e)) + except Exception as e: + self.error(origin) + + def limit(self, origin, func): + if origin.sender and origin.sender.startswith('#'): + if hasattr(self.config, 'limit'): + limits = self.config.limit.get(origin.sender) + if limits and (func.__module__ not in limits): + return True + return False + + def dispatch(self, origin, args): + bytes, event, args = args[0], args[1], args[2:] + text = decode(bytes) + event = decode(event) + + if origin.nick in self.config.ignore: + return + + for priority in ('high', 'medium', 'low'): + items = list(self.commands[priority].items()) + for regexp, funcs in items: + for func in funcs: + if event != func.event and func.event != '*': continue + + match = regexp.match(text) + if match: + if self.limit(origin, func): continue + + phenny = self.wrapped(origin, text, match) + input = self.input(origin, text, bytes, match, event, args) + + if func.thread: + targs = (func, origin, phenny, input) + t = threading.Thread(target=self.call, args=targs) + t.start() + else: self.call(func, origin, phenny, input) + + for source in [decode(origin.sender), decode(origin.nick)]: + try: self.stats[(func.name, source)] += 1 + except KeyError: + self.stats[(func.name, source)] = 1 if __name__ == '__main__': - print __doc__ + print(__doc__) diff --git a/icao.py b/icao.py index 3ee121ae9..d350b2b6c 100755 --- a/icao.py +++ b/icao.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 """ icao.py - Phenny ICAO Codes Data This data and module are in the public domain. @@ -363,7 +363,6 @@ ("EDDF", 50.0263888889, 8.54305555556), ("EDDG", 52.1344444444, 7.68472222222), ("EDDH", 53.6302777778, 9.98805555556), - ("EDDI", 52.4727777778, 13.4038888889), ("EDDK", 50.8658333333, 7.1425), ("EDDL", 51.2894444444, 6.76666666667), ("EDDM", 48.3536111111, 11.7858333333), @@ -1354,6 +1353,7 @@ ("KAUS", 30.1944444444, -97.6697222222), ("KBAB", 39.1358333333, -121.436388889), ("KBAD", 32.5016666667, -93.6625), + ("KBCB", 37.207778, -80.407778), ("KBCT", 26.3783333333, -80.1075), ("KBDE", 48.7283333333, -94.6122222222), ("KBDL", 41.9388888889, -72.6830555556), diff --git a/irc.py b/irc.py index 251ed64bb..a95926f98 100755 --- a/irc.py +++ b/irc.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 """ irc.py - A Utility IRC Bot Copyright 2008, Sean B. Palmer, inamidst.com @@ -7,200 +7,257 @@ http://inamidst.com/phenny/ """ -import sys, re, time, traceback -import socket, asyncore, asynchat - -class Origin(object): - source = re.compile(r'([^!]*)!?([^@]*)@?(.*)') - - def __init__(self, bot, source, args): - match = Origin.source.match(source or '') - self.nick, self.user, self.host = match.groups() - - if len(args) > 1: - target = args[1] - else: target = None - - mappings = {bot.nick: self.nick, None: None} - self.sender = mappings.get(target, target) - -class Bot(asynchat.async_chat): - def __init__(self, nick, name, channels, password=None): - asynchat.async_chat.__init__(self) - self.set_terminator('\n') - self.buffer = '' - - self.nick = nick - self.user = nick - self.name = name - self.password = password - - self.verbose = True - self.channels = channels or [] - self.stack = [] - - import threading - self.sending = threading.RLock() - - def initiate_send(self): - self.sending.acquire() - asynchat.async_chat.initiate_send(self) - self.sending.release() - - # def push(self, *args, **kargs): - # asynchat.async_chat.push(self, *args, **kargs) - - def __write(self, args, text=None): - # print 'PUSH: %r %r %r' % (self, args, text) - try: - if text is not None: - # 510 because CR and LF count too, as nyuszika7h points out - self.push((' '.join(args) + ' :' + text)[:510] + '\r\n') - else: self.push(' '.join(args)[:510] + '\r\n') - except IndexError: - pass - - def write(self, args, text=None): - # This is a safe version of __write - def safe(input): - input = input.replace('\n', '') - input = input.replace('\r', '') - return input.encode('utf-8') - try: - args = [safe(arg) for arg in args] - if text is not None: - text = safe(text) - self.__write(args, text) - except Exception, e: pass - - def run(self, host, port=6667): - self.initiate_connect(host, port) - - def initiate_connect(self, host, port): - if self.verbose: - message = 'Connecting to %s:%s...' % (host, port) - print >> sys.stderr, message, - self.create_socket(socket.AF_INET, socket.SOCK_STREAM) - self.connect((host, port)) - try: asyncore.loop() - except KeyboardInterrupt: - sys.exit() - - def handle_connect(self): - if self.verbose: - print >> sys.stderr, 'connected!' - if self.password: - self.write(('PASS', self.password)) - self.write(('NICK', self.nick)) - self.write(('USER', self.user, '+iw', self.nick), self.name) - - def handle_close(self): - self.close() - print >> sys.stderr, 'Closed!' - - def collect_incoming_data(self, data): - self.buffer += data - - def found_terminator(self): - line = self.buffer - if line.endswith('\r'): - line = line[:-1] - self.buffer = '' - - # print 'GOT:', repr(line) - if line.startswith(':'): - source, line = line[1:].split(' ', 1) - else: source = None - - if ' :' in line: - argstr, text = line.split(' :', 1) - else: argstr, text = line, '' - args = argstr.split() - - origin = Origin(self, source, args) - self.dispatch(origin, tuple([text] + args)) - - if args[0] == 'PING': - self.write(('PONG', text)) - - def dispatch(self, origin, args): - pass - - def msg(self, recipient, text): - self.sending.acquire() - - # Cf. http://swhack.com/logs/2006-03-01#T19-43-25 - if isinstance(text, unicode): - try: text = text.encode('utf-8') - except UnicodeEncodeError, e: - text = e.__class__ + ': ' + str(e) - if isinstance(recipient, unicode): - try: recipient = recipient.encode('utf-8') - except UnicodeEncodeError, e: - return - - # No messages within the last 3 seconds? Go ahead! - # Otherwise, wait so it's been at least 0.8 seconds + penalty - if self.stack: - elapsed = time.time() - self.stack[-1][0] - if elapsed < 3: - penalty = float(max(0, len(text) - 50)) / 70 - wait = 0.8 + penalty - if elapsed < wait: - time.sleep(wait - elapsed) - - # Loop detection - messages = [m[1] for m in self.stack[-8:]] - if messages.count(text) >= 5: - text = '...' - if messages.count('...') >= 3: - self.sending.release() - return - - def safe(input): - input = input.replace('\n', '') - return input.replace('\r', '') - self.__write(('PRIVMSG', safe(recipient)), safe(text)) - self.stack.append((time.time(), text)) - self.stack = self.stack[-10:] - - self.sending.release() - - def notice(self, dest, text): - self.write(('NOTICE', dest), text) - - def error(self, origin): - try: - import traceback - trace = traceback.format_exc() - print trace - lines = list(reversed(trace.splitlines())) - - report = [lines[0].strip()] - for line in lines: - line = line.strip() - if line.startswith('File "/'): - report.append(line[0].lower() + line[1:]) - break - else: report.append('source unknown') - - self.msg(origin.sender, report[0] + ' (' + report[1] + ')') - except: self.msg(origin.sender, "Got an error.") - -class TestBot(Bot): - def f_ping(self, origin, match, args): - delay = m.group(1) - if delay is not None: - import time - time.sleep(int(delay)) - self.msg(origin.sender, 'pong (%s)' % delay) - else: self.msg(origin.sender, 'pong') - f_ping.rule = r'^\.ping(?:[ \t]+(\d+))?$' - -def main(): - # bot = TestBot('testbot', ['#d8uv.com']) - # bot.run('irc.freenode.net') - print __doc__ - -if __name__=="__main__": - main() +import asynchat +import asyncore +import functools +import proto +import re +import socket +import ssl +import sys +import time +import traceback +import threading +from tools import decorate + + +class Origin(object): + source = re.compile(r'([^!]*)!?([^@]*)@?(.*)') + + def __init__(self, bot, source, args): + if not source: + source = "" + match = Origin.source.match(source) + self.nick, self.user, self.host = match.groups() + + if len(args) > 1: + target = args[1] + else: + target = None + + mappings = {bot.nick: self.nick, None: None} + self.sender = mappings.get(target, target) + + +class Bot(asynchat.async_chat): + def __init__(self, nick, name, channels, password=None): + asynchat.async_chat.__init__(self) + self.set_terminator(b'\n') + self.buffer = b'' + + self.nick = nick + self.user = nick + self.name = name + self.password = password + + self.verbose = True + self.channels = channels or [] + self.stack = [] + + self.sending = threading.RLock() + + proto_func = lambda attr: functools.partial(proto.commands[attr], self) + proto_map = {attr: proto_func(attr) for attr in proto.commands} + self.proto = decorate(object(), proto_map) + + def initiate_send(self): + self.sending.acquire() + asynchat.async_chat.initiate_send(self) + self.sending.release() + + # def push(self, *args, **kargs): + # asynchat.async_chat.push(self, *args, **kargs) + + def __write(self, args, text=None): + line = b' '.join(args) + + if text is not None: + line += b' :' + text + + # 510 because CR and LF count too + self.push(line[:510] + b'\r\n') + + def write(self, args, text=None): + """This is a safe version of __write""" + def safe(input): + if type(input) == str: + input = re.sub(' ?(\r|\n)+', ' ', input) + return input.encode('utf-8') + else: + input = re.sub(b' ?(\r|\n)+', b' ', input) + return input + try: + args = [safe(arg) for arg in args] + if text is not None: + text = safe(text) + self.__write(args, text) + except Exception as e: + raise + + def run(self, host, port=6667, ssl=False, ipv6=False, ca_certs=None, + ssl_context=None): + if ssl_context is None: + ssl_context = self.get_ssl_context(ca_certs) + self.initiate_connect(host, port, ssl, ipv6, ssl_context) + + def get_ssl_context(self, ca_certs): + return ssl.create_default_context( + purpose=ssl.Purpose.SERVER_AUTH, + cafile=ca_certs) + + def initiate_connect(self, host, port, use_ssl, ipv6, ssl_context): + if self.verbose: + message = 'Connecting to %s:%s...' % (host, port) + print(message, end=' ', file=sys.stderr) + if ipv6 and socket.has_ipv6: + af = socket.AF_INET6 + else: + af = socket.AF_INET + self.create_socket(af, socket.SOCK_STREAM, use_ssl, host, ssl_context) + self.connect((host, port)) + try: + asyncore.loop() + except KeyboardInterrupt: + sys.exit() + + def create_socket(self, family, type, use_ssl=False, hostname=None, + ssl_context=None): + self.family_and_type = family, type + sock = socket.socket(family, type) + if use_ssl: + sock = ssl_context.wrap_socket(sock, server_hostname=hostname) + # FIXME: this doesn't work with SSL enabled + #sock.setblocking(False) + self.set_socket(sock) + + def handle_connect(self): + if self.verbose: + print('connected!', file=sys.stderr) + + if self.password: + self.proto.pass_(self.password) + + self.proto.nick(self.nick) + self.proto.user(self.user, '+iw', self.name) + + def handle_close(self): + self.close() + print('Closed!', file=sys.stderr) + + def collect_incoming_data(self, data): + self.buffer += data + + def found_terminator(self): + line = self.buffer + if line.endswith(b'\r'): + line = line[:-1] + self.buffer = b'' + + try: + line = line.decode('utf-8') + except UnicodeDecodeError: + line = line.decode('iso-8859-1') + + if line.startswith(':'): + source, line = line[1:].split(' ', 1) + else: + source = None + + if ' :' in line: + argstr, text = line.split(' :', 1) + else: + argstr, text = line, '' + args = argstr.split() + + origin = Origin(self, source, args) + self.dispatch(origin, tuple([text] + args)) + + if args[0] == 'PING': + self.proto.pong(text) + + def dispatch(self, origin, args): + pass + + def msg(self, recipient, text): + self.sending.acquire() + + # Cf. http://swhack.com/logs/2006-03-01#T19-43-25 + if isinstance(text, str): + try: + text = text.encode('utf-8') + except UnicodeEncodeError as e: + text = e.__class__ + ': ' + str(e) + if isinstance(recipient, str): + try: + recipient = recipient.encode('utf-8') + except UnicodeEncodeError as e: + return + + # No messages within the last 3 seconds? Go ahead! + # Otherwise, wait so it's been at least 0.8 seconds + penalty + if self.stack: + elapsed = time.time() - self.stack[-1][0] + if elapsed < 3: + penalty = float(max(0, len(text) - 50)) / 70 + wait = 0.8 + penalty + if elapsed < wait: + time.sleep(wait - elapsed) + + # Loop detection + messages = [m[1] for m in self.stack[-8:]] + if messages.count(text) >= 5: + text = '...' + if messages.count('...') >= 3: + self.sending.release() + return + + self.proto.privmsg(recipient, text) + self.stack.append((time.time(), text)) + self.stack = self.stack[-10:] + + self.sending.release() + + def action(self, recipient, text): + text = "\x01ACTION {0}\x01".format(text) + return self.msg(recipient, text) + + def error(self, origin): + try: + trace = traceback.format_exc() + print(trace) + lines = list(reversed(trace.splitlines())) + + report = [lines[0].strip()] + for line in lines: + line = line.strip() + if line.startswith('File "/'): + report.append(line[0].lower() + line[1:]) + break + else: + report.append('source unknown') + + self.msg(origin.sender, report[0] + ' (' + report[1] + ')') + except: + self.msg(origin.sender, "Got an error.") + + +class TestBot(Bot): + def f_ping(self, origin, match, args): + delay = match.group(1) + if delay is not None: + import time + time.sleep(int(delay)) + self.msg(origin.sender, 'pong (%s)' % delay) + else: + self.msg(origin.sender, 'pong') + f_ping.rule = r'^\.ping(?:[ \t]+(\d+))?$' + + +def main(): + bot = TestBot('testbot007', 'testbot007', ['#wadsworth']) + bot.run('irc.freenode.net') + print(__doc__) + + +if __name__ == "__main__": + main() diff --git a/metar.py b/metar.py new file mode 100644 index 000000000..0bbcd128d --- /dev/null +++ b/metar.py @@ -0,0 +1,306 @@ +# metar.py +# Copyright (c) 2013, mutantmonkey +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +# AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +# OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +# PERFORMANCE OF THIS SOFTWARE. + +import datetime + +INTENSITY = { + "-": "light", + "+": "heavy", + "VC": "in the vicinity:", +} + +DESCRIPTOR = { + "MI": "shallow", + "PR": "partial", + "BC": "patches", + "DR": "low drifting", + "BL": "blowing", + "SH": "showers", + "TS": "thunderstorm", + "FZ": "freezing", +} + +PRECIPITATION = { + "DZ": "drizzle", + "RA": "rain", + "SN": "snow", + "SG": "snow grains", + "IC": "ice crystals", + "PL": "ice pellets", + "GR": "hail", + "GS": "small hail", + "UP": "unknown precipitation", +} + +OBSCURATION = { + "BR": "mist", + "FG": "fog", + "VA": "volcanic ash", + "DU": "widespread dust", + "SA": "sand", + "HZ": "haze", + "PY": "spray", +} + +CLOUD_COVER = { + "SKC": "clear", + "CLR": "clear", + "NSC": "clear", + "FEW": "a few clouds", + "SCT": "scattered clouds", + "BKN": "broken clouds", + "OVC": "overcast", + "VV": "indefinite ceiling", +} + +OTHER = { + "PO": "whirls", + "SQ": "squals", + "FC": "tornado", + "SS": "sandstorm", + "DS": "duststorm", +} + +import re + + +class Weather(object): + cover = None + height = None + wind_speed = None + wind_direction = None + intensity = None + descriptor = None + precipitation = None + obscuration = None + other = None + conditions = None + + def describe_wind(self): + if self.wind_speed is not None: + if self.wind_speed < 1: + return "calm" + elif self.wind_speed < 4: + return "light air" + elif self.wind_speed < 7: + return "light breeze" + elif self.wind_speed < 11: + return "gentle breeze" + elif self.wind_speed < 16: + return "moderate breeze" + elif self.wind_speed < 22: + return "fresh breeze" + elif self.wind_speed < 28: + return "strong breeze" + elif self.wind_speed < 34: + return "near gale" + elif self.wind_speed < 41: + return "gale" + elif self.wind_speed < 56: + return "storm" + elif self.wind_speed < 64: + return "violent storm" + else: + return "hurricane" + else: + return 'unknown' + + def windsock(self): + if self.wind_direction is not None: + if (self.wind_speed <= 22.5) or (self.wind_speed > 337.5): + return '\u2191' + elif (self.wind_speed > 22.5) and (self.wind_speed <= 67.5): + return '\u2197' + elif (self.wind_speed > 67.5) and (self.wind_speed <= 112.5): + return '\u2192' + elif (self.wind_speed > 112.5) and (self.wind_speed <= 157.5): + return '\u2198' + elif (self.wind_speed > 157.5) and (self.wind_speed <= 202.5): + return '\u2193' + elif (self.wind_speed > 202.5) and (self.wind_speed <= 247.5): + return '\u2199' + elif (self.wind_speed > 247.5) and (self.wind_speed <= 292.5): + return '\u2190' + elif (self.wind_speed > 292.5) and (self.wind_speed <= 337.5): + return '\u2196' + else: + return '?' + + def __repr__(self): + chunks = [] + if self.cover: + chunks.append(self.cover) + + chunks.append('{0}°C'.format(self.temperature)) + + if self.pressure: + chunks.append('{0} hPa'.format(self.pressure)) + + if self.conditions: + chunks.append(self.conditions) + + wind = self.wind_speed if self.wind_speed is not None else '?' + chunks.append('{note} {speed} m/s ({windsock})'.format( + note=self.describe_wind(), + speed=wind, + windsock=self.windsock())) + + ret = ', '.join(chunks) + ' - {station} {time}' + return ret.format(station=self.station, + time=self.time.strftime("%H:%MZ")) + + +def build_regex(key, classifier): + ret = "|".join([re.escape(x) for x in classifier.keys()]) + return r"(?P<{key}>{regex})".format(key=re.escape(key), regex=ret) + + +def weather_regex(): + ret = r'\s' + ret += build_regex('intensity', INTENSITY) + r'?' + ret += build_regex('descriptor', DESCRIPTOR) + r'?' + ret += build_regex('precipitation', PRECIPITATION) + r'?' + ret += build_regex('obscuration', OBSCURATION) + r'?' + ret += build_regex('other', OTHER) + r'?' + ret += r'\s' + return re.compile(ret) + + +def parse_temp(t): + if t[0] == 'M': + return -int(t[1:]) + return int(t) + + +def parse(data): + w = Weather() + + data = data.splitlines() + metar = data[1].split() + + w.metar = data[1] + w.station = metar[0] + metar = metar[1:] + + # time + time_re = re.compile(r"\d{2}(?P\d{2})(?P\d{2})Z") + m = time_re.search(w.metar) + if m: + w.time = datetime.time(hour=int(m.group('hour')), + minute=int(m.group('min'))) + + # mode + #if metar[0] == "AUTO": + # metar = metar[1:] + + # wind speed + wind_re = re.compile(r"(?P\d{3})(?P\d+)(G(?P\d+))?(?PKT|MPS)") + m = wind_re.search(w.metar) + if m: + w.wind_direction = int(m.group('direction')) + + if m.group('unit') == "KT": + # convert knots to m/s + w.wind_speed = round(int(m.group('speed')) * 1852 / 3600) + if m.group('gust'): + w.wind_gust = round(int(m.group('speed')) * 1852 / 3600) + else: + w.wind_gust = None + else: + w.wind_speed = int(m.group('speed')) + if m.group('gust'): + w.wind_gust = int(m.group('gust')) + else: + w.wind_gust = None + metar = metar[1:] + + # visibility + # 0800N? + visibility_re = re.compile(r"(?P(?P\d+)SM|(?P\d{4})\s|CAVOK)") + m = visibility_re.search(w.metar) + if m: + if m.group('dist'): + w.visibility = m.group('dist') + elif m.group('disti'): + w.visibility = m.group('disti') + elif m.group('vis') == 'CAVOK': + w.cover = "clear" + w.visibility = m.group('vis') + else: + w.visibility = None + + # runway visibility range + + # conditions + matches = weather_regex().finditer(w.metar) + for m in matches: + if not m: + continue + + weather = [] + if m.group('intensity'): + w.intensity = INTENSITY[m.group('intensity')] + weather.append(w.intensity) + if m.group('descriptor'): + w.descriptor = DESCRIPTOR[m.group('descriptor')] + weather.append(w.descriptor) + if m.group('precipitation'): + w.precipitation = PRECIPITATION[m.group('precipitation')] + weather.append(w.precipitation) + if m.group('obscuration'): + w.obscuration = OBSCURATION[m.group('obscuration')] + weather.append(w.obscuration) + if m.group('other'): + w.other = OTHER[m.group('other')] + weather.append(w.other) + if len(weather) > 0: + w.conditions = " ".join(weather) + + # cloud cover + cover_re = re.compile(build_regex('cover', CLOUD_COVER) +\ + r"(?P\d*)") + matches = cover_re.finditer(w.metar) + for m in matches: + w.cover = CLOUD_COVER[m.group('cover')] + w.height = m.group('height') + + # temperature + temp_re = re.compile(r"(?P[M\d]+)\/(?P[M\d]+)") + m = temp_re.search(w.metar) + if m: + w.temperature = parse_temp(m.group('temp')) + w.dewpoint = parse_temp(m.group('dewpoint')) + else: + w.temperature = None + + # pressure + pressure_re = re.compile(r"([QA])(\d+)") + m = pressure_re.search(w.metar) + if m and m.group(1) == 'A': + # convert inHg to hPa + w.pressure = round(float(m.group(2)) * 0.3386389) + elif m: + w.pressure = int(m.group(2)) + else: + w.pressure = None + + return w + + +if __name__ == "__main__": + import glob + for station in glob.glob('test/metar/*.TXT'): + with open(station) as f: + print(parse(f.read())) diff --git a/modules/8ball.py b/modules/8ball.py new file mode 100644 index 000000000..bb9e1a575 --- /dev/null +++ b/modules/8ball.py @@ -0,0 +1,53 @@ +#!/usr/bin/python3 +""" +8ball.py - magic 8-ball +author: mutantmonkey +""" + +import random + +def eightball(phenny, input): + """.8ball - Magic 8-ball.""" + + strong_yes = [ + '45 seconds full throttle', + 'It is certain', + 'It is decidedly so', + 'Without a doubt', + 'Yes--definitely', + 'You may rely on it', + ] + tentative_yes = [ + 'As I see it, yes', + 'Most likely', + 'Outlook good', + 'Signs point to yes', + 'Yes', + ] + negative = [ + 'Your request is not bro enough', + 'Reply hazy, try again', + 'Ask again later', + 'Better not tell you now', + 'Cannot predict now', + 'Concentrate and ask again', + ] + noncommital = [ + 'I am sorry, too high to respond', + "Don't count on it", + 'My reply is no', + 'My sources say no', + 'Outlook not so good', + 'Very doubtful' + ] + + # black magic + quotes = strong_yes + tentative_yes + negative + noncommital + quote = random.choice(quotes) + phenny.reply(quote) +eightball.commands = ['8ball'] +eightball.name = '8ball' +eightball.example = '.8ball is pie amazing?' + +if __name__ == '__main__': + print(__doc__.strip()) diff --git a/modules/__init__.py b/modules/__init__.py old mode 100755 new mode 100644 diff --git a/modules/admin.py b/modules/admin.py old mode 100755 new mode 100644 index 249f117ea..aeac5c32c --- a/modules/admin.py +++ b/modules/admin.py @@ -8,56 +8,62 @@ """ def join(phenny, input): - """Join the specified channel. This is an admin-only command.""" - # Can only be done in privmsg by an admin - if input.sender.startswith('#'): return - if input.admin: - channel, key = input.group(1), input.group(2) - if not key: - phenny.write(['JOIN'], channel) - else: phenny.write(['JOIN', channel, key]) + """Join the specified channel. This is an admin-only command.""" + # Can only be done in privmsg by an admin + if input.sender.startswith('#'): return + if input.admin: + channel, key = input.group(1), input.group(2) + phenny.proto.join(channel, key) join.rule = r'\.join (#\S+)(?: *(\S+))?' join.priority = 'low' join.example = '.join #example or .join #example key' +def autojoin(phenny, input): + """Join the specified channel when invited by an admin.""" + if input.admin: + channel = input.group(1) + phenny.proto.join(channel) +autojoin.event = 'INVITE' +autojoin.rule = r'(.*)' + def part(phenny, input): - """Part the specified channel. This is an admin-only command.""" - # Can only be done in privmsg by an admin - if input.sender.startswith('#'): return - if input.admin: - phenny.write(['PART'], input.group(2)) -part.commands = ['part'] + """Part the specified channel. This is an admin-only command.""" + # Can only be done in privmsg by an admin + if input.sender.startswith('#'): return + if input.admin: + phenny.proto.part(input.group(2)) +part.rule = (['part'], r'(#\S+)') part.priority = 'low' part.example = '.part #example' def quit(phenny, input): - """Quit from the server. This is an owner-only command.""" - # Can only be done in privmsg by the owner - if input.sender.startswith('#'): return - if input.owner: - phenny.write(['QUIT']) - __import__('os')._exit(0) + """Quit from the server. This is an owner-only command.""" + # Can only be done in privmsg by the owner + if input.sender.startswith('#'): return + if input.owner: + phenny.proto.quit() + __import__('os')._exit(0) quit.commands = ['quit'] quit.priority = 'low' def msg(phenny, input): - # Can only be done in privmsg by an admin - if input.sender.startswith('#'): return - a, b = input.group(2), input.group(3) - if (not a) or (not b): return - if input.admin: - phenny.msg(a, b) + # Can only be done in privmsg by an admin + if input.sender.startswith('#'): return + a, b = input.group(2), input.group(3) + if (not a) or (not b): return + if input.admin: + phenny.msg(a, b) msg.rule = (['msg'], r'(#?\S+) (.+)') msg.priority = 'low' def me(phenny, input): - # Can only be done in privmsg by an admin - if input.sender.startswith('#'): return - if input.admin: - msg = '\x01ACTION %s\x01' % input.group(3) - phenny.msg(input.group(2), msg) + # Can only be done in privmsg by an admin + if input.sender.startswith('#'): return + if input.admin: + msg = '\x01ACTION %s\x01' % input.group(3) + phenny.msg(input.group(2), msg) me.rule = (['me'], r'(#?\S+) (.*)') me.priority = 'low' if __name__ == '__main__': - print __doc__.strip() + print(__doc__.strip()) diff --git a/modules/archwiki.py b/modules/archwiki.py new file mode 100644 index 000000000..909db21a6 --- /dev/null +++ b/modules/archwiki.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python +""" +archwiki.py - Phenny ArchWiki Module +Copyright 2008-9, Sean B. Palmer, inamidst.com +Licensed under the Eiffel Forum License 2. + +http://inamidst.com/phenny/ + +modified from Wikipedia module +author: mutantmonkey +""" + +import wiki + +endpoints = { + 'api': 'https://wiki.archlinux.org/api.php?action=query&list=search&srsearch={0}&limit=1&format=json', + 'url': 'https://wiki.archlinux.org/index.php/{0}', + 'search': 'https://wiki.archlinux.org/index.php/Special:Search?search={0}&fulltext=Search', +} + +def awik(phenny, input): + """.awik - Look up something on the ArchWiki.""" + + origterm = input.group(1) + if not origterm: + return phenny.say('Perhaps you meant ".awik dwm"?') + + term, section = wiki.parse_term(origterm) + + w = wiki.Wiki(endpoints) + match = w.search(term) + + if not match: + phenny.say('Can\'t find anything in the ArchWiki for "{0}".'.format(term)) + return + + snippet, url = wiki.extract_snippet(match, section) + + phenny.say('"{0}" - {1}'.format(snippet, url)) + +awik.commands = ['awik'] +awik.priority = 'high' + +if __name__ == '__main__': + print(__doc__.strip()) diff --git a/modules/botfun.py b/modules/botfun.py new file mode 100644 index 000000000..8f6aa20c5 --- /dev/null +++ b/modules/botfun.py @@ -0,0 +1,31 @@ +#!/usr/bin/python3 +""" +botfun.py - activities that bots do +author: mutantmonkey +""" + +import random + +otherbot = "quone" + +def botfight(phenny, input): + """.botfight - Fight the other bot in the channel.""" + + messages = ["hits %s", "punches %s", "kicks %s", "hits %s with a rubber hose", "stabs %s with a clean kitchen knife"] + response = random.choice(messages) + + phenny.do(response % otherbot) +botfight.commands = ['botfight'] +botfight.priority = 'low' +botfight.example = '.botfight' + +def bothug(phenny, input): + """.bothug - Hug the other bot in the channel.""" + + phenny.do("hugs %s" % otherbot) +bothug.commands = ['bothug'] +bothug.priority = 'low' +bothug.example = '.bothug' + +if __name__ == '__main__': + print(__doc__.strip()) diff --git a/modules/botsnack.py b/modules/botsnack.py new file mode 100644 index 000000000..485eabeb3 --- /dev/null +++ b/modules/botsnack.py @@ -0,0 +1,110 @@ +#!/usr/bin/python3 +""" +botsnack.py - .botsnack module (aka simulated hunger 1.0) +author: mutantmonkey +author: Casey Link + +This module simulates bot hunger and provides a mechanism +for users to feed the bot. + +To prevent abuse when the bot gets very, very full, it explodes +and enters a random cooldown period for 3-10 minutes. When in this +cooldown period all calls to botsnack are ignored. +""" + +import random, math, time + +# the rate that affects how much eating a snack nourishes the bot +# smaller number = less nourishment = more snacks can be eaten (before fullness) +# larger number = more nourishment = less snacks can be eaten +r_eat = 0.05 + +# the rate that affects how fast the bot becomes hungry over time +# smaller number = the bot gets hungry slower +# larger number = the bot gets hungry faster +r_hunger = 0.005 + +def increase_hunger(current_hunger, x): + # more hungry === value closer to 0 + return current_hunger * math.exp(-r_hunger * x) + +def decrease_hunger(current_hunger, food_value): + # less hungry === closer to 100 + if current_hunger > 50: # exponential growth + return min(100, current_hunger * math.exp(r_eat*food_value)) + else: # linear increase + return current_hunger + food_value + +def botsnack(phenny, input): + """.botsnack - Feed me a bot snack.""" + + now = time.time() + + # 0. Handle cooldown. + # Check if the cooldown period has elapsed, if not, then + # ignore this invocation. Else reset to the default state + if botsnack.coolingdown: + if now - botsnack.coolingstarted > botsnack.coolingperiod: + print("cooling down over, reseting") + botsnack.coolingdown = False + botsnack.hunger = 50.0 + botsnack.last_tick = now + else: + print("cooling down! %s < %s" %(now - botsnack.coolingstarted, botsnack.coolingperiod)) + return # ignore! + + # 1. Time has has passed, so the bot has gotten + # hungry. Lets increase his/her hunger proportionate + # to the amount of time that has passeed. + delta = now - botsnack.last_tick + old_hunger = botsnack.hunger + + botsnack.hunger = increase_hunger(old_hunger, delta) + + print("hunger was %s, increased to %s" %(old_hunger, botsnack.hunger)) + + botsnack.last_tick = now + + # 2. Eat some food. Send resposne + + old_hunger = botsnack.hunger + botsnack.hunger = decrease_hunger(old_hunger, random.uniform(1,5)) + print("hunger was %s, decreased to %s" %(old_hunger, botsnack.hunger)) + + if botsnack.hunger > 95: # special case to prevent abuse + phenny.say("Too much food!") + phenny.do("explodes") + botsnack.coolingperiod = random.uniform(3,10)*60 + botsnack.coolingstarted = now + botsnack.coolingdown = True + return + + if botsnack.hunger > 90: + messages = ["I don't think this will fit...", "Ugh, no more please", "Seriously, I can't eat anymore!", "/me shudders but downs the snack anyways"] + elif botsnack.hunger > 70: + messages = ["Thanks, but that's enough", "I suppose I could have one more", "If you insist"] + elif botsnack.hunger > 50: + messages = ["Om nom nom", "Delicious, thanks!", "Yummy!", "Wow! That's delicious"] + elif botsnack.hunger > 30: + messages = ["That really hit the spot!", "/me smacks lips", "Mmmmm!"] + elif botsnack.hunger > 10: + messages = ["Awww yea, that was tasty", "/me munches rudely", "Do you have any more?"] + elif botsnack.hunger > 1: + messages = ["/me noms furiously", "I really needed that!", "I'll take another!"] + else: + messages = ["I'M STARVING. GIVE ME MORE!", "/me gnaws ravenously on the snack with a starved look"] + + msg = random.choice(messages) + if msg.startswith("/me "): + phenny.do(msg.partition("/me ")[2]) + else: + phenny.say(msg) + +botsnack.commands = ['botsnack'] +botsnack.priority = 'low' +botsnack.hunger = 50.0 +botsnack.last_tick = time.time() +botsnack.coolingdown = False + +if __name__ == '__main__': + print(__doc__.strip()) diff --git a/modules/calc.py b/modules/calc.py old mode 100755 new mode 100644 index 9a5b187a3..4a9bdc475 --- a/modules/calc.py +++ b/modules/calc.py @@ -10,106 +10,35 @@ import re import web - -r_result = re.compile(r'(?i)(.*?)') -r_tag = re.compile(r'<\S+.*?>') - -subs = [ - (' in ', ' -> '), - (' over ', ' / '), - (u'£', 'GBP '), - (u'€', 'EUR '), - ('\$', 'USD '), - (r'\bKB\b', 'kilobytes'), - (r'\bMB\b', 'megabytes'), - (r'\bGB\b', 'kilobytes'), - ('kbps', '(kilobits / second)'), - ('mbps', '(megabits / second)') -] - -def calc(phenny, input): - """Use the Frink online calculator.""" - q = input.group(2) - if not q: - return phenny.say('0?') - - query = q[:] - for a, b in subs: - query = re.sub(a, b, query) - query = query.rstrip(' \t') - - precision = 5 - if query[-3:] in ('GBP', 'USD', 'EUR', 'NOK'): - precision = 2 - query = web.urllib.quote(query.encode('utf-8')) - - uri = 'http://futureboy.us/fsp/frink.fsp?fromVal=' - bytes = web.get(uri + query) - m = r_result.search(bytes) - if m: - result = m.group(1) - result = r_tag.sub('', result) # strip span.warning tags - result = result.replace('>', '>') - result = result.replace('(undefined symbol)', '(?) ') - - if '.' in result: - try: result = str(round(float(result), precision)) - except ValueError: pass - - if not result.strip(): - result = '?' - elif ' in ' in q: - result += ' ' + q.split(' in ', 1)[1] - - phenny.say(q + ' = ' + result[:350]) - else: phenny.reply("Sorry, can't calculate that.") - phenny.say('Note that .calc is deprecated, consider using .c') -calc.commands = ['calc'] -calc.example = '.calc 5 + 3' - -def c(phenny, input): - """Google calculator.""" - if not input.group(2): - return phenny.reply("Nothing to calculate.") - q = input.group(2).encode('utf-8') - q = q.replace('\xcf\x95', 'phi') # utf-8 U+03D5 - q = q.replace('\xcf\x80', 'pi') # utf-8 U+03C0 - uri = 'http://www.google.com/ig/calculator?q=' - bytes = web.get(uri + web.urllib.quote(q)) - parts = bytes.split('",') - answer = [p for p in parts if p.startswith('rhs: "')][0][6:] - if answer: - answer = answer.decode('unicode-escape') - answer = ''.join(chr(ord(c)) for c in answer) - answer = answer.decode('utf-8') - answer = answer.replace(u'\xc2\xa0', ',') - answer = answer.replace('', '^(') - answer = answer.replace('', ')') - answer = web.decode(answer) - phenny.say(answer) - else: phenny.say('Sorry, no result.') +from modules.search import newton_api + +operations = {'simplify', 'factor', 'derive', 'integrate', 'zeroes', 'tangent', + 'area', 'cos', 'sin', 'tan', 'arccos', 'arcsin', 'arctan', 'abs', 'log'} + +def c(phenny, input): + """Newton calculator.""" + if not input.group(2): + return phenny.reply("Nothing to calculate.") + q = input.group(2) + q = q.split(' ', 1) + + if len(q) > 1 and q[0] in operations: + operation = q[0] + expression = q[1] + elif len(q) > 0: + operation = 'simplify' + expression = q[0] + + result = newton_api(operation, expression) + + if result: + phenny.say(result) + else: + phenny.reply("Sorry, no result.") c.commands = ['c'] c.example = '.c 5 + 3' +c.example = '.c integrate 1/3 x^3 + x^2 + C' -def py(phenny, input): - query = input.group(2).encode('utf-8') - uri = 'http://tumbolia.appspot.com/py/' - answer = web.get(uri + web.urllib.quote(query)) - if answer: - phenny.say(answer) - else: phenny.reply('Sorry, no result.') -py.commands = ['py'] - -def wa(phenny, input): - if not input.group(2): - return phenny.reply("No search term.") - query = input.group(2).encode('utf-8') - uri = 'http://tumbolia.appspot.com/wa/' - answer = web.get(uri + web.urllib.quote(query.replace('+', '%2B'))) - if answer: - phenny.say(answer) - else: phenny.reply('Sorry, no result.') -wa.commands = ['wa'] -if __name__ == '__main__': - print __doc__.strip() +if __name__ == '__main__': + print(__doc__.strip()) diff --git a/modules/chillmeter.py b/modules/chillmeter.py new file mode 100644 index 000000000..9732b33e6 --- /dev/null +++ b/modules/chillmeter.py @@ -0,0 +1,146 @@ +#!/usr/bin/python3 +""" +chillmeter.py - .chill measures chill level of the channel +author: Casey Link +author: mutantmonkey + +so chill bro. +""" +import random, time + + +# chill decay rate per minute +chill_decay_rate = 5 + +chill_words = [ + # words that make the place chill + ("chill", 1), + ("bro", 1), + ("fist bump", 2), + ("fistbump", 2), + ("natty", 1), + ("natties", 2), + ("head nod", 1), + ("she said", 1), + ("keystone", 1), + ("sandwich", 1), + ("lax", 2), + ("lacrosse", 2), + ("pinny", 2), + ("bowl", 1), + ("slampiece", 2), + ("smirnoff", 1), + ("ices", 1), + ("iced", 1), + ("longboard", 2), + ("boning", 1), + ("orange", 1), + ("maroon", 1), + ("kicks", 1), + ("dome", 1), + ("69", 1), + ("bang", 1), + ("COD", 2), + ("blazed", 1), + + # words that unchill the place + ("dude", -1), + ("suck", -2), + ("desi", -1), + ("lame", -2), + ("imageshack", -1), + ("microsoft", -1), + ("btreecat", -1), + ("homework", -1), + ("project", -2), + ("test", -2), + ("exam", -2), + ("4chan", -1), + ("digg", -1), + ("work", -1), + ("unchill", -2), +] + +# all things chill +chill_things = [ + ("natty", "natties"), + ("smirnoff ice", "smirnoffs"), + ("bong hit", "bong hits"), + ("case of keystone", "cases of keystone"), + ("fist bump", "fist bumps"), + ("head nod", "head nods"), + ("bro", "bros"), + ("bowl", "bowls") +] + +# keeps a finger on the pulse of the chillness +def measure(phenny, input): + chill = measure.channels.get(input.sender, 0) + now = time.time() + if now - measure.last_tick > 60: + measure.last_tick = now + if chill > 0: + chill -= chill_decay_rate + chill = max(0, chill) + elif chill < 0: + chill += chill_decay_rate + chill = min(0, chill) + measure.channels[input.sender] = chill + + if ".chill" in input: + return # dont self count + + for w in chill_words: + if w[0] in input.lower(): + chill += w[1] + + measure.channels[input.sender] = chill + + +measure.rule = r'.*' +measure.priority = 'low' +measure.last_tick = time.time() +measure.channels = {} + +def chill(phenny, input): + """.chill - Measure the current channel chillness level.""" + level = measure.channels.get(input.sender, 0) + + n = random.randint(1,2) + items = [] + used = set() + for i in range(n): + if level == 0: + amount = random.randint(5, 10) + elif level < 0: + amount = random.randint(10, -level * 2 + 10) + else: + amount = random.randint(1, level) + item = random.choice(chill_things) + + while item in used: + item = random.choice(chill_things) + used.add(item) + + if amount == 1: + item = item[0] # singular + else: + item = item[1] # plural + items.append("%s %s" % (amount, item)) + + item_str = ", ".join(items) + #print level, item_str + + if level <= 0: + message = "WARNING: CHILL LEVEL IS DANGEROUSLY LOW. RECOMMEND %s" % (item_str.upper()) + else: + message = "chill level is currently: %s" % (item_str) + + phenny.say(message) + + +chill.commands = ['chill'] +chill.priority = 'low' + +if __name__ == '__main__': + print(__doc__.strip()) diff --git a/modules/choose.py b/modules/choose.py new file mode 100644 index 000000000..6a00a244b --- /dev/null +++ b/modules/choose.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +choose.py - sometimes you just can't decide, a phenny module +""" + +import re, random + +def choose(phenny, input): + """.choose - for when you just can't decide""" + origterm = input.groups()[1] + if not origterm: + return phenny.say(".choose - for when you just can't decide") + c = re.findall(r'([^,]+)', origterm) + if len(c) == 1: + c = re.findall(r'(\S+)', origterm) + if len(c) == 1: + return phenny.reply("%s" % (c[0].strip())) + fate = random.choice(c).strip() + return phenny.reply("%s" % (fate)) +choose.rule = (['choose'], r'(.*)') + +if __name__ == '__main__': + print(__doc__.strip()) diff --git a/modules/clock.py b/modules/clock.py old mode 100755 new mode 100644 index 91f2d5b57..d7597637f --- a/modules/clock.py +++ b/modules/clock.py @@ -7,25 +7,32 @@ http://inamidst.com/phenny/ """ -import re, math, time, urllib, locale, socket, struct, datetime +import re +import math +import time +import locale +import socket +import struct +import datetime +import web from decimal import Decimal as dec from tools import deprecated TimeZones = {'KST': 9, 'CADT': 10.5, 'EETDST': 3, 'MESZ': 2, 'WADT': 9, - 'EET': 2, 'MST': -7, 'WAST': 8, 'IST': 5.5, 'B': 2, - 'MSK': 3, 'X': -11, 'MSD': 4, 'CETDST': 2, 'AST': -4, - 'HKT': 8, 'JST': 9, 'CAST': 9.5, 'CET': 1, 'CEST': 2, - 'EEST': 3, 'EAST': 10, 'METDST': 2, 'MDT': -6, 'A': 1, - 'UTC': 0, 'ADT': -3, 'EST': -5, 'E': 5, 'D': 4, 'G': 7, - 'F': 6, 'I': 9, 'H': 8, 'K': 10, 'PDT': -7, 'M': 12, - 'L': 11, 'O': -2, 'MEST': 2, 'Q': -4, 'P': -3, 'S': -6, - 'R': -5, 'U': -8, 'T': -7, 'W': -10, 'WET': 0, 'Y': -12, - 'CST': -6, 'EADT': 11, 'Z': 0, 'GMT': 0, 'WETDST': 1, - 'C': 3, 'WEST': 1, 'CDT': -5, 'MET': 1, 'N': -1, 'V': -9, - 'EDT': -4, 'UT': 0, 'PST': -8, 'MEZ': 1, 'BST': 1, - 'ACS': 9.5, 'ATL': -4, 'ALA': -9, 'HAW': -10, 'AKDT': -8, - 'AKST': -9, - 'BDST': 2} + 'EET': 2, 'MST': -7, 'WAST': 8, 'IST': 5.5, 'B': 2, + 'MSK': 3, 'X': -11, 'MSD': 4, 'CETDST': 2, 'AST': -4, + 'HKT': 8, 'JST': 9, 'CAST': 9.5, 'CET': 1, 'CEST': 2, + 'EEST': 3, 'EAST': 10, 'METDST': 2, 'MDT': -6, 'A': 1, + 'UTC': 0, 'ADT': -3, 'EST': -5, 'E': 5, 'D': 4, 'G': 7, + 'F': 6, 'I': 9, 'H': 8, 'K': 10, 'PDT': -7, 'M': 12, + 'L': 11, 'O': -2, 'MEST': 2, 'Q': -4, 'P': -3, 'S': -6, + 'R': -5, 'U': -8, 'T': -7, 'W': -10, 'WET': 0, 'Y': -12, + 'CST': -6, 'EADT': 11, 'Z': 0, 'GMT': 0, 'WETDST': 1, + 'C': 3, 'WEST': 1, 'CDT': -5, 'MET': 1, 'N': -1, 'V': -9, + 'EDT': -4, 'UT': 0, 'PST': -8, 'MEZ': 1, 'BST': 1, + 'ACS': 9.5, 'ATL': -4, 'ALA': -9, 'HAW': -10, 'AKDT': -8, + 'AKST': -9, + 'BDST': 2, 'KGT': 6} TZ1 = { 'NDT': -2.5, @@ -183,8 +190,8 @@ } TZ3 = { - 'AEST': 10, - 'AEDT': 11 + 'AEST': 10, + 'AEDT': 11 } # TimeZones.update(TZ2) # do these have to be negated? @@ -193,110 +200,109 @@ r_local = re.compile(r'\([a-z]+_[A-Z]+\)') -@deprecated -def f_time(self, origin, match, args): - """Returns the current time.""" - tz = match.group(2) or 'GMT' +def f_time(phenny, input): + """Returns the current time.""" + tz = input.group(2) or 'GMT' - # Personal time zones, because they're rad - if hasattr(self.config, 'timezones'): - People = self.config.timezones - else: People = {} + # Personal time zones, because they're rad + if hasattr(phenny.config, 'timezones'): + People = phenny.config.timezones + else: People = {} - if People.has_key(tz): - tz = People[tz] - elif (not match.group(2)) and People.has_key(origin.nick): - tz = People[origin.nick] + if tz in People: + tz = People[tz] + elif (not input.group(2)) and input.nick in People: + tz = People[input.nick] - TZ = tz.upper() - if len(tz) > 30: return + TZ = tz.upper() + if len(tz) > 30: return - if (TZ == 'UTC') or (TZ == 'Z'): - msg = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()) - self.msg(origin.sender, msg) - elif r_local.match(tz): # thanks to Mark Shoulsdon (clsn) - locale.setlocale(locale.LC_TIME, (tz[1:-1], 'UTF-8')) - msg = time.strftime("%A, %d %B %Y %H:%M:%SZ", time.gmtime()) - self.msg(origin.sender, msg) - elif TimeZones.has_key(TZ): - offset = TimeZones[TZ] * 3600 - timenow = time.gmtime(time.time() + offset) - msg = time.strftime("%a, %d %b %Y %H:%M:%S " + str(TZ), timenow) - self.msg(origin.sender, msg) - elif tz and tz[0] in ('+', '-') and 4 <= len(tz) <= 6: - timenow = time.gmtime(time.time() + (int(tz[:3]) * 3600)) - msg = time.strftime("%a, %d %b %Y %H:%M:%S " + str(tz), timenow) - self.msg(origin.sender, msg) - else: - try: t = float(tz) - except ValueError: - import os, re, subprocess - r_tz = re.compile(r'^[A-Za-z]+(?:/[A-Za-z_]+)*$') - if r_tz.match(tz) and os.path.isfile('/usr/share/zoneinfo/' + tz): - cmd, PIPE = 'TZ=%s date' % tz, subprocess.PIPE - proc = subprocess.Popen(cmd, shell=True, stdout=PIPE) - self.msg(origin.sender, proc.communicate()[0]) - else: - error = "Sorry, I don't know about the '%s' timezone." % tz - self.msg(origin.sender, origin.nick + ': ' + error) - else: - timenow = time.gmtime(time.time() + (t * 3600)) - msg = time.strftime("%a, %d %b %Y %H:%M:%S " + str(tz), timenow) - self.msg(origin.sender, msg) -f_time.commands = ['t'] -f_time.name = 't' -f_time.example = '.t UTC' + if (TZ == 'UTC') or (TZ == 'Z'): + msg = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()) + phenny.reply(msg) + elif r_local.match(tz): # thanks to Mark Shoulsdon (clsn) + locale.setlocale(locale.LC_TIME, (tz[1:-1], 'UTF-8')) + msg = time.strftime("%A, %d %B %Y %H:%M:%SZ", time.gmtime()) + phenny.reply(msg) + elif TZ in TimeZones: + offset = TimeZones[TZ] * 3600 + timenow = time.gmtime(time.time() + offset) + msg = time.strftime("%a, %d %b %Y %H:%M:%S " + str(TZ), timenow) + phenny.reply(msg) + elif tz and tz[0] in ('+', '-') and 4 <= len(tz) <= 6: + timenow = time.gmtime(time.time() + (int(tz[:3]) * 3600)) + msg = time.strftime("%a, %d %b %Y %H:%M:%S " + str(tz), timenow) + phenny.reply(msg) + else: + try: t = float(tz) + except ValueError: + import os, re, subprocess + r_tz = re.compile(r'^[A-Za-z]+(?:/[A-Za-z_]+)*$') + if r_tz.match(tz) and os.path.isfile('/usr/share/zoneinfo/' + tz): + cmd, PIPE = 'TZ=%s date' % tz, subprocess.PIPE + proc = subprocess.Popen(cmd, shell=True, stdout=PIPE) + phenny.reply(proc.communicate()[0]) + else: + error = "Sorry, I don't know about the '%s' timezone." % tz + phenny.reply(error) + else: + timenow = time.gmtime(time.time() + (t * 3600)) + msg = time.strftime("%a, %d %b %Y %H:%M:%S " + str(tz), timenow) + phenny.reply(msg) +f_time.name = 'time' +f_time.commands = ['time'] +f_time.example = '.time UTC' def beats(phenny, input): - """Shows the internet time in Swatch beats.""" - beats = ((time.time() + 3600) % 86400) / 86.4 - beats = int(math.floor(beats)) - phenny.say('@%03i' % beats) + """Shows the internet time in Swatch beats.""" + beats = ((time.time() + 3600) % 86400) / 86.4 + beats = int(math.floor(beats)) + phenny.say('@%03i' % beats) beats.commands = ['beats'] beats.priority = 'low' def divide(input, by): - return (input / by), (input % by) + return (input // by), (input % by) def yi(phenny, input): - """Shows whether it is currently yi or not.""" - quadraels, remainder = divide(int(time.time()), 1753200) - raels = quadraels * 4 - extraraels, remainder = divide(remainder, 432000) - if extraraels == 4: - return phenny.say('Yes! PARTAI!') - else: phenny.say('Not yet...') + """Shows whether it is currently yi or not.""" + quadraels, remainder = divide(int(time.time()), 1753200) + raels = quadraels * 4 + extraraels, remainder = divide(remainder, 432000) + if extraraels == 4: + return phenny.say('Yes! PARTAI!') + elif extraraels == 3: + return phenny.say('Soon...') + else: phenny.say('Not yet...') yi.commands = ['yi'] yi.priority = 'low' def tock(phenny, input): - """Shows the time from the USNO's atomic clock.""" - u = urllib.urlopen('http://tycho.usno.navy.mil/cgi-bin/timer.pl') - info = u.info() - u.close() - phenny.say('"' + info['Date'] + '" - tycho.usno.navy.mil') + """Shows the time from the USNO's atomic clock.""" + info = web.head('http://tycho.usno.navy.mil/cgi-bin/timer.pl') + phenny.say('"' + info['Date'] + '" - tycho.usno.navy.mil') tock.commands = ['tock'] tock.priority = 'high' def npl(phenny, input): - """Shows the time from NPL's SNTP server.""" - # for server in ('ntp1.npl.co.uk', 'ntp2.npl.co.uk'): - client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - client.sendto('\x1b' + 47 * '\0', ('ntp1.npl.co.uk', 123)) - data, address = client.recvfrom(1024) - if data: - buf = struct.unpack('B' * 48, data) - d = dec('0.0') - for i in range(8): - d += dec(buf[32 + i]) * dec(str(math.pow(2, (3 - i) * 8))) - d -= dec(2208988800L) - a, b = str(d).split('.') - f = '%Y-%m-%d %H:%M:%S' - result = datetime.datetime.fromtimestamp(d).strftime(f) + '.' + b[:6] - phenny.say(result + ' - ntp1.npl.co.uk') - else: phenny.say('No data received, sorry') + """Shows the time from NPL's SNTP server.""" + # for server in ('ntp1.npl.co.uk', 'ntp2.npl.co.uk'): + client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + client.sendto(b'\x1b' + 47 * b'\0', ('ntp1.npl.co.uk', 123)) + data, address = client.recvfrom(1024) + if data: + buf = struct.unpack('B' * 48, data) + d = dec('0.0') + for i in range(8): + d += dec(buf[32 + i]) * dec(str(math.pow(2, (3 - i) * 8))) + d -= dec(2208988800) + a, b = str(d).split('.') + f = '%Y-%m-%d %H:%M:%S' + result = datetime.datetime.fromtimestamp(d).strftime(f) + '.' + b[:6] + phenny.say(result + ' - ntp1.npl.co.uk') + else: phenny.say('No data received, sorry') npl.commands = ['npl'] npl.priority = 'high' if __name__ == '__main__': - print __doc__.strip() + print(__doc__.strip()) diff --git a/modules/codepoints.py b/modules/codepoints.py old mode 100755 new mode 100644 index eb9c8bfa8..10e726c42 --- a/modules/codepoints.py +++ b/modules/codepoints.py @@ -11,124 +11,124 @@ from itertools import islice def about(u, cp=None, name=None): - if cp is None: - cp = ord(u) - if name is None: - try: name = unicodedata.name(u) - except ValueError: - return 'U+%04X (No name found)' % cp - - if not unicodedata.combining(u): - template = 'U+%04X %s (%s)' - else: template = 'U+%04X %s (\xe2\x97\x8c%s)' - return template % (cp, name, u.encode('utf-8')) + if cp is None: + cp = ord(u) + if name is None: + try: name = unicodedata.name(u) + except ValueError: + return 'U+%04X (No name found)' % cp + + if not unicodedata.combining(u): + template = 'U+%04X %s (%s)' + else: template = 'U+%04X %s (\xe2\x97\x8c%s)' + return template % (cp, name, u) def codepoint_simple(arg): - arg = arg.upper() - - r_label = re.compile('\\b' + arg.replace(' ', '.*\\b') + '\\b') - - results = [] - for cp in xrange(0xFFFF): - u = unichr(cp) - try: name = unicodedata.name(u) - except ValueError: continue - - if r_label.search(name): - results.append((len(name), u, cp, name)) - if not results: - r_label = re.compile('\\b' + arg.replace(' ', '.*\\b')) - for cp in xrange(0xFFFF): - u = unichr(cp) - try: name = unicodedata.name(u) - except ValueError: continue - - if r_label.search(name): + arg = arg.upper() + + r_label = re.compile('\\b' + arg.replace(' ', '.*\\b') + '\\b') + + results = [] + for cp in range(0xFFFF): + u = chr(cp) + try: name = unicodedata.name(u) + except ValueError: continue + + if r_label.search(name): results.append((len(name), u, cp, name)) + if not results: + r_label = re.compile('\\b' + arg.replace(' ', '.*\\b')) + for cp in range(0xFFFF): + u = chr(cp) + try: name = unicodedata.name(u) + except ValueError: continue + + if r_label.search(name): + results.append((len(name), u, cp, name)) - if not results: - return None + if not results: + return None - length, u, cp, name = sorted(results)[0] - return about(u, cp, name) + length, u, cp, name = sorted(results)[0] + return about(u, cp, name) def codepoint_extended(arg): - arg = arg.upper() - try: r_search = re.compile(arg) - except: raise ValueError('Broken regexp: %r' % arg) + arg = arg.upper() + try: r_search = re.compile(arg) + except: raise ValueError('Broken regexp: %r' % arg) - for cp in xrange(1, 0x10FFFF): - u = unichr(cp) - name = unicodedata.name(u, '-') + for cp in range(1, 0x10FFFF): + u = chr(cp) + name = unicodedata.name(u, '-') - if r_search.search(name): - yield about(u, cp, name) + if r_search.search(name): + yield about(u, cp, name) def u(phenny, input): - """Look up unicode information.""" - arg = input.bytes[3:] - # phenny.msg('#inamidst', '%r' % arg) - if not arg: - return phenny.reply('You gave me zero length input.') - elif not arg.strip(' '): - if len(arg) > 1: return phenny.reply('%s SPACEs (U+0020)' % len(arg)) - return phenny.reply('1 SPACE (U+0020)') - - # @@ space - if set(arg.upper()) - set( - 'ABCDEFGHIJKLMNOPQRSTUVWYXYZ0123456789- .?+*{}[]\\/^$'): - printable = False - elif len(arg) > 1: - printable = True - else: printable = False - - if printable: - extended = False - for c in '.?+*{}[]\\/^$': - if c in arg: - extended = True - break - - if len(arg) == 4: - try: u = unichr(int(arg, 16)) - except ValueError: pass - else: return phenny.say(about(u)) - - if extended: - # look up a codepoint with regexp - results = list(islice(codepoint_extended(arg), 4)) - for i, result in enumerate(results): - if (i < 2) or ((i == 2) and (len(results) < 4)): - phenny.say(result) - elif (i == 2) and (len(results) > 3): - phenny.say(result + ' [...]') - if not results: - phenny.reply('Sorry, no results') - else: - # look up a codepoint freely - result = codepoint_simple(arg) - if result is not None: - phenny.say(result) - else: phenny.reply("Sorry, no results for %r." % arg) - else: - text = arg.decode('utf-8') - # look up less than three podecoints - if len(text) <= 3: - for u in text: - phenny.say(about(u)) - # look up more than three podecoints - elif len(text) <= 10: - phenny.reply(' '.join('U+%04X' % ord(c) for c in text)) - else: phenny.reply('Sorry, your input is too long!') + """Look up unicode information.""" + arg = input.bytes[3:] + # phenny.msg('#inamidst', '%r' % arg) + if not arg: + return phenny.reply('You gave me zero length input.') + elif not arg.strip(' '): + if len(arg) > 1: return phenny.reply('%s SPACEs (U+0020)' % len(arg)) + return phenny.reply('1 SPACE (U+0020)') + + # @@ space + if set(arg.upper()) - set( + 'ABCDEFGHIJKLMNOPQRSTUVWYXYZ0123456789- .?+*{}[]\\/^$'): + printable = False + elif len(arg) > 1: + printable = True + else: printable = False + + if printable: + extended = False + for c in '.?+*{}[]\\/^$': + if c in arg: + extended = True + break + + if len(arg) == 4: + try: u = chr(int(arg, 16)) + except ValueError: pass + else: return phenny.say(about(u)) + + if extended: + # look up a codepoint with regexp + results = list(islice(codepoint_extended(arg), 4)) + for i, result in enumerate(results): + if (i < 2) or ((i == 2) and (len(results) < 4)): + phenny.say(result) + elif (i == 2) and (len(results) > 3): + phenny.say(result + ' [...]') + if not results: + phenny.reply('Sorry, no results') + else: + # look up a codepoint freely + result = codepoint_simple(arg) + if result is not None: + phenny.say(result) + else: phenny.reply("Sorry, no results for %r." % arg) + else: + text = arg + # look up less than three podecoints + if len(text) <= 3: + for u in text: + phenny.say(about(u)) + # look up more than three podecoints + elif len(text) <= 10: + phenny.reply(' '.join('U+%04X' % ord(c) for c in text)) + else: phenny.reply('Sorry, your input is too long!') u.commands = ['u'] u.example = '.u 203D' def bytes(phenny, input): - """Show the input as pretty printed bytes.""" - b = input.bytes - phenny.reply('%r' % b[b.find(' ') + 1:]) + """Show the input as pretty printed bytes.""" + b = input.bytes + phenny.reply('%r' % b[b.find(' ') + 1:]) bytes.commands = ['bytes'] bytes.example = '.bytes \xe3\x8b\xa1' if __name__ == '__main__': - print __doc__.strip() + print(__doc__.strip()) diff --git a/modules/commit.py b/modules/commit.py new file mode 100644 index 000000000..0321be023 --- /dev/null +++ b/modules/commit.py @@ -0,0 +1,23 @@ +#!/usr/bin/python3 +""" +commit.py - what the commit +author: mutantmonkey +""" + +import web +from tools import GrumbleError + + +def commit(phenny, input): + """.commit - Get a What the Commit commit message.""" + + try: + msg = web.get("http://whatthecommit.com/index.txt") + except: + raise GrumbleError("THE INTERNET IS FUCKING BROKEN. Please try again later.") + + phenny.reply(msg) +commit.commands = ['commit'] + +if __name__ == '__main__': + print(__doc__.strip()) diff --git a/modules/etymology.py b/modules/etymology.py deleted file mode 100755 index 37d13163a..000000000 --- a/modules/etymology.py +++ /dev/null @@ -1,114 +0,0 @@ -#!/usr/bin/env python -""" -etymology.py - Phenny Etymology Module -Copyright 2007-9, Sean B. Palmer, inamidst.com -Licensed under the Eiffel Forum License 2. - -http://inamidst.com/phenny/ -""" - -import re, urllib -import web -from tools import deprecated - -etysite = 'http://www.etymonline.com/index.php?' -etyuri = etysite + 'allowed_in_frame=0&term=%s' -etysearch = etysite + 'allowed_in_frame=0&search=%s' - -r_definition = re.compile(r'(?ims)]*>.*?') -r_tag = re.compile(r'<(?!!)[^>]+>') -r_whitespace = re.compile(r'[\t\r\n ]+') - -class Grab(urllib.URLopener): - def __init__(self, *args): - self.version = 'Mozilla/5.0 (Phenny)' - urllib.URLopener.__init__(self, *args) - def http_error_default(self, url, fp, errcode, errmsg, headers): - return urllib.addinfourl(fp, [headers, errcode], "http:" + url) - -abbrs = [ - 'cf', 'lit', 'etc', 'Ger', 'Du', 'Skt', 'Rus', 'Eng', 'Amer.Eng', 'Sp', - 'Fr', 'N', 'E', 'S', 'W', 'L', 'Gen', 'J.C', 'dial', 'Gk', - '19c', '18c', '17c', '16c', 'St', 'Capt', 'obs', 'Jan', 'Feb', 'Mar', - 'Apr', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec', 'c', 'tr', 'e', 'g' -] -t_sentence = r'^.*?(?') - s = s.replace('<', '<') - s = s.replace('&', '&') - return s - -def text(html): - html = r_tag.sub('', html) - html = r_whitespace.sub(' ', html) - return unescape(html).strip() - -def etymology(word): - # @@ sbp, would it be possible to have a flag for .ety to get 2nd/etc - # entries? - http://swhack.com/logs/2006-07-19#T15-05-29 - - if len(word) > 25: - raise ValueError("Word too long: %s[...]" % word[:10]) - word = {'axe': 'ax/axe'}.get(word, word) - - grab = urllib._urlopener - urllib._urlopener = Grab() - urllib._urlopener.addheader("Referer", "http://www.etymonline.com/") - bytes = web.get(etyuri % web.urllib.quote(word)) - urllib._urlopener = grab - definitions = r_definition.findall(bytes) - - if not definitions: - return None - - defn = text(definitions[0]) - m = r_sentence.match(defn) - if not m: - return None - sentence = m.group(0) - - try: - sentence = unicode(sentence, 'iso-8859-1') - sentence = sentence.encode('utf-8') - except: pass - sentence = web.decode(sentence) - - maxlength = 275 - if len(sentence) > maxlength: - sentence = sentence[:maxlength] - words = sentence[:-5].split(' ') - words.pop() - sentence = ' '.join(words) + ' [...]' - - sentence = '"' + sentence.replace('"', "'") + '"' - return sentence + ' - etymonline.com' - -@deprecated -def f_etymology(self, origin, match, args): - word = match.group(2) - - try: result = etymology(word.encode('iso-8859-1')) - except IOError: - msg = "Can't connect to etymonline.com (%s)" % (etyuri % word) - self.msg(origin.sender, msg) - return - except AttributeError: - result = None - - if result is not None: - self.msg(origin.sender, result) - else: - uri = etysearch % word - msg = 'Can\'t find the etymology for "%s". Try %s' % (word, uri) - self.msg(origin.sender, msg) -# @@ Cf. http://swhack.com/logs/2006-01-04#T01-50-22 -f_etymology.rule = (['ety'], r"(.+?)$") -f_etymology.thread = True -f_etymology.priority = 'high' - -if __name__=="__main__": - import sys - print etymology(sys.argv[1]) diff --git a/modules/fcc.py b/modules/fcc.py new file mode 100644 index 000000000..391a55c5e --- /dev/null +++ b/modules/fcc.py @@ -0,0 +1,35 @@ +#!/usr/bin/python3 +""" +fcc.py - fcc callsign lookup +author: mutantmonkey +""" + +from tools import GrumbleError +import web +import json + +def fcc(phenny, input): + """.fcc - Look up a callsign issued by the FCC.""" + + callsign = input.group(2) + if not callsign: + phenny.say(".fcc - Look up a callsign issued by the FCC.") + return + + try: + req = web.get("http://callook.info/{0}/json".format(web.quote(callsign))) + data = json.loads(req) + except: + raise GrumbleError("THE INTERNET IS FUCKING BROKEN. Please try again later.") + + if len(data) <= 0 or data['status'] == 'INVALID': + phenny.reply('No results found for {0}'.format(callsign)) + return + + response = "{0} - {1} - {2}".format(data['current']['callsign'], + data['name'], data['otherInfo']['ulsUrl']) + phenny.say(response) +fcc.rule = (['fcc'], r'(.*)') + +if __name__ == '__main__': + print(__doc__.strip()) diff --git a/modules/foodforus.py b/modules/foodforus.py new file mode 100644 index 000000000..2c2df9cdf --- /dev/null +++ b/modules/foodforus.py @@ -0,0 +1,98 @@ +#!/usr/bin/python3 +""" +foodforus.py - foodforus module +author: mutantmonkey +""" + +from tools import GrumbleError +import hashlib +import json +import web + +API_URL = 'https://foodfor.vtluug.org' + + +def _sign_vote(api_key, args): + data = "ffu1" + for k, v in sorted(args.items()): + if k == 'sig': + continue + data += '{0}{1}'.format(k, v) + data += api_key + h = hashlib.sha256() + h.update(data.encode('utf-8')) + return h.hexdigest() + + +def food(phenny, input): + """.food""" + key = input.group(2) or input.sender + try: + req = web.get(API_URL + '/food/' + web.quote(key.strip())) + data = json.loads(req) + except: + raise GrumbleError("Uh oh, I couldn't contact foodforus. HOW WILL WE "\ + "EAT NOW‽") + + restaurants = data['restaurants'][:4] + times = data['times'][:4] + + restr = ", ".join(["{0} ({1})".format(r[0], r[1]) for r in + restaurants]) + tistr = ", ".join(["{0} ({1})".format(t[0], t[1]) for t in times]) + + if len(restr) > 0 and len(tistr) > 0: + return phenny.say("{0} at {1}".format(restr, tistr)) + else: + return phenny.say("Sorry, people need to vote before we can food!") +food.rule = (['food'], r'(.*)') + + +def foodvote(phenny, input): + """.foodvote""" + if not input.group(2) or not input.group(3): + return phenny.reply("You need to specify a place and time, as in "\ + ".foodvote hokie haus 18:45") + + key = input.group(4) or input.sender + postdata = { + 'user': input.nick, + 'restaurant': input.group(2), + 'start': input.group(3), + 'key': key.strip(), + } + postdata['sig'] = _sign_vote(phenny.config.foodforus_api_key, postdata) + + try: + req = web.post(API_URL + '/vote', postdata) + data = json.loads(req) + except: + raise GrumbleError("Uh oh, I couldn't contact foodforus. HOW WILL WE "\ + "EAT NOW‽") + + if 'error' in data: + phenny.reply(data['error']) + else: + phenny.reply("Your vote has been recorded.") +foodvote.rule = (['foodvote'], r'(.*) (\d{2}:\d{2})( .*)?') + + +def pickfood(phenny, input): + key = input.group(2) or input.sender + try: + req = web.get(API_URL + '/food/' + web.quote(key.strip())) + data = json.loads(req) + except: + raise GrumbleError("Uh oh, I couldn't contact foodforus. HOW WILL WE "\ + "EAT NOW‽") + + if len(data['restaurants']) > 0 and len(data['times']) > 0: + restaurant = data['restaurants'][0] + time = data['times'][0] + + phenny.say("Food is {place} ({place_votes} votes) at {time} "\ + "({time_votes} votes). Happy fooding!".format(place=restaurant[0], + place_votes=restaurant[1], time=time[0], time_votes=time[1])) + else: + phenny.say("Sorry, people need to vote before we can food!") +pickfood.rule = (['pickfood'], r'(.*)') diff --git a/modules/head.py b/modules/head.py old mode 100755 new mode 100644 index 66e9eef86..330ebda33 --- a/modules/head.py +++ b/modules/head.py @@ -7,183 +7,192 @@ http://inamidst.com/phenny/ """ -import re, urllib, urllib2, httplib, urlparse, time -from htmlentitydefs import name2codepoint +import re +import urllib.parse +import time +from html.entities import name2codepoint import web from tools import deprecated -def head(phenny, input): - """Provide HTTP HEAD information.""" - uri = input.group(2) - uri = (uri or '').encode('utf-8') - if ' ' in uri: - uri, header = uri.rsplit(' ', 1) - else: uri, header = uri, None - - if not uri and hasattr(phenny, 'last_seen_uri'): - try: uri = phenny.last_seen_uri[input.sender] - except KeyError: return phenny.say('?') - - if not uri.startswith('htt'): - uri = 'http://' + uri - # uri = uri.replace('#!', '?_escaped_fragment_=') - - try: info = web.head(uri) - except IOError: return phenny.say("Can't connect to %s" % uri) - except httplib.InvalidURL: return phenny.say("Not a valid URI, sorry.") - - if not isinstance(info, list): - try: info = dict(info) - except TypeError: - return phenny.reply('Try .head http://example.org/ [optional header]') - info['Status'] = '200' - else: - newInfo = dict(info[0]) - newInfo['Status'] = str(info[1]) - info = newInfo - - if header is None: - data = [] - if info.has_key('Status'): - data.append(info['Status']) - if info.has_key('content-type'): - data.append(info['content-type'].replace('; charset=', ', ')) - if info.has_key('last-modified'): - modified = info['last-modified'] - modified = time.strptime(modified, '%a, %d %b %Y %H:%M:%S %Z') - data.append(time.strftime('%Y-%m-%d %H:%M:%S UTC', modified)) - if info.has_key('content-length'): - data.append(info['content-length'] + ' bytes') - phenny.reply(', '.join(data)) - else: - headerlower = header.lower() - if info.has_key(headerlower): - phenny.say(header + ': ' + info.get(headerlower)) - else: - msg = 'There was no %s header in the response.' % header - phenny.say(msg) + +def head(phenny, input): + """Provide HTTP HEAD information.""" + uri = input.group(2) + uri = (uri or '') + if ' ' in uri: + uri, header = uri.rsplit(' ', 1) + else: + uri, header = uri, None + + if not uri and hasattr(phenny, 'last_seen_uri'): + try: + uri = phenny.last_seen_uri[input.sender] + except KeyError: + return phenny.say('?') + + if not uri.startswith('htt'): + uri = 'http://' + uri + # uri = uri.replace('#!', '?_escaped_fragment_=') + start = time.time() + + try: + info = web.head(uri) + info['status'] = '200' + except web.HTTPError as e: + if hasattr(e, 'code'): + return phenny.say(str(e.code)) + else: + return phenny.say(str(e.response.status_code)) + except web.ConnectionError: + return phenny.say("Can't connect to %s" % uri) + + resptime = time.time() - start + + if header is None: + data = [] + if 'Status' in info: + data.append(info['Status']) + if 'content-type' in info: + data.append(info['content-type'].replace('; charset=', ', ')) + if 'last-modified' in info: + modified = info['last-modified'] + modified = time.strptime(modified, '%a, %d %b %Y %H:%M:%S %Z') + data.append(time.strftime('%Y-%m-%d %H:%M:%S UTC', modified)) + if 'content-length' in info: + data.append(info['content-length'] + ' bytes') + data.append('{0:1.2f} s'.format(resptime)) + phenny.reply(', '.join(data)) + else: + headerlower = header.lower() + if headerlower in info: + phenny.say(header + ': ' + info.get(headerlower)) + else: + msg = 'There was no %s header in the response.' % header + phenny.say(msg) head.commands = ['head'] head.example = '.head http://www.w3.org/' + r_title = re.compile(r'(?ims)]*>(.*?)') r_entity = re.compile(r'&[A-Za-z0-9#]+;') -@deprecated -def f_title(self, origin, match, args): - """.title - Return the title of URI.""" - uri = match.group(2) - uri = (uri or '').encode('utf-8') - - if not uri and hasattr(self, 'last_seen_uri'): - uri = self.last_seen_uri.get(origin.sender) - if not uri: - return self.msg(origin.sender, 'I need a URI to give the title of...') - - if not ':' in uri: - uri = 'http://' + uri - uri = uri.replace('#!', '?_escaped_fragment_=') - - localhost = [ - 'http://localhost/', 'http://localhost:80/', - 'http://localhost:8080/', 'http://127.0.0.1/', - 'http://127.0.0.1:80/', 'http://127.0.0.1:8080/', - 'https://localhost/', 'https://localhost:80/', - 'https://localhost:8080/', 'https://127.0.0.1/', - 'https://127.0.0.1:80/', 'https://127.0.0.1:8080/', - ] - for s in localhost: - if uri.startswith(s): - return phenny.reply('Sorry, access forbidden.') - - try: - redirects = 0 - while True: - headers = { - 'Accept': 'text/html', - 'User-Agent': 'Mozilla/5.0 (Phenny)' - } - req = urllib2.Request(uri, headers=headers) - u = urllib2.urlopen(req) - info = u.info() - u.close() - # info = web.head(uri) - - if not isinstance(info, list): - status = '200' - else: - status = str(info[1]) - info = info[0] - if status.startswith('3'): - uri = urlparse.urljoin(uri, info['Location']) - else: break - - redirects += 1 - if redirects >= 25: - self.msg(origin.sender, origin.nick + ": Too many redirects") - return - - try: mtype = info['content-type'] - except: - err = ": Couldn't get the Content-Type, sorry" - return self.msg(origin.sender, origin.nick + err) - if not (('/html' in mtype) or ('/xhtml' in mtype)): - self.msg(origin.sender, origin.nick + ": Document isn't HTML") - return - - u = urllib2.urlopen(req) - bytes = u.read(262144) - u.close() - - except IOError: - self.msg(origin.sender, "Can't connect to %s" % uri) - return - - m = r_title.search(bytes) - if m: - title = m.group(1) - title = title.strip() - title = title.replace('\t', ' ') - title = title.replace('\r', ' ') - title = title.replace('\n', ' ') - while ' ' in title: - title = title.replace(' ', ' ') - if len(title) > 200: - title = title[:200] + '[...]' - - def e(m): - entity = m.group(0) - if entity.startswith('&#x'): - cp = int(entity[3:-1], 16) - return unichr(cp).encode('utf-8') - elif entity.startswith('&#'): - cp = int(entity[2:-1]) - return unichr(cp).encode('utf-8') - else: - char = name2codepoint[entity[1:-1]] - return unichr(char).encode('utf-8') - title = r_entity.sub(e, title) - - if title: - try: title.decode('utf-8') - except: - try: title = title.decode('iso-8859-1').encode('utf-8') - except: title = title.decode('cp1252').encode('utf-8') - else: pass - else: title = '[The title is empty.]' - - title = title.replace('\n', '') - title = title.replace('\r', '') - self.msg(origin.sender, origin.nick + ': ' + title) - else: self.msg(origin.sender, origin.nick + ': No title found') -f_title.commands = ['title'] - -def noteuri(phenny, input): - uri = input.group(1).encode('utf-8') - if not hasattr(phenny.bot, 'last_seen_uri'): - phenny.bot.last_seen_uri = {} - phenny.bot.last_seen_uri[input.sender] = uri + +def noteuri(phenny, input): + uri = input.group(1) + if not hasattr(phenny.bot, 'last_seen_uri'): + phenny.bot.last_seen_uri = {} + phenny.bot.last_seen_uri[input.sender] = uri noteuri.rule = r'.*(http[s]?://[^<> "\x01]+)[,.]?' noteuri.priority = 'low' -if __name__ == '__main__': - print __doc__.strip() + + +def snarfuri(phenny, input): + uri = input.group(2) + title = gettitle(phenny, input, uri) + + if title: + phenny.msg(input.sender, title) +snarfuri.rule = r'([^\.].*)?(http[s]?://[^<> "\x01]+)[,.]?' +snarfuri.priority = 'low' +snarfuri.thread = True + + +def gettitle(phenny, input, uri): + if not ':' in uri: + uri = 'http://' + uri + uri = uri.replace('#!', '?_escaped_fragment_=') + + title = None + localhost = [ + 'http://localhost/', 'http://localhost:80/', + 'http://localhost:8080/', 'http://127.0.0.1/', + 'http://127.0.0.1:80/', 'http://127.0.0.1:8080/', + 'https://localhost/', 'https://localhost:80/', + 'https://localhost:8080/', 'https://127.0.0.1/', + 'https://127.0.0.1:80/', 'https://127.0.0.1:8080/', + ] + for s in localhost: + if uri.startswith(s): + return phenny.reply('Sorry, access forbidden.') + + try: + redirects = 0 + while True: + info = web.head(uri) + + if not isinstance(info, list): + status = '200' + else: + status = str(info[1]) + info = info[0] + if status.startswith('3'): + uri = urllib.parse.urljoin(uri, info['Location']) + else: + break + + redirects += 1 + if redirects >= 25: + return None + + try: + mtype = info['content-type'] + except: + return None + + if not (('/html' in mtype) or ('/xhtml' in mtype)): + return None + + bytes = web.get(uri) + #bytes = u.read(262144) + #u.close() + + except: + return + + m = r_title.search(bytes) + if m: + title = m.group(1) + title = title.strip() + title = title.replace('\t', ' ') + title = title.replace('\r', ' ') + title = title.replace('\n', ' ') + while ' ' in title: + title = title.replace(' ', ' ') + if len(title) > 200: + title = title[:200] + '[...]' + + def e(m): + entity = m.group(0) + if entity.startswith('&#x'): + cp = int(entity[3:-1], 16) + return chr(cp) + elif entity.startswith('&#'): + cp = int(entity[2:-1]) + return chr(cp) + else: + char = name2codepoint[entity[1:-1]] + return chr(char) + title = r_entity.sub(e, title) + + if title: + title = title.replace('\n', '') + title = title.replace('\r', '') + title = "[ {0} ]".format(title) + + if "posted" in phenny.variables: + from modules.posted import check_posted + + posted = check_posted(phenny, input, uri) + + if posted: + title = "{0} (posted: {1})".format(title, posted) + + + else: + title = None + return title + + +if __name__ == '__main__': + print(__doc__.strip()) diff --git a/modules/hs.py b/modules/hs.py new file mode 100644 index 000000000..c9fbbb9f4 --- /dev/null +++ b/modules/hs.py @@ -0,0 +1,65 @@ +#!/usr/bin/python3 +""" +hs.py - hokie stalker module +author: mutantmonkey +""" + +from tools import GrumbleError +import web +import lxml.etree + +SEARCH_URL = "https://webapps.middleware.vt.edu/peoplesearch/PeopleSearch?query={0}&dsml-version=2" +RESULTS_URL = "http://search.vt.edu/search/people.html?q={0}" +PERSON_URL = "http://search.vt.edu/search/person.html?person={0:d}" +NS = '{http://www.dsml.org/DSML}' + +"""Search the people search database using the argument as a query.""" +def search(query): + query = web.quote(query) + try: + r = web.get(SEARCH_URL.format(query), verify=False) + except (web.ConnectionError, web.HTTPError): + raise GrumbleError("THE INTERNET IS FUCKING BROKEN. Please try again later.") + + # apparently the failure mode if you search for <3 characters is a blank + # XML page... + if len(r) <= 0: + return False + + xml = lxml.etree.fromstring(r.encode('utf-8')) + results = xml.findall('{0}directory-entries/{0}entry'.format(NS)) + if len(results) <= 0: + return False + + ret = [] + for entry in results: + entry_data = {} + for attr in entry.findall('{0}attr'.format(NS)): + entry_data[attr.attrib['name']] = attr[0].text + ret.append(entry_data) + + return ret + +def hs(phenny, input): + """.hs - Search for someone on Virginia Tech People Search.""" + + q = input.group(2) + if q is None: + return + q = q.strip() + results = RESULTS_URL.format(web.quote(q)) + + s = search(q) + if s: + if len(s) >1: + phenny.reply("Multiple results found; try {0}".format(results)) + else: + for entry in s: + person = PERSON_URL.format(int(entry['uid'])) + phenny.reply("{0} - {1}".format(entry['cn'], person)) + else: + phenny.reply("No results found") +hs.rule = (['hs'], r'(.*)') + +if __name__ == '__main__': + print(__doc__.strip()) diff --git a/modules/imdb.py b/modules/imdb.py new file mode 100644 index 000000000..fca1d6543 --- /dev/null +++ b/modules/imdb.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python +""" +imdb.py - Phenny Web Search Module +Copyright 2012, Randy Nance, randynance.info +Licensed under the Eiffel Forum License 2. + +http://inamidst.com/phenny/ +""" + +import re +import web + + +r_imdb_find = re.compile(r'href="/title/(.*?)/') +r_imdb_details = re.compile(r'(.*?) \((.*?)\) .*?name="description" content="(.*?)"') + +def imdb_search(query): + query = query.replace('!', '') + query = web.quote(query) + uri = 'http://imdb.com/find?q=%s' % query + bytes = web.get(uri) + m = r_imdb_find.search(bytes) + if not m: return m + ID = web.decode(m.group(1)) + uri = 'http://imdb.com/title/%s' % ID + bytes = web.get(uri) + bytes = bytes.replace('\n', '') + info = r_imdb_details.search(bytes) + info = {'Title': info.group(1), 'Year': info.group(2), 'Plot': info.group(3), 'imdbID': ID} + return info + + +def imdb(phenny, input): + """.imdb <movie> - Find a link to a movie on IMDb.""" + + query = input.group(2) + if not query: + return phenny.say('.imdb what?') + + m = imdb_search(query) + if m: + phenny.say('{0} ({1}): {2} http://imdb.com/title/{3}'.format( + m['Title'], + m['Year'], + m['Plot'], + m['imdbID'])) + else: + phenny.reply("No results found for '%s'." % query) +imdb.commands = ['imdb'] +imdb.example = '.imdb Promethius' diff --git a/modules/info.py b/modules/info.py old mode 100755 new mode 100644 index dbf2d44c2..ce3cab29d --- a/modules/info.py +++ b/modules/info.py @@ -7,83 +7,95 @@ http://inamidst.com/phenny/ """ -def doc(phenny, input): - """Shows a command's documentation, and possibly an example.""" - name = input.group(1) - name = name.lower() - - if phenny.doc.has_key(name): - phenny.reply(phenny.doc[name][0]) - if phenny.doc[name][1]: - phenny.say('e.g. ' + phenny.doc[name][1]) -doc.rule = ('$nick', '(?i)(?:help|doc) +([A-Za-z]+)(?:\?+)?$') -doc.example = '$nickname: doc tell?' -doc.priority = 'low' - -def commands(phenny, input): - # This function only works in private message - if input.sender.startswith('#'): return - names = ', '.join(sorted(phenny.doc.iterkeys())) - phenny.say('Commands I recognise: ' + names + '.') - phenny.say(("For help, do '%s: help example?' where example is the " + - "name of the command you want help for.") % phenny.nick) -commands.commands = ['commands'] -commands.priority = 'low' - -def help(phenny, input): - response = ( - 'Hi, I\'m a bot. Say ".commands" to me in private for a list ' + - 'of my commands, or see http://inamidst.com/phenny/ for more ' + - 'general details. My owner is %s.' - ) % phenny.config.owner - phenny.reply(response) -help.rule = ('$nick', r'(?i)help(?:[?!]+)?$') + +def help(phenny, input): + command = input.group(2) + + # work out a help URL to display + if hasattr(phenny.config, 'helpurl'): + helpurl = phenny.config.helpurl + else: + helpurl = "https://vtluug.org/wiki/Wadsworth" + + if input.sender.startswith('#'): + # channels get a brief message instead + phenny.say( + "Hey there, I'm a friendly bot for this channel. Say \".help\" " + "to me in private for a list of my commands or check out my help " + "page at {helpurl}.".format( + helpurl=helpurl, + owner=phenny.config.owner)) + elif command is not None: + command = command.lower() + if command in phenny.doc: + phenny.say(phenny.doc[command][0]) + if phenny.doc[command][1]: + phenny.say('e.g. ' + phenny.doc[command][1]) + else: + phenny.say("Sorry, I'm not that kind of bot.") + else: + commands = ', '.join(sorted(phenny.doc.keys())) + phenny.say( + "Hey there, I'm a friendly bot! Here are the commands I " + "recognize: {commands}".format(commands=commands)) + phenny.say( + "For help with a command, just use .help followed by the name of" + " the command, like \".help botsnack\".") + phenny.say( + "If you need additional help, check out {helpurl} or talk to my " + "owner, {owner}.".format( + helpurl=helpurl, + owner=phenny.config.owner)) +help.rule = (['help', 'command'], r'(.*)') help.priority = 'low' + def stats(phenny, input): - """Show information on command usage patterns.""" - commands = {} - users = {} - channels = {} - - ignore = set(['f_note', 'startup', 'message', 'noteuri']) - for (name, user), count in phenny.stats.items(): - if name in ignore: continue - if not user: continue - - if not user.startswith('#'): - try: users[user] += count - except KeyError: users[user] = count - else: - try: commands[name] += count - except KeyError: commands[name] = count - - try: channels[user] += count - except KeyError: channels[user] = count - - comrank = sorted([(b, a) for (a, b) in commands.iteritems()], reverse=True) - userank = sorted([(b, a) for (a, b) in users.iteritems()], reverse=True) - charank = sorted([(b, a) for (a, b) in channels.iteritems()], reverse=True) - - # most heavily used commands - creply = 'most used commands: ' - for count, command in comrank[:10]: - creply += '%s (%s), ' % (command, count) - phenny.say(creply.rstrip(', ')) - - # most heavy users - reply = 'power users: ' - for count, user in userank[:10]: - reply += '%s (%s), ' % (user, count) - phenny.say(reply.rstrip(', ')) - - # most heavy channels - chreply = 'power channels: ' - for count, channel in charank[:3]: - chreply += '%s (%s), ' % (channel, count) - phenny.say(chreply.rstrip(', ')) + """Show information on command usage patterns.""" + commands = {} + users = {} + channels = {} + + ignore = set(['f_note', 'startup', 'message', 'noteuri', 'logger', + 'snarfuri', 'measure', 'messageAlert']) + for (name, user), count in list(phenny.stats.items()): + if name in ignore: continue + if not user: continue + + if not user.startswith('#'): + try: users[user] += count + except KeyError: users[user] = count + else: + try: commands[name] += count + except KeyError: commands[name] = count + + try: channels[user] += count + except KeyError: channels[user] = count + + comrank = sorted([(b, a) for (a, b) in commands.items()], reverse=True) + userank = sorted([(b, a) for (a, b) in users.items()], reverse=True) + charank = sorted([(b, a) for (a, b) in channels.items()], reverse=True) + + # most heavily used commands + creply = 'most used commands: ' + for count, command in comrank[:10]: + creply += '%s (%s), ' % (command, count) + phenny.say(creply.rstrip(', ')) + + # most heavy users + reply = 'power users: ' + for count, user in userank[:10]: + reply += '%s (%s), ' % (user, count) + phenny.say(reply.rstrip(', ')) + + # most heavy channels + chreply = 'power channels: ' + for count, channel in charank[:3]: + chreply += '%s (%s), ' % (channel, count) + phenny.say(chreply.rstrip(', ')) stats.commands = ['stats'] stats.priority = 'low' -if __name__ == '__main__': - print __doc__.strip() + +if __name__ == '__main__': + print(__doc__.strip()) diff --git a/modules/lastfm.py b/modules/lastfm.py new file mode 100644 index 000000000..aad400032 --- /dev/null +++ b/modules/lastfm.py @@ -0,0 +1,202 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +""" +lastfm.py - lastfm module +author: Casey Link <unnamedrambler@gmail.com> +""" + +import random +import configparser +import os +import web +from lxml import etree +from datetime import datetime + +APIKEY = "816cfe50ddeeb73c9987b85de5c19e71" +APIURL = "https://ws.audioscrobbler.com/2.0/?api_key="+APIKEY+"&" + +config = configparser.RawConfigParser() +config.optionxform = str +config_filename = "" + +def setup(self): + fn = self.nick + '-' + self.config.host + '.lastfm.db' + global config_filename + config_filename = os.path.join(os.path.expanduser('~/.phenny'), fn) + if not os.path.exists(config_filename): + try: f = open(config_filename, 'w') + except OSError: pass + else: + f.write('') + f.close() + + config_file = config.read(config_filename) + if not config.has_section("Nick2User"): + config.add_section("Nick2User") + if not config.has_section("User2Nick"): + config.add_section("User2Nick") + if not config.has_section("Nick2Verb"): + config.add_section("Nick2Verb") + +def lastfm_set(phenny, input): + cmd = input.group(2) + if not cmd or len(cmd.strip()) == 0: + phenny.say("commands: user, verb") + phenny.say("set <username>: associates your IRC nick with your last.fm username.") + phenny.say("example: lastfm-set user joebob") + phenny.say("verb <past>,<present>: customizes the verbs used when displaying your now playing info.") + phenny.say("example: lastfm-set verb listened to, is listening to") + return + if cmd == "user": + value = input.group(5) + if len(value) == 0: + phenny.say("um.. try again. the format is 'lastfm-set user username'") + return + set_username(input.nick, value) + phenny.say("ok, i'll remember that %s is %s on lastfm" % (input.nick, value)) + return + if cmd == "verb": + past = input.group(3) + present = input.group(4) + if len(past) == 0 or len(present) == 0: + phenny.say("umm.. try again. the format is 'lastfm-set verb past phrase, present phrase' example: 'lastfm-set verb listened to, listening to'") + return + set_verb(input.nick, past, present) + phenny.say("ok, i'll remember that %s prefers '%s' and '%s'" % (input.nick, past, present)) + return + +lastfm_set.rule = (['lastfm-set'], r'(\S+)\s+(?:(.*?),(.*)|(\S+))') + +def now_playing(phenny, input): + nick = input.nick + user = "" + arg = input.group(2) + if not arg or len(arg.strip()) == 0: + user = resolve_username(nick) # use the sender + if not user: #nick didnt resolve + user = nick + else: # use the argument + user = resolve_username(arg.strip()) + if not user: # user didnt resolve + user = arg + user = user.strip() + try: + req = web.get("%smethod=user.getrecenttracks&user=%s" % (APIURL, web.quote(user))) + except web.HTTPError as e: + if e.response.status_code == 400: + phenny.say("%s doesn't exist on last.fm, perhaps they need to set user" % (user)) + return + else: + phenny.say("uhoh. try again later, mmkay?") + return + root = etree.fromstring(req.encode('utf-8')) + recenttracks = list(root) + if len(recenttracks) == 0: + phenny.say("%s hasn't played anything recently. this isn't you? try lastfm-set" % (user)) + return + tracks = list(recenttracks[0]) + #print(etree.tostring(recenttracks[0])) + if len(tracks) == 0: + phenny.say("%s hasn't played anything recently. this isn't you? try lastfm-set" % (user)) + return + first = tracks[0] + now = True if first.get("nowplaying") == "true" else False + tags = {} + for e in first.getiterator(): + tags[e.tag] = e + + track = tags['name'].text.strip() + + artist = tags['artist'].text.strip() + + album = "unknown" + if tags['album'].text: + album = tags['album'].text + + date = None + stamp = None + if not now: + date = tags['date'].get("uts") + stamp = int(date) + + if now: + present = get_verb(nick)[1] + phenny.say("%s %s \"%s\" by %s on %s" %(user.strip(), present.strip(), track, artist, album )) + return + else: + past = get_verb(nick)[0] + phenny.say("%s %s \"%s\" by %s on %s %s" %(user.strip(), past.strip(), track, artist, album, pretty_date(stamp))) + +now_playing.commands = ['np'] + +def save_config(): + configfile = open(config_filename, 'w') + config.write(configfile) + configfile.close() + +def set_verb(nick, past, present): + verbs = "%s,%s" % (past,present) + config.set("Nick2Verb", nick, verbs ) + save_config() + +def get_verb(nick): + if config.has_option("Nick2Verb", nick): + return config.get("Nick2Verb", nick).split(',') + return ["listened to","is listening to"] + +def set_username(nick, username): + old_user = resolve_username(nick) + if old_user: + config.remove_option("User2Nick", old_user) + config.set("Nick2User", nick, username) + config.set("User2Nick", username, nick) + save_config() + +def resolve_username(nick): + if config.has_option("Nick2User", nick): + return config.get("Nick2User", nick) + return None + +def pretty_date(time=False): + """ + Get a datetime object or a int() Epoch timestamp and return a + pretty string like 'an hour ago', 'Yesterday', '3 months ago', + 'just now', etc + """ + from datetime import datetime + now = datetime.now() + if type(time) is int: + diff = now - datetime.fromtimestamp(time) + elif not time: + diff = now - now + second_diff = diff.seconds + day_diff = diff.days + + if day_diff < 0: + return '' + + if day_diff == 0: + if second_diff < 10: + return "just now" + if second_diff < 60: + return str(second_diff) + " seconds ago" + if second_diff < 120: + return "a minute ago" + if second_diff < 3600: + return str( second_diff // 60 ) + " minutes ago" + if second_diff < 7200: + return "an hour ago" + if second_diff < 86400: + return str( second_diff // 3600 ) + " hours ago" + if day_diff == 1: + return "Yesterday" + if day_diff < 7: + return str(day_diff) + " days ago" + if day_diff < 31: + return str(day_diff // 7) + " weeks ago" + if day_diff < 365: + return str(day_diff // 30) + " months ago" + return str(day_diff // 365) + " years ago" + +if __name__ == '__main__': + print(__doc__.strip()) diff --git a/modules/linx.py b/modules/linx.py new file mode 100644 index 000000000..8b9550a78 --- /dev/null +++ b/modules/linx.py @@ -0,0 +1,33 @@ +#!/usr/bin/python3 +""" +linx.py - linx.li tools +author: andreim <andreim@andreim.net> +author: mutantmonkey <mutantmonkey@mutantmonkey.in> +""" + +from tools import GrumbleError +import requests +import json + + +def linx(phenny, input, short=False): + """.linx <url> - Upload a remote URL to linx.li.""" + + url = input.group(2) + if not url: + phenny.reply("No URL provided") + return + + try: + r = requests.get("https://linx.vtluug.org/upload?", params={"url": url}, headers={"Accept": "application/json"}) + if "url" in r.json(): + phenny.reply(r.json()["url"]) + else: + phenny.reply(r.json()["error"]) + + except Exception as exc: + raise GrumbleError(exc) + +linx.rule = (['linx'], r'(.*)') + + diff --git a/modules/logger.py b/modules/logger.py new file mode 100644 index 000000000..88e4c6dbb --- /dev/null +++ b/modules/logger.py @@ -0,0 +1,71 @@ +#!/usr/bin/python3 +""" +logger.py - logger for privacy-protecting IRC stats +author: mutantmonkey <mutantmonkey@mutantmonkey.in> +""" + +import os +import random +import sqlite3 + +def setup(self): + fn = self.nick + '-' + self.config.host + '.logger.db' + self.logger_db = os.path.join(os.path.expanduser('~/.phenny'), fn) + self.logger_conn = sqlite3.connect(self.logger_db) + + c = self.logger_conn.cursor() + c.execute('''create table if not exists lines_by_nick ( + channel varchar(255), + nick varchar(255), + lines unsigned big int not null default 0, + characters unsigned big int not null default 0, + last_time timestamp default CURRENT_TIMESTAMP, + quote text, + unique (channel, nick) on conflict replace + );''') + +def logger(phenny, input): + if not logger.conn: + logger.conn = sqlite3.connect(phenny.logger_db) + + sqlite_data = { + 'channel': input.sender, + 'nick': input.nick, + 'msg': input.group(1), + 'chars': len(input.group(1)), + } + + # format action messages + if sqlite_data['msg'][:8] == '\x01ACTION ': + sqlite_data['msg'] = '* {0} {1}'.format(sqlite_data['nick'], sqlite_data['msg'][8:-1]) + + c = logger.conn.cursor() + c.execute('''insert or replace into lines_by_nick + (channel, nick, lines, characters, last_time, quote) + values( + :channel, + :nick, + coalesce((select lines from lines_by_nick where + channel=:channel and nick=:nick) + 1, 1), + coalesce((select characters from lines_by_nick where + channel=:channel and nick=:nick) + :chars, :chars), + CURRENT_TIMESTAMP, + coalesce((select quote from lines_by_nick where + channel=:channel and nick=:nick), :msg) + );''', sqlite_data) + c.close() + + if random.randint(0, 20) == 10: + c = logger.conn.cursor() + c.execute('update lines_by_nick set quote=:msg where channel=:channel \ + and nick=:nick', sqlite_data) + c.close() + + logger.conn.commit() +logger.conn = None +logger.priority = 'low' +logger.rule = r'(.*)' +logger.thread = False + +if __name__ == '__main__': + print(__doc__.strip()) diff --git a/modules/mylife.py b/modules/mylife.py new file mode 100644 index 000000000..3a0ec215a --- /dev/null +++ b/modules/mylife.py @@ -0,0 +1,28 @@ +#!/usr/bin/python3 +""" +mylife.py - various commentary on life +author: Ramblurr <unnamedrambler@gmail.com> +author: mutantmonkey <mutantmonkey@mutantmonkey.in> +""" + +from tools import GrumbleError +import web +import lxml.html + + +def fml(phenny, input): + """.fml - Grab something from fmylife.com.""" + try: + req = web.get("http://www.fmylife.com/random") + except: + raise GrumbleError("I tried to use .fml, but it was broken. FML") + + doc = lxml.html.fromstring(req) + quote = doc.find_class('block')[0].text_content() + quote = quote.strip() + phenny.say(quote) +fml.commands = ['fml'] + + +if __name__ == '__main__': + print(__doc__.strip()) diff --git a/modules/nsfw.py b/modules/nsfw.py new file mode 100644 index 000000000..914beb14d --- /dev/null +++ b/modules/nsfw.py @@ -0,0 +1,17 @@ +#!/usr/bin/python3 +""" +nsfw.py - some things just aren't safe for work, a phenny module +author: Casey Link <unnamedrambler@gmail.com +""" + +def nsfw(phenny, input): + """.nsfw <link> - Mark a link (or some text) as being not safe for work.""" + link = input.group(2) + if not link: + phenny.say(".nsfw <link> - for when a link isn't safe for work") + return + phenny.say("!!NSFW!! -> %s <- !!NSFW!!" % (link)) +nsfw.rule = (['nsfw'], r'(.*)') + +if __name__ == '__main__': + print(__doc__.strip()) diff --git a/modules/oblique.py b/modules/oblique.py deleted file mode 100755 index d93446e32..000000000 --- a/modules/oblique.py +++ /dev/null @@ -1,119 +0,0 @@ -#!/usr/bin/env python -""" -oblique.py - Web Services Interface -Copyright 2008-9, Sean B. Palmer, inamidst.com -Licensed under the Eiffel Forum License 2. - -http://inamidst.com/phenny/ -""" - -import re, urllib -import web - -definitions = 'https://github.com/nslater/oblique/wiki' - -r_item = re.compile(r'(?i)<li>(.*?)</li>') -r_tag = re.compile(r'<[^>]+>') - -def mappings(uri): - result = {} - bytes = web.get(uri) - for item in r_item.findall(bytes): - item = r_tag.sub('', item).strip(' \t\r\n') - if not ' ' in item: continue - - command, template = item.split(' ', 1) - if not command.isalnum(): continue - if not template.startswith('http://'): continue - result[command] = template.replace('&', '&') - return result - -def service(phenny, input, command, args): - t = o.services[command] - template = t.replace('${args}', urllib.quote(args.encode('utf-8'), '')) - template = template.replace('${nick}', urllib.quote(input.nick, '')) - uri = template.replace('${sender}', urllib.quote(input.sender, '')) - - info = web.head(uri) - if isinstance(info, list): - info = info[0] - if not 'text/plain' in info.get('content-type', '').lower(): - return phenny.reply("Sorry, the service didn't respond in plain text.") - bytes = web.get(uri) - lines = bytes.splitlines() - if not lines: - return phenny.reply("Sorry, the service didn't respond any output.") - try: line = lines[0].encode('utf-8')[:350] - except: line = lines[0][:250] - phenny.say(line) - -def refresh(phenny): - if hasattr(phenny.config, 'services'): - services = phenny.config.services - else: services = definitions - - old = o.services - o.serviceURI = services - o.services = mappings(o.serviceURI) - return len(o.services), set(o.services) - set(old) - -def o(phenny, input): - """Call a webservice.""" - text = input.group(2) - - if (not o.services) or (text == 'refresh'): - length, added = refresh(phenny) - if text == 'refresh': - msg = 'Okay, found %s services.' % length - if added: - msg += ' Added: ' + ', '.join(sorted(added)[:5]) - if len(added) > 5: msg += ', &c.' - return phenny.reply(msg) - - if not text: - return phenny.reply('Try %s for details.' % o.serviceURI) - - if ' ' in text: - command, args = text.split(' ', 1) - else: command, args = text, '' - command = command.lower() - - if command == 'service': - msg = o.services.get(args, 'No such service!') - return phenny.reply(msg) - - if not o.services.has_key(command): - return phenny.reply('Service not found in %s' % o.serviceURI) - - if hasattr(phenny.config, 'external'): - default = phenny.config.external.get('*') - manifest = phenny.config.external.get(input.sender, default) - if manifest: - commands = set(manifest) - if (command not in commands) and (manifest[0] != '!'): - return phenny.reply('Sorry, %s is not whitelisted' % command) - elif (command in commands) and (manifest[0] == '!'): - return phenny.reply('Sorry, %s is blacklisted' % command) - service(phenny, input, command, args) -o.commands = ['o'] -o.example = '.o servicename arg1 arg2 arg3' -o.services = {} -o.serviceURI = None - -def snippet(phenny, input): - if not o.services: - refresh(phenny) - - search = urllib.quote(input.group(2).encode('utf-8')) - py = "BeautifulSoup.BeautifulSoup(re.sub('<.*?>|(?<= ) +', '', " + \ - "''.join(chr(ord(c)) for c in " + \ - "eval(urllib.urlopen('http://ajax.googleapis.com/ajax/serv" + \ - "ices/search/web?v=1.0&q=" + search + "').read()" + \ - ".replace('null', 'None'))['responseData']['resul" + \ - "ts'][0]['content'].decode('unicode-escape')).replace(" + \ - "'"', '\x22')), convertEntities=True)" - service(phenny, input, 'py', py) -snippet.commands = ['snippet'] - -if __name__ == '__main__': - print __doc__.strip() diff --git a/modules/ping.py b/modules/ping.py old mode 100755 new mode 100644 index 23219ace4..e6272d93e --- a/modules/ping.py +++ b/modules/ping.py @@ -8,16 +8,16 @@ import random def hello(phenny, input): - greeting = random.choice(('Hi', 'Hey', 'Hello')) - punctuation = random.choice(('', '!')) - phenny.say(greeting + ' ' + input.nick + punctuation) + greeting = random.choice(('Hi', 'Hey', 'Hello')) + punctuation = random.choice(('', '!')) + phenny.say(greeting + ' ' + input.nick + punctuation) hello.rule = r'(?i)(hi|hello|hey) $nickname[ \t]*$' def interjection(phenny, input): - phenny.say(input.nick + '!') + phenny.say(input.nick + '!') interjection.rule = r'$nickname!' interjection.priority = 'high' interjection.thread = False if __name__ == '__main__': - print __doc__.strip() + print(__doc__.strip()) diff --git a/modules/posted.py b/modules/posted.py new file mode 100644 index 000000000..320047788 --- /dev/null +++ b/modules/posted.py @@ -0,0 +1,69 @@ +#!/usr/bin/python3 +""" +posted.py - Remembers who posted which URL, can show on URL match. +author: andreim <andreim@andreim.net> +""" +import os +import sqlite3 +from ago import human + + +def setup(self): + fn = self.nick + '-' + self.config.host + '.posted.db' + self.posted_db = os.path.join(os.path.expanduser('~/.phenny'), fn) + conn = sqlite3.connect(self.posted_db) + + c = conn.cursor() + c.execute('''create table if not exists posted ( + channel varchar(255), + nick varchar(255), + url varchar(512), + time timestamp date default (datetime('now', 'localtime')) + );''') + + c.close() + conn.close() + + +def check_posted(phenny, input, url): + if url: + conn = sqlite3.connect(phenny.posted_db, + detect_types=sqlite3.PARSE_DECLTYPES) + c = conn.cursor() + c.execute("SELECT nick, time FROM posted WHERE channel=? AND url=?", + (input.sender, url)) + res = c.fetchone() + + posted = None + + if res: + nickname = res[0] + time = human(res[1]) + + posted = "{0} by {1}".format(time, nickname) + + + else: + c.execute("INSERT INTO posted (channel, nick, url) VALUES (?, ?, ?)", + (input.sender, input.nick, url)) + conn.commit() + + conn.close() + + return posted + + +def posted(phenny, input): + if not input.group(2): + return phenny.say(".posted <URL> - checks if URL has been posted" + + " before in this channel.") + url = input.group(2) + + posted = check_posted(phenny, input, url) + if posted: + phenny.reply("URL was posted {0}".format(posted)) + else: + phenny.reply("I don't remember seeing this URL in this channel.") + +posted.thread = False +posted.commands = ["posted"] diff --git a/modules/reload.py b/modules/reload.py old mode 100755 new mode 100644 index dfd0e8eca..0723d61be --- a/modules/reload.py +++ b/modules/reload.py @@ -11,45 +11,45 @@ import irc def f_reload(phenny, input): - """Reloads a module, for use by admins only.""" - if not input.admin: return + """Reloads a module, for use by admins only.""" + if not input.admin: return - name = input.group(2) - if name == phenny.config.owner: - return phenny.reply('What?') + name = input.group(2) + if name == phenny.config.owner: + return phenny.reply('What?') - if (not name) or (name == '*'): - phenny.variables = None - phenny.commands = None - phenny.setup() - return phenny.reply('done') + if (not name) or (name == '*'): + phenny.variables = None + phenny.commands = None + phenny.setup() + return phenny.reply('done') - if not sys.modules.has_key(name): - return phenny.reply('%s: no such module!' % name) + if name not in sys.modules: + return phenny.reply('%s: no such module!' % name) - # Thanks to moot for prodding me on this - path = sys.modules[name].__file__ - if path.endswith('.pyc') or path.endswith('.pyo'): - path = path[:-1] - if not os.path.isfile(path): - return phenny.reply('Found %s, but not the source file' % name) + # Thanks to moot for prodding me on this + path = sys.modules[name].__file__ + if path.endswith('.pyc') or path.endswith('.pyo'): + path = path[:-1] + if not os.path.isfile(path): + return phenny.reply('Found %s, but not the source file' % name) - module = imp.load_source(name, path) - sys.modules[name] = module - if hasattr(module, 'setup'): - module.setup(phenny) + module = imp.load_source(name, path) + sys.modules[name] = module + if hasattr(module, 'setup'): + module.setup(phenny) - mtime = os.path.getmtime(module.__file__) - modified = time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(mtime)) + mtime = os.path.getmtime(module.__file__) + modified = time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(mtime)) - phenny.register(vars(module)) - phenny.bind_commands() + phenny.register(vars(module)) + phenny.bind_commands() - phenny.reply('%r (version: %s)' % (module, modified)) + phenny.reply('%r (version: %s)' % (module, modified)) f_reload.name = 'reload' f_reload.rule = ('$nick', ['reload'], r'(\S+)?') f_reload.priority = 'low' f_reload.thread = False if __name__ == '__main__': - print __doc__.strip() + print(__doc__.strip()) diff --git a/modules/remind.py b/modules/remind.py old mode 100755 new mode 100644 index ec1a4d16f..df39ec640 --- a/modules/remind.py +++ b/modules/remind.py @@ -10,127 +10,182 @@ import os, re, time, threading def filename(self): - name = self.nick + '-' + self.config.host + '.reminders.db' - return os.path.join(os.path.expanduser('~/.phenny'), name) + name = self.nick + '-' + self.config.host + '.reminders.db' + return os.path.join(os.path.expanduser('~/.phenny'), name) def load_database(name): - data = {} - if os.path.isfile(name): - f = open(name, 'rb') - for line in f: - unixtime, channel, nick, message = line.split('\t') - message = message.rstrip('\n') - t = int(unixtime) - reminder = (channel, nick, message) - try: data[t].append(reminder) - except KeyError: data[t] = [reminder] - f.close() - return data + data = {} + if os.path.isfile(name): + f = open(name, 'r') + for line in f: + unixtime, channel, nick, message = line.split('\t') + message = message.rstrip('\n') + t = int(unixtime) + reminder = (channel, nick, message) + try: data[t].append(reminder) + except KeyError: data[t] = [reminder] + f.close() + return data def dump_database(name, data): - f = open(name, 'wb') - for unixtime, reminders in data.iteritems(): - for channel, nick, message in reminders: - f.write('%s\t%s\t%s\t%s\n' % (unixtime, channel, nick, message)) - f.close() + f = open(name, 'w') + for unixtime, reminders in data.items(): + for channel, nick, message in reminders: + f.write('%s\t%s\t%s\t%s\n' % (unixtime, channel, nick, message)) + f.close() def setup(phenny): - phenny.rfn = filename(phenny) - phenny.rdb = load_database(phenny.rfn) - - def monitor(phenny): - time.sleep(5) - while True: - now = int(time.time()) - unixtimes = [int(key) for key in phenny.rdb] - oldtimes = [t for t in unixtimes if t <= now] - if oldtimes: - for oldtime in oldtimes: - for (channel, nick, message) in phenny.rdb[oldtime]: - if message: - phenny.msg(channel, nick + ': ' + message) - else: phenny.msg(channel, nick + '!') - del phenny.rdb[oldtime] - dump_database(phenny.rfn, phenny.rdb) - time.sleep(2.5) - - targs = (phenny,) - t = threading.Thread(target=monitor, args=targs) - t.start() + phenny.rfn = filename(phenny) + phenny.rdb = load_database(phenny.rfn) + + def monitor(phenny): + time.sleep(5) + while True: + now = int(time.time()) + unixtimes = [int(key) for key in phenny.rdb] + oldtimes = [t for t in unixtimes if t <= now] + if oldtimes: + for oldtime in oldtimes: + for (channel, nick, message) in phenny.rdb[oldtime]: + if message: + phenny.msg(channel, nick + ': ' + message) + else: phenny.msg(channel, nick + '!') + del phenny.rdb[oldtime] + dump_database(phenny.rfn, phenny.rdb) + time.sleep(2.5) + + targs = (phenny,) + t = threading.Thread(target=monitor, args=targs) + t.daemon = True + t.start() scaling = { - 'years': 365.25 * 24 * 3600, - 'year': 365.25 * 24 * 3600, - 'yrs': 365.25 * 24 * 3600, - 'y': 365.25 * 24 * 3600, - - 'months': 29.53059 * 24 * 3600, - 'month': 29.53059 * 24 * 3600, - 'mo': 29.53059 * 24 * 3600, - - 'weeks': 7 * 24 * 3600, - 'week': 7 * 24 * 3600, - 'wks': 7 * 24 * 3600, - 'wk': 7 * 24 * 3600, - 'w': 7 * 24 * 3600, - - 'days': 24 * 3600, - 'day': 24 * 3600, - 'd': 24 * 3600, - - 'hours': 3600, - 'hour': 3600, - 'hrs': 3600, - 'hr': 3600, - 'h': 3600, - - 'minutes': 60, - 'minute': 60, - 'mins': 60, - 'min': 60, - 'm': 60, - - 'seconds': 1, - 'second': 1, - 'secs': 1, - 'sec': 1, - 's': 1 + 'years': 365.25 * 24 * 3600, + 'year': 365.25 * 24 * 3600, + 'yrs': 365.25 * 24 * 3600, + 'y': 365.25 * 24 * 3600, + + 'months': 29.53059 * 24 * 3600, + 'month': 29.53059 * 24 * 3600, + 'mo': 29.53059 * 24 * 3600, + + 'weeks': 7 * 24 * 3600, + 'week': 7 * 24 * 3600, + 'wks': 7 * 24 * 3600, + 'wk': 7 * 24 * 3600, + 'w': 7 * 24 * 3600, + + 'days': 24 * 3600, + 'day': 24 * 3600, + 'd': 24 * 3600, + + 'hours': 3600, + 'hour': 3600, + 'hrs': 3600, + 'hr': 3600, + 'h': 3600, + + 'minutes': 60, + 'minute': 60, + 'mins': 60, + 'min': 60, + 'm': 60, + + 'seconds': 1, + 'second': 1, + 'secs': 1, + 'sec': 1, + 's': 1 } -periods = '|'.join(scaling.keys()) +periods = '|'.join(list(scaling.keys())) p_command = r'\.in ([0-9]+(?:\.[0-9]+)?)\s?((?:%s)\b)?:?\s?(.*)' % periods r_command = re.compile(p_command) def remind(phenny, input): - m = r_command.match(input.bytes) - if not m: - return phenny.reply("Sorry, didn't understand the input.") - length, scale, message = m.groups() - - length = float(length) - factor = scaling.get(scale, 60) - duration = length * factor - - if duration % 1: - duration = int(duration) + 1 - else: duration = int(duration) - - t = int(time.time()) + duration - reminder = (input.sender, input.nick, message) - - try: phenny.rdb[t].append(reminder) - except KeyError: phenny.rdb[t] = [reminder] - - dump_database(phenny.rfn, phenny.rdb) - - if duration >= 60: - w = '' - if duration >= 3600 * 12: - w += time.strftime(' on %d %b %Y', time.gmtime(t)) - w += time.strftime(' at %H:%MZ', time.gmtime(t)) - phenny.reply('Okay, will remind%s' % w) - else: phenny.reply('Okay, will remind in %s secs' % duration) + """Set a reminder""" + m = r_command.match(input.bytes) + if not m: + return phenny.reply("Sorry, didn't understand the input.") + length, scale, message = m.groups() + + length = float(length) + factor = scaling.get(scale, 60) + duration = length * factor + + if duration % 1: + duration = int(duration) + 1 + else: duration = int(duration) + + t = int(time.time()) + duration + reminder = (input.sender, input.nick, message) + + try: phenny.rdb[t].append(reminder) + except KeyError: phenny.rdb[t] = [reminder] + + dump_database(phenny.rfn, phenny.rdb) + + if duration >= 60: + w = '' + if duration >= 3600 * 12: + w += time.strftime(' on %d %b %Y', time.gmtime(t)) + w += time.strftime(' at %H:%MZ', time.gmtime(t)) + phenny.reply('Okay, will remind%s' % w) + else: phenny.reply('Okay, will remind in %s secs' % duration) +remind.name = 'in' +remind.example = '.in 15 minutes do work' remind.commands = ['in'] +r_time = re.compile(r'^([0-9]{2}[:.][0-9]{2})') +r_zone = re.compile(r'( ?([A-Za-z]+|[+-]\d\d?))') + +import calendar +from modules.clock import TimeZones + +def at(phenny, input): + message = input[4:] + + m = r_time.match(message) + if not m: + return phenny.reply("Sorry, didn't understand the time spec.") + t = m.group(1).replace('.', ':') + message = message[len(t):] + + m = r_zone.match(message) + if not m: + return phenny.reply("Sorry, didn't understand the zone spec.") + z = m.group(2) + message = message[len(m.group(1)):].strip() + + if z.startswith('+') or z.startswith('-'): + tz = int(z) + + if z in TimeZones: + tz = TimeZones[z] + else: return phenny.reply("Sorry, didn't understand the time zone.") + + d = time.strftime("%Y-%m-%d", time.gmtime()) + d = time.strptime(("%s %s" % (d, t)), "%Y-%m-%d %H:%M") + + d = int(calendar.timegm(d) - (3600 * tz)) + duration = int((d - time.time()) / 60) + + if duration < 1: + return phenny.reply("Sorry, that date is this minute or in the past. And only times in the same day are supported!") + + # phenny.say("%s %s %s" % (t, tz, d)) + + reminder = (input.sender, input.nick, message) + # phenny.say(str((d, reminder))) + try: phenny.rdb[d].append(reminder) + except KeyError: phenny.rdb[d] = [reminder] + + phenny.sending.acquire() + dump_database(phenny.rfn, phenny.rdb) + phenny.sending.release() + + phenny.reply("Reminding at %s %s - in %s minute(s)" % (t, z, duration)) +at.commands = ['at'] + if __name__ == '__main__': - print __doc__.strip() + print(__doc__.strip()) diff --git a/modules/rule34.py b/modules/rule34.py new file mode 100644 index 000000000..d8820c972 --- /dev/null +++ b/modules/rule34.py @@ -0,0 +1,41 @@ +#!/usr/bin/python3 +""" +rule34.py - rule 34 module +author: mutantmonkey <mutantmonkey@mutantmonkey.in> +""" + +from tools import GrumbleError +import web +import lxml.html + +def rule34(phenny, input): + """.rule34 <query> - Rule 34: If it exists there is porn of it.""" + + q = input.group(2) + if not q: + phenny.say(rule34.__doc__.strip()) + return + + try: + req = web.get("http://rule34.xxx/index.php?page=post&s=list&tags={0}".format(web.quote(q))) + except: + raise GrumbleError("THE INTERNET IS FUCKING BROKEN. Please try again later.") + + doc = lxml.html.fromstring(req) + doc.make_links_absolute('http://rule34.xxx/') + thumbs = doc.find_class('thumb') + if len(thumbs) <= 0: + phenny.reply("You just broke Rule 34! Better start uploading...") + return + + try: + link = thumbs[0].find('a').attrib['href'] + except AttributeError: + raise GrumbleError("THE INTERNET IS FUCKING BROKEN. Please try again later.") + + response = '!!NSFW!! -> {0} <- !!NSFW!!'.format(link) + phenny.reply(response) +rule34.rule = (['rule34'], r'(.*)') + +if __name__ == '__main__': + print(__doc__.strip()) diff --git a/modules/search.py b/modules/search.py old mode 100755 new mode 100644 index 13fadbb36..7ae6c1914 --- a/modules/search.py +++ b/modules/search.py @@ -10,193 +10,191 @@ import re import web -class Grab(web.urllib.URLopener): - def __init__(self, *args): - self.version = 'Mozilla/5.0 (Phenny)' - web.urllib.URLopener.__init__(self, *args) - self.addheader('Referer', 'https://github.com/sbp/phenny') - def http_error_default(self, url, fp, errcode, errmsg, headers): - return web.urllib.addinfourl(fp, [headers, errcode], "http:" + url) - -def google_ajax(query): - """Search using AjaxSearch, and return its JSON.""" - if isinstance(query, unicode): - query = query.encode('utf-8') - uri = 'http://ajax.googleapis.com/ajax/services/search/web' - args = '?v=1.0&safe=off&q=' + web.urllib.quote(query) - handler = web.urllib._urlopener - web.urllib._urlopener = Grab() - bytes = web.get(uri + args) - web.urllib._urlopener = handler - return web.json(bytes) + +r_google = re.compile(r'href="\/url\?q=(http.*?)&') + +def generic_google(query): + query = web.quote(query) + uri = 'https://google.co.uk/search?q=%s' % query + return web.get(uri) def google_search(query): - results = google_ajax(query) - try: return results['responseData']['results'][0]['unescapedUrl'] - except IndexError: return None - except TypeError: - print results - return False + bytes = generic_google(query) + m = r_google.search(bytes) + if m: + uri = web.decode(m.group(1)) + return web.unquote(uri) -def google_count(query): - results = google_ajax(query) - if not results.has_key('responseData'): return '0' - if not results['responseData'].has_key('cursor'): return '0' - if not results['responseData']['cursor'].has_key('estimatedResultCount'): - return '0' - return results['responseData']['cursor']['estimatedResultCount'] +r_google_count = re.compile(r'id="resultStats">About (.*?) ') +def google_count(query): + query = web.quote(query) + uri = 'https://google.co.uk/search?q=%s' % query + bytes = web.get(uri) + m = r_google_count.search(bytes) + if m: + result = web.decode(m.group(1)).replace(',', '') + return int(result) + else: return 0 + def formatnumber(n): - """Format a number with beautiful commas.""" - parts = list(str(n)) - for i in range((len(parts) - 3), 0, -3): - parts.insert(i, ',') - return ''.join(parts) + """Format a number with beautiful commas.""" + parts = list(str(n)) + for i in range((len(parts) - 3), 0, -3): + parts.insert(i, ',') + return ''.join(parts) def g(phenny, input): - """Queries Google for the specified input.""" - query = input.group(2) - if not query: - return phenny.reply('.g what?') - query = query.encode('utf-8') - uri = google_search(query) - if uri: - phenny.reply(uri) - if not hasattr(phenny.bot, 'last_seen_uri'): - phenny.bot.last_seen_uri = {} - phenny.bot.last_seen_uri[input.sender] = uri - elif uri is False: phenny.reply("Problem getting data from Google.") - else: phenny.reply("No results found for '%s'." % query) + """Queries Google for the specified input.""" + query = input.group(2) + if not query: + return phenny.reply('.g what?') + uri = google_search(query) + if uri: + phenny.reply(uri) + if not hasattr(phenny.bot, 'last_seen_uri'): + phenny.bot.last_seen_uri = {} + phenny.bot.last_seen_uri[input.sender] = uri + else: phenny.reply("No results found for '%s'." % query) g.commands = ['g'] g.priority = 'high' g.example = '.g swhack' def gc(phenny, input): - """Returns the number of Google results for the specified input.""" - query = input.group(2) - if not query: - return phenny.reply('.gc what?') - query = query.encode('utf-8') - num = formatnumber(google_count(query)) - phenny.say(query + ': ' + num) + """Returns the number of Google results for the specified input.""" + query = input.group(2) + if not query: + return phenny.reply('.gc what?') + num = formatnumber(google_count(query)) + phenny.say(query + ': ' + num) gc.commands = ['gc'] gc.priority = 'high' gc.example = '.gc extrapolate' r_query = re.compile( - r'\+?"[^"\\]*(?:\\.[^"\\]*)*"|\[[^]\\]*(?:\\.[^]\\]*)*\]|\S+' + r'\+?"[^"\\]*(?:\\.[^"\\]*)*"|\[[^]\\]*(?:\\.[^]\\]*)*\]|\S+' ) def gcs(phenny, input): - if not input.group(2): - return phenny.reply("Nothing to compare.") - queries = r_query.findall(input.group(2)) - if len(queries) > 6: - return phenny.reply('Sorry, can only compare up to six things.') - - results = [] - for i, query in enumerate(queries): - query = query.strip('[]') - query = query.encode('utf-8') - n = int((formatnumber(google_count(query)) or '0').replace(',', '')) - results.append((n, query)) - if i >= 2: __import__('time').sleep(0.25) - if i >= 4: __import__('time').sleep(0.25) - - results = [(term, n) for (n, term) in reversed(sorted(results))] - reply = ', '.join('%s (%s)' % (t, formatnumber(n)) for (t, n) in results) - phenny.say(reply) + """Compare the number of Google results for the specified paramters.""" + if not input.group(2): + return phenny.reply("Nothing to compare.") + queries = r_query.findall(input.group(2)) + if len(queries) > 6: + return phenny.reply('Sorry, can only compare up to six things.') + results = [] + for i, query in enumerate(queries): + query = query.strip('[]') + n = int((formatnumber(google_count(query)) or '0').replace(',', '')) + results.append((n, query)) + if i >= 2: __import__('time').sleep(0.25) + if i >= 4: __import__('time').sleep(0.25) + + results = [(term, n) for (n, term) in reversed(sorted(results))] + reply = ', '.join('%s (%s)' % (t, formatnumber(n)) for (t, n) in results) + phenny.say(reply) gcs.commands = ['gcs', 'comp'] +gcs.example = '.gcs Ronaldo Messi' -r_bing = re.compile(r'<h3><a href="([^"]+)"') +r_bing = re.compile(r'<h2><a href="([^"]+)"') def bing_search(query, lang='en-GB'): - query = web.urllib.quote(query) - base = 'http://www.bing.com/search?mkt=%s&q=' % lang - bytes = web.get(base + query) - m = r_bing.search(bytes) - if m: return m.group(1) + query = web.quote(query) + base = 'https://www.bing.com/search?mkt=%s&q=' % lang + bytes = web.get(base + query) + m = r_bing.search(bytes) + if m: return m.group(1) def bing(phenny, input): - """Queries Bing for the specified input.""" - query = input.group(2) - if query.startswith(':'): - lang, query = query.split(' ', 1) - lang = lang[1:] - else: lang = 'en-GB' - if not query: - return phenny.reply('.bing what?') - - query = query.encode('utf-8') - uri = bing_search(query, lang) - if uri: - phenny.reply(uri) - if not hasattr(phenny.bot, 'last_seen_uri'): - phenny.bot.last_seen_uri = {} - phenny.bot.last_seen_uri[input.sender] = uri - else: phenny.reply("No results found for '%s'." % query) + """Queries Bing for the specified input.""" + query = input.group(2) + if query.startswith(':'): + lang, query = query.split(' ', 1) + lang = lang[1:] + else: lang = 'en-GB' + if not query: + return phenny.reply('.bing what?') + uri = bing_search(query, lang) + if uri: + phenny.reply(uri) + if not hasattr(phenny.bot, 'last_seen_uri'): + phenny.bot.last_seen_uri = {} + phenny.bot.last_seen_uri[input.sender] = uri + else: phenny.reply("No results found for '%s'." % query) bing.commands = ['bing'] bing.example = '.bing swhack' -r_duck = re.compile(r'nofollow" class="[^"]+" href="(.*?)">') +r_duck = re.compile(r'web-result.*?nofollow.*?href=".+?(http.*?)"', re.DOTALL) def duck_search(query): - query = query.replace('!', '') - query = web.urllib.quote(query) - uri = 'http://duckduckgo.com/html/?q=%s&kl=uk-en' % query - bytes = web.get(uri) - m = r_duck.search(bytes) - if m: return web.decode(m.group(1)) - -def duck(phenny, input): - query = input.group(2) - if not query: return phenny.reply('.ddg what?') - - query = query.encode('utf-8') - uri = duck_search(query) - if uri: - phenny.reply(uri) - if not hasattr(phenny.bot, 'last_seen_uri'): - phenny.bot.last_seen_uri = {} - phenny.bot.last_seen_uri[input.sender] = uri - else: phenny.reply("No results found for '%s'." % query) + query = query.replace('!', '') + query = web.quote(query) + uri = 'https://duckduckgo.com/html/?q=%s&kl=uk-en' % query + bytes = web.get(uri) + m = r_duck.search(bytes) + if m: + uri = web.decode(m.group(1)) + return web.unquote(uri) + +def duck_api(query): + uri = 'https://api.duckduckgo.com/?q=%s&format=json&no_redirect=1' % query + bytes = web.get(uri) + json = web.json(bytes) + if query[:1] == '!': + return json['Redirect'] + elif json['Abstract']: + return json['AbstractURL'] + ' : ' + json['Abstract'] + else: return json['AbstractURL'] + +def duck(phenny, input): + """Queries DuckDuckGo for specified input.""" + query = input.group(2) + if not query: return phenny.reply('.ddg what?') + uri = duck_api(query) + if not uri: + uri = duck_search(query) + if uri: + phenny.reply(uri) + if not hasattr(phenny.bot, 'last_seen_uri'): + phenny.bot.last_seen_uri = {} + phenny.bot.last_seen_uri[input.sender] = uri + else: phenny.reply("No results found for '%s'." % query) duck.commands = ['duck', 'ddg'] +duck.example = '.duck football' + +def newton_api(operation, expression): + expression = web.quote(expression, safe='') + uri = "https://newton.now.sh/{}/{}".format(operation, expression) + bytes = web.get(uri) + json = web.json(bytes) + if 'result' in json: + return str(json['result']) + return None def search(phenny, input): - if not input.group(2): - return phenny.reply('.search for what?') - query = input.group(2).encode('utf-8') - gu = google_search(query) or '-' - bu = bing_search(query) or '-' - du = duck_search(query) or '-' - - if (gu == bu) and (bu == du): - result = '%s (g, b, d)' % gu - elif (gu == bu): - result = '%s (g, b), %s (d)' % (gu, du) - elif (bu == du): - result = '%s (b, d), %s (g)' % (bu, gu) - elif (gu == du): - result = '%s (g, d), %s (b)' % (gu, bu) - else: - if len(gu) > 250: gu = '(extremely long link)' - if len(bu) > 150: bu = '(extremely long link)' - if len(du) > 150: du = '(extremely long link)' - result = '%s (g), %s (b), %s (d)' % (gu, bu, du) - - phenny.reply(result) + if not input.group(2): + return phenny.reply('.search for what?') + query = input.group(2) + gu = google_search(query) or '-' + bu = bing_search(query) or '-' + du = duck_search(query) or '-' + + if (gu == bu) and (bu == du): + result = '%s (g, b, d)' % gu + elif (gu == bu): + result = '%s (g, b), %s (d)' % (gu, du) + elif (bu == du): + result = '%s (b, d), %s (g)' % (bu, gu) + elif (gu == du): + result = '%s (g, d), %s (b)' % (gu, bu) + else: + if len(gu) > 250: gu = '(extremely long link)' + if len(bu) > 150: bu = '(extremely long link)' + if len(du) > 150: du = '(extremely long link)' + result = '%s (g), %s (b), %s (d)' % (gu, bu, du) + + phenny.reply(result) search.commands = ['search'] -def suggest(phenny, input): - if not input.group(2): - return phenny.reply("No query term.") - query = input.group(2).encode('utf-8') - uri = 'http://websitedev.de/temp-bin/suggest.pl?q=' - answer = web.get(uri + web.urllib.quote(query).replace('+', '%2B')) - if answer: - phenny.say(answer) - else: phenny.reply('Sorry, no result.') -suggest.commands = ['suggest'] - if __name__ == '__main__': - print __doc__.strip() + print(__doc__.strip()) diff --git a/modules/seen.py b/modules/seen.py old mode 100755 new mode 100644 index 26dc05f8e..e369dc5d8 --- a/modules/seen.py +++ b/modules/seen.py @@ -7,43 +7,63 @@ http://inamidst.com/phenny/ """ -import time +import time, os, shelve, datetime from tools import deprecated -@deprecated -def f_seen(self, origin, match, args): - """.seen <nick> - Reports when <nick> was last seen.""" - if origin.sender == '#talis': return - nick = match.group(2).lower() - if not hasattr(self, 'seen'): - return self.msg(origin.sender, '?') - if self.seen.has_key(nick): - channel, t = self.seen[nick] - t = time.strftime('%Y-%m-%d %H:%M:%S UTC', time.gmtime(t)) - - msg = "I last saw %s at %s on %s" % (nick, t, channel) - self.msg(origin.sender, str(origin.nick) + ': ' + msg) - else: self.msg(origin.sender, "Sorry, I haven't seen %s around." % nick) +def f_seen(phenny, input): + """.seen <nick> - Reports when <nick> was last seen.""" + nick = input.group(2).lower() + if not hasattr(phenny, 'seen'): + return phenny.msg(input.sender, '?') + if nick in phenny.seen: + channel, t = phenny.seen[nick] + dt = timesince(datetime.datetime.utcfromtimestamp(t)) + t = time.strftime('%Y-%m-%d %H:%M:%S UTC', time.gmtime(t)) + + msg = "I last saw %s at %s (%s) on %s" % (nick, t, dt, channel) + phenny.reply(msg) + else: phenny.reply("Sorry, I haven't seen %s around." % nick) +f_seen.name = 'seen' +f_seen.example = '.seen firespeaker' f_seen.rule = (['seen'], r'(\S+)') @deprecated def f_note(self, origin, match, args): - def note(self, origin, match, args): - if not hasattr(self.bot, 'seen'): - self.bot.seen = {} - if origin.sender.startswith('#'): - # if origin.sender == '#inamidst': return - self.seen[origin.nick.lower()] = (origin.sender, time.time()) - - # if not hasattr(self, 'chanspeak'): - # self.chanspeak = {} - # if (len(args) > 2) and args[2].startswith('#'): - # self.chanspeak[args[2]] = args[0] - - try: note(self, origin, match, args) - except Exception, e: print e + def note(self, origin, match, args): + if not hasattr(self.bot, 'seen'): + fn = self.nick + '-' + self.config.host + '.seen' + path = os.path.join(os.path.expanduser('~/.phenny'), fn) + self.bot.seen = shelve.open(path) + if origin.sender.startswith('#'): + self.seen[origin.nick.lower()] = (origin.sender, time.time()) + self.seen.sync() + + try: note(self, origin, match, args) + except Exception as e: print(e) f_note.rule = r'(.*)' f_note.priority = 'low' +def timesince(td): + seconds = int(abs(datetime.datetime.utcnow() - td).total_seconds()) + periods = [ + ('year', 60*60*24*365), + ('month', 60*60*24*30), + ('day', 60*60*24), + ('hour', 60*60), + ('minute', 60), + ('second', 1) + ] + + strings = [] + for period_name, period_seconds in periods: + if seconds > period_seconds and len(strings) < 2: + period_value, seconds = divmod(seconds, period_seconds) + if period_value == 1: + strings.append("%s %s" % (period_value, period_name)) + else: + strings.append("%s %ss" % (period_value, period_name)) + + return "just now" if len(strings) < 1 else " and ".join(strings) + " ago" + if __name__ == '__main__': - print __doc__.strip() + print(__doc__.strip()) diff --git a/modules/slogan.py b/modules/slogan.py new file mode 100644 index 000000000..ac421b9ac --- /dev/null +++ b/modules/slogan.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python +""" +slogan.py - Phenny Slogan Module +Copyright (c) 2011 Dafydd Crosby - http://www.dafyddcrosby.com + +Licensed under the Eiffel Forum License 2. +""" + +from tools import GrumbleError +import re +import web + +uri = 'http://www.sloganizer.net/en/outbound.php?slogan=%s' + +def sloganize(word): + bytes = web.get(uri % web.quote(word)) + return bytes + +def slogan(phenny, input): + """.slogan <term> - Come up with a slogan for a term.""" + + word = input.group(2) + if word is None: + phenny.say("You need to specify a word; try .slogan Granola") + return + + word = word.strip() + slogan = sloganize(word) + + # Remove HTML tags + remove_tags = re.compile(r'<.*?>') + slogan = remove_tags.sub('', slogan) + + if not slogan: + raise GrumbleError("Looks like an issue with sloganizer.net") + phenny.say(slogan) +slogan.commands = ['slogan'] +slogan.example = '.slogan Granola' + +if __name__ == '__main__': + print(__doc__.strip()) diff --git a/modules/stache.py b/modules/stache.py new file mode 100644 index 000000000..6c20d2775 --- /dev/null +++ b/modules/stache.py @@ -0,0 +1,22 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +""" +stache.py - mustachify.me module +author: mutantmonkey <mutantmonkey@mutantmonkey.in> +""" + +import web + +def stache(phenny, input): + """.stache <url> - Mustachify an image.""" + url = input.group(2) + if not url: + phenny.reply("Please provide an image to Mustachify™.") + return + + phenny.reply('http://mustachify.me/?src=' + web.quote(url)) +stache.rule = (['stache'], + '(https?:\/\/[^ #]+\.(?:png|jpg|jpeg))(?:[#]\.png)?') + +if __name__ == '__main__': + print(__doc__.strip()) diff --git a/modules/startup.py b/modules/startup.py old mode 100755 new mode 100644 index 297dfb5a3..a77a967fb --- a/modules/startup.py +++ b/modules/startup.py @@ -10,65 +10,58 @@ import threading, time def setup(phenny): - # by clsn - phenny.data = {} - refresh_delay = 300.0 + print("Setting up phenny") + # by clsn + phenny.data = {} + refresh_delay = 300.0 - if hasattr(phenny.config, 'refresh_delay'): - try: refresh_delay = float(phenny.config.refresh_delay) - except: pass + if hasattr(phenny.config, 'refresh_delay'): + try: refresh_delay = float(phenny.config.refresh_delay) + except: pass - def close(): - print "Nobody PONGed our PING, restarting" - phenny.handle_close() + def close(): + print("Nobody PONGed our PING, restarting") + phenny.handle_close() - def pingloop(): - timer = threading.Timer(refresh_delay, close, ()) - phenny.data['startup.setup.timer'] = timer - phenny.data['startup.setup.timer'].start() - # print "PING!" - phenny.write(('PING', phenny.config.host)) - phenny.data['startup.setup.pingloop'] = pingloop + def pingloop(): + timer = threading.Timer(refresh_delay, close, ()) + phenny.data['startup.setup.timer'] = timer + phenny.data['startup.setup.timer'].start() + phenny.proto.ping(phenny.config.host) + phenny.data['startup.setup.pingloop'] = pingloop - def pong(phenny, input): - try: - # print "PONG!" - phenny.data['startup.setup.timer'].cancel() - time.sleep(refresh_delay + 60.0) - pingloop() - except: pass - pong.event = 'PONG' - pong.thread = True - pong.rule = r'.*' - phenny.variables['pong'] = pong + def pong(phenny, input): + try: + phenny.data['startup.setup.timer'].cancel() + time.sleep(refresh_delay + 60.0) + pingloop() + except: pass + pong.event = 'PONG' + pong.thread = True + pong.rule = r'.*' + phenny.variables['pong'] = pong - # Need to wrap handle_connect to start the loop. - inner_handle_connect = phenny.handle_connect +def startup(phenny, input): + import time - def outer_handle_connect(): - inner_handle_connect() - if phenny.data.get('startup.setup.pingloop'): - phenny.data['startup.setup.pingloop']() + # Start the ping loop. Has to be done after USER on e.g. quakenet + if phenny.data.get('startup.setup.pingloop'): + phenny.data['startup.setup.pingloop']() - phenny.handle_connect = outer_handle_connect + if hasattr(phenny.config, 'serverpass'): + phenny.proto.pass_(phenny.config.serverpass) -def startup(phenny, input): - import time + if hasattr(phenny.config, 'password'): + phenny.msg('NickServ', 'IDENTIFY %s' % phenny.config.password) + time.sleep(5) - if hasattr(phenny.config, 'serverpass'): - phenny.write(('PASS', phenny.config.serverpass)) - - if hasattr(phenny.config, 'password'): - phenny.msg('NickServ', 'IDENTIFY %s' % phenny.config.password) - time.sleep(5) - - # Cf. http://swhack.com/logs/2005-12-05#T19-32-36 - for channel in phenny.channels: - phenny.write(('JOIN', channel)) - time.sleep(0.5) + # Cf. http://swhack.com/logs/2005-12-05#T19-32-36 + for channel in phenny.channels: + phenny.proto.join(channel) + time.sleep(0.5) startup.rule = r'(.*)' startup.event = '251' startup.priority = 'low' if __name__ == '__main__': - print __doc__.strip() + print(__doc__.strip()) diff --git a/modules/tell.py b/modules/tell.py old mode 100755 new mode 100644 index d3ee609ed..7249fd536 --- a/modules/tell.py +++ b/modules/tell.py @@ -24,129 +24,139 @@ '#programmering', '#maxima', '#robin', '##concurrency', '#paredit' ]) def loadReminders(fn): - result = {} - f = open(fn) - for line in f: - line = line.strip() - if line: - try: tellee, teller, verb, timenow, msg = line.split('\t', 4) - except ValueError: continue # @@ hmm - result.setdefault(tellee, []).append((teller, verb, timenow, msg)) - f.close() - return result + result = {} + f = open(fn) + for line in f: + line = line.strip() + if line: + try: tellee, teller, verb, timenow, msg = line.split('\t', 4) + except ValueError: continue # @@ hmm + result.setdefault(tellee, []).append((teller, verb, timenow, msg)) + f.close() + return result def dumpReminders(fn, data): - f = open(fn, 'w') - for tellee in data.iterkeys(): - for remindon in data[tellee]: - line = '\t'.join((tellee,) + remindon) - try: f.write(line + '\n') - except IOError: break - try: f.close() - except IOError: pass - return True + f = open(fn, 'w') + for tellee in data.keys(): + for remindon in data[tellee]: + line = '\t'.join((tellee,) + remindon) + try: f.write(line + '\n') + except IOError: break + try: f.close() + except IOError: pass + return True def setup(self): - fn = self.nick + '-' + self.config.host + '.tell.db' - self.tell_filename = os.path.join(os.path.expanduser('~/.phenny'), fn) - if not os.path.exists(self.tell_filename): - try: f = open(self.tell_filename, 'w') - except OSError: pass - else: - f.write('') - f.close() - self.reminders = loadReminders(self.tell_filename) # @@ tell + fn = self.nick + '-' + self.config.host + '.tell.db' + self.tell_filename = os.path.join(os.path.expanduser('~/.phenny'), fn) + if not os.path.exists(self.tell_filename): + try: f = open(self.tell_filename, 'w') + except OSError: pass + else: + f.write('') + f.close() + self.reminders = loadReminders(self.tell_filename) # @@ tell def f_remind(phenny, input): - teller = input.nick - - # @@ Multiple comma-separated tellees? Cf. Terje, #swhack, 2006-04-15 - verb, tellee, msg = input.groups() - verb = verb.encode('utf-8') - tellee = tellee.encode('utf-8') - msg = msg.encode('utf-8') - - tellee_original = tellee.rstrip('.,:;') - tellee = tellee_original.lower() - - if not os.path.exists(phenny.tell_filename): - return - - if len(tellee) > 20: - return phenny.reply('That nickname is too long.') - - timenow = time.strftime('%d %b %H:%MZ', time.gmtime()) - if not tellee in (teller.lower(), phenny.nick, 'me'): # @@ - # @@ <deltab> and year, if necessary - warn = False - if not phenny.reminders.has_key(tellee): - phenny.reminders[tellee] = [(teller, verb, timenow, msg)] - else: - # if len(phenny.reminders[tellee]) >= maximum: - # warn = True - phenny.reminders[tellee].append((teller, verb, timenow, msg)) - # @@ Stephanie's augmentation - response = "I'll pass that on when %s is around." % tellee_original - # if warn: response += (" I'll have to use a pastebin, though, so " + - # "your message may get lost.") - - rand = random.random() - if rand > 0.9999: response = "yeah, yeah" - elif rand > 0.999: response = "yeah, sure, whatever" - - phenny.reply(response) - elif teller.lower() == tellee: - phenny.say('You can %s yourself that.' % verb) - else: phenny.say("Hey, I'm not as stupid as Monty you know!") - - dumpReminders(phenny.tell_filename, phenny.reminders) # @@ tell + teller = input.nick + + # @@ Multiple comma-separated tellees? Cf. Terje, #swhack, 2006-04-15 + verb, tellee, msg = input.groups() + verb = verb + tellee = tellee + msg = msg + + tellee_original = tellee.rstrip('.,:;') + tellee = tellee_original.lower() + + if not os.path.exists(phenny.tell_filename): + return + + if len(tellee) > 20: + return phenny.reply('That nickname is too long.') + + timenow = time.strftime('%d %b %H:%MZ', time.gmtime()) + if not tellee in (teller.lower(), phenny.nick.lower(), 'me'): # @@ + # @@ <deltab> and year, if necessary + warn = False + if tellee not in phenny.reminders: + phenny.reminders[tellee] = [(teller, verb, timenow, msg)] + else: + # if len(phenny.reminders[tellee]) >= maximum: + # warn = True + phenny.reminders[tellee].append((teller, verb, timenow, msg)) + # @@ Stephanie's augmentation + response = "I'll pass that on when %s is around." % tellee_original + # if warn: response += (" I'll have to use a pastebin, though, so " + + # "your message may get lost.") + + rand = random.random() + if rand > 0.9999: response = "yeah, yeah" + elif rand > 0.999: response = "yeah, sure, whatever" + + phenny.reply(response) + elif teller.lower() == tellee: + phenny.say('You can %s yourself that.' % verb) + else: phenny.say("Hey, I'm not as stupid as Monty you know!") + + dumpReminders(phenny.tell_filename, phenny.reminders) # @@ tell f_remind.rule = ('$nick', ['tell', 'ask'], r'(\S+) (.*)') +f_remind.thread = False def getReminders(phenny, channel, key, tellee): - lines = [] - template = "%s: %s <%s> %s %s %s" - today = time.strftime('%d %b', time.gmtime()) + lines = [] + template = "%s: %s <%s> %s %s %s" + today = time.strftime('%d %b', time.gmtime()) - for (teller, verb, datetime, msg) in phenny.reminders[key]: - if datetime.startswith(today): - datetime = datetime[len(today)+1:] - lines.append(template % (tellee, datetime, teller, verb, tellee, msg)) + for (teller, verb, datetime, msg) in phenny.reminders[key]: + if datetime.startswith(today): + datetime = datetime[len(today)+1:] + lines.append(template % (tellee, datetime, teller, verb, tellee, msg)) - try: del phenny.reminders[key] - except KeyError: phenny.msg(channel, 'Er...') - return lines + try: del phenny.reminders[key] + except KeyError: phenny.msg(channel, 'Er...') + return lines def message(phenny, input): - if not input.sender.startswith('#'): return - - tellee = input.nick - channel = input.sender - - if not os: return - if not os.path.exists(phenny.tell_filename): - return - - reminders = [] - remkeys = list(reversed(sorted(phenny.reminders.keys()))) - for remkey in remkeys: - if not remkey.endswith('*') or remkey.endswith(':'): - if tellee.lower() == remkey: + if not input.sender.startswith('#'): return + + tellee = input.nick + channel = input.sender + + if not os: return + if not os.path.exists(phenny.tell_filename): + return + + reminders = [] + remkeys = list(reversed(sorted(phenny.reminders.keys()))) + for remkey in remkeys: + if not remkey.endswith('*') or remkey.endswith(':'): + if tellee.lower() == remkey: + reminders.extend(getReminders(phenny, channel, remkey, tellee)) + elif tellee.lower().startswith(remkey.rstrip('*:')): reminders.extend(getReminders(phenny, channel, remkey, tellee)) - elif tellee.lower().startswith(remkey.rstrip('*:')): - reminders.extend(getReminders(phenny, channel, remkey, tellee)) - for line in reminders[:maximum]: - phenny.say(line) + for line in reminders[:maximum]: + phenny.say(line) - if reminders[maximum:]: - phenny.say('Further messages sent privately') - for line in reminders[maximum:]: - phenny.msg(tellee, line) + if reminders[maximum:]: + phenny.say('Further messages sent privately') + for line in reminders[maximum:]: + phenny.msg(tellee, line) - if len(phenny.reminders.keys()) != remkeys: - dumpReminders(phenny.tell_filename, phenny.reminders) # @@ tell + if len(list(phenny.reminders.keys())) != remkeys: + dumpReminders(phenny.tell_filename, phenny.reminders) # @@ tell message.rule = r'(.*)' message.priority = 'low' +message.thread = False + +def messageAlert(phenny, input): + if (input.nick.lower() in list(phenny.reminders.keys())): + phenny.say(input.nick + ': You have messages.') +messageAlert.event = 'JOIN' +messageAlert.rule = r'.*' +messageAlert.priority = 'low' +messageAlert.thread = False if __name__ == '__main__': - print __doc__.strip() + print(__doc__.strip()) diff --git a/modules/test/__init__.py b/modules/test/__init__.py new file mode 100644 index 000000000..c1f6ddb6b --- /dev/null +++ b/modules/test/__init__.py @@ -0,0 +1,3 @@ +# add current working directory to path +import sys +sys.path.append('.') diff --git a/modules/test/test_archwiki.py b/modules/test/test_archwiki.py new file mode 100644 index 000000000..d11bdcf29 --- /dev/null +++ b/modules/test/test_archwiki.py @@ -0,0 +1,77 @@ +""" +test_archwiki.py - tests for the arch wiki module +author: mutantmonkey <mutantmonkey@mutantmonkey.in> +""" +import unittest +from mock import MagicMock +from modules import archwiki +import wiki + + +class TestArchwiki(unittest.TestCase): + def setUp(self): + self.phenny = MagicMock() + self.input = MagicMock() + + self.term = None + self.section = None + + def prepare(self): + if self.section: + self.text = self.term + '#' + self.section + url_text = wiki.format_term(self.term) +\ + '#' + wiki.format_section(self.section) + else: + self.text = self.term + url_text = wiki.format_term(self.term) + + self.input.group = lambda x: [None, self.text][x] + self.url = 'https://wiki.archlinux.org/index.php/{0}'.format(url_text) + + def check_snippet(self, output): + self.assertIn(self.url, output) + + for keyword in self.keywords: + self.assertIn(keyword, output) + + def test_awik(self): + self.term = "OpenDMARC" + self.prepare() + + archwiki.awik(self.phenny, self.input) + out = self.phenny.say.call_args[0][0] + + self.keywords = ['DMARC', 'implementation', 'specification'] + self.check_snippet(out) + + def test_awik_fragment(self): + self.term = "KVM" + self.section = "Kernel support" + self.prepare() + + archwiki.awik(self.phenny, self.input) + out = self.phenny.say.call_args[0][0] + + self.keywords = ['kernel', 'modules', 'KVM', 'VIRTIO'] + self.check_snippet(out) + + def test_awik_invalid(self): + self.term = "KVM" + self.section = "Enabling KSM" + self.prepare() + + archwiki.awik(self.phenny, self.input) + out = self.phenny.say.call_args[0][0] + + message = "No '{0}' section found.".format(self.section) + self.assertEqual(out, '"{0}" - {1}'.format(message, self.url)) + + def test_awik_none(self): + self.term = "Ajgoajh" + self.prepare() + + archwiki.awik(self.phenny, self.input) + out = self.phenny.say.call_args[0][0] + + expected = "Can't find anything in the ArchWiki for \"{0}\"." + self.assertEqual(out, expected.format(self.text)) diff --git a/modules/test/test_calc.py b/modules/test/test_calc.py new file mode 100644 index 000000000..fca3d9128 --- /dev/null +++ b/modules/test/test_calc.py @@ -0,0 +1,116 @@ +# coding=utf-8 +""" +test_calc.py - tests for the calc module +author: mutantmonkey <mutantmonkey@mutantmonkey.in> +""" + +import unittest +from mock import MagicMock, Mock +from modules.calc import c + + +class TestCalc(unittest.TestCase): + def setUp(self): + self.phenny = MagicMock() + + def test_c(self): + input = Mock(group=lambda x: '5*5') + c(self.phenny, input) + + self.phenny.say.assert_called_once_with('25') + + def test_c_simplify(self): + input = Mock(group=lambda x: 'simplify 2^2+2(2)') + c(self.phenny, input) + + self.phenny.say.assert_called_once_with('8') + + def test_c_factor(self): + input = Mock(group=lambda x: 'factor x^2 + 2x') + c(self.phenny, input) + + self.phenny.say.assert_called_once_with('x (x + 2)') + + def test_c_derive(self): + input = Mock(group=lambda x: 'derive x^2+2x') + c(self.phenny, input) + + self.phenny.say.assert_called_once_with('2 x + 2') + + def test_c_integrate(self): + input = Mock(group=lambda x: 'integrate x^2+2x') + c(self.phenny, input) + + self.phenny.say.assert_called_once_with('1/3 x^3 + x^2') + + def test_c_zeroes(self): + input = Mock(group=lambda x: 'zeroes x^2+2x') + c(self.phenny, input) + + self.phenny.say.assert_called_once_with('[-2, 0]') + + def test_c_tangent(self): + input = Mock(group=lambda x: 'tangent 2|x^3') + c(self.phenny, input) + + self.phenny.say.assert_called_once_with('12 x + -16') + + def test_c_area(self): + input = Mock(group=lambda x: 'area 2:4|x^3') + c(self.phenny, input) + + self.phenny.say.assert_called_once_with('60') + + def test_c_cos(self): + input = Mock(group=lambda x: 'cos pi') + c(self.phenny, input) + + self.phenny.say.assert_called_once_with('-1') + + def test_c_sin(self): + input = Mock(group=lambda x: 'sin 0') + c(self.phenny, input) + + self.phenny.say.assert_called_once_with('0') + + def test_c_tan(self): + input = Mock(group=lambda x: 'tan .03') + c(self.phenny, input) + + self.phenny.say.assert_called_once_with('0.030009') + + def test_c_arccos(self): + input = Mock(group=lambda x: 'arccos 1') + c(self.phenny, input) + + self.phenny.say.assert_called_once_with('0') + + def test_c_arcsin(self): + input = Mock(group=lambda x: 'arcsin .04') + c(self.phenny, input) + + self.phenny.say.assert_called_once_with('0.0400107') + + def test_c_arctan(self): + input = Mock(group=lambda x: 'arctan 1') + c(self.phenny, input) + + self.phenny.say.assert_called_once_with('1/2 pi') + + def test_c_abs(self): + input = Mock(group=lambda x: 'abs -3') + c(self.phenny, input) + + self.phenny.say.assert_called_once_with('3') + + def test_c_log(self): + input = Mock(group=lambda x: 'log 2|8') + c(self.phenny, input) + + self.phenny.say.assert_called_once_with('3') + + def test_c_none(self): + input = Mock(group=lambda x: 'tangent 2lx^3') + c(self.phenny, input) + + self.phenny.reply.assert_called_once_with('Sorry, no result.') diff --git a/modules/test/test_clock.py b/modules/test/test_clock.py new file mode 100644 index 000000000..686d21aa7 --- /dev/null +++ b/modules/test/test_clock.py @@ -0,0 +1,75 @@ +""" +test_clock.py - tests for the clock module +author: mutantmonkey <mutantmonkey@mutantmonkey.in> +""" + +import re +import datetime +import unittest +from mock import MagicMock, Mock, patch +from modules.clock import f_time, beats, yi, tock, npl + + +class TestClock(unittest.TestCase): + def setUp(self): + self.phenny = MagicMock() + + @patch('time.time') + def test_time(self, mock_time): + mock_time.return_value = 1338674651 + input = Mock(group=lambda x: 'EDT') + f_time(self.phenny, input) + + self.phenny.msg.called_once_with('#phenny', + "Sat, 02 Jun 2012 18:04:11 EDT") + + @patch('time.time') + def test_beats_zero(self, mock_time): + mock_time.return_value = 0 + beats(self.phenny, None) + + self.phenny.say.assert_called_with('@041') + + @patch('time.time') + def test_beats_normal(self, mock_time): + mock_time.return_value = 369182 + beats(self.phenny, None) + + self.phenny.say.assert_called_with('@314') + + @patch('time.time') + def test_yi_normal(self, mock_time): + mock_time.return_value = 369182 + yi(self.phenny, None) + + self.phenny.say.assert_called_with('Not yet...') + + @patch('time.time') + def test_yi_soon(self, mock_time): + mock_time.return_value = 1339419000 + yi(self.phenny, None) + + self.phenny.say.assert_called_with('Soon...') + + @patch('time.time') + def test_yi_now(self, mock_time): + mock_time.return_value = 1339419650 + yi(self.phenny, None) + + self.phenny.say.assert_called_with('Yes! PARTAI!') + + def test_tock(self): + tock(self.phenny, None) + + out = self.phenny.say.call_args[0][0] + m = re.match('^.* - tycho.usno.navy.mil$', + out, flags=re.UNICODE) + self.assertTrue(m) + + def test_npl(self): + npl(self.phenny, None) + + out = self.phenny.say.call_args[0][0] + m = re.match('^.* - ntp1.npl.co.uk$', + out, flags=re.UNICODE) + self.assertTrue(m) diff --git a/modules/test/test_commit.py b/modules/test/test_commit.py new file mode 100644 index 000000000..bb79f5710 --- /dev/null +++ b/modules/test/test_commit.py @@ -0,0 +1,17 @@ +""" +test_commit.py - tests for the what the commit module +author: mutantmonkey <mutantmonkey@mutantmonkey.in> +""" + +import unittest +from mock import MagicMock +from modules.commit import commit + + +class TestCommit(unittest.TestCase): + def setUp(self): + self.phenny = MagicMock() + + def test_commit(self): + commit(self.phenny, None) + assert self.phenny.reply.called is True diff --git a/modules/test/test_fcc.py b/modules/test/test_fcc.py new file mode 100644 index 000000000..0086cd9f9 --- /dev/null +++ b/modules/test/test_fcc.py @@ -0,0 +1,34 @@ +""" +test_fcc.py - tests for the fcc callsign lookup module +author: mutantmonkey <mutantmonkey@mutantmonkey.in> +""" + +import unittest +from mock import MagicMock, Mock +from modules.fcc import fcc + + +class TestFcc(unittest.TestCase): + def setUp(self): + self.phenny = MagicMock() + + def test_result(self): + callsign = 'KK4EWT' + ham = 'JAMES B WILLIAMS' + key = 3326562 + + input = Mock(group=lambda x: 'KK4EWT') + fcc(self.phenny, input) + + self.phenny.say.assert_called_once_with('{0} - {1} - '\ + 'http://wireless2.fcc.gov/UlsApp/UlsSearch/license.jsp?licKey={2}' + .format(callsign, ham, key)) + + def test_none(self): + callsign = 'XFOOBAR' + + input = Mock(group=lambda x: callsign) + fcc(self.phenny, input) + + self.phenny.reply.assert_called_once_with('No results found for '\ + '{0}'.format(callsign)) diff --git a/modules/test/test_head.py b/modules/test/test_head.py new file mode 100644 index 000000000..db596e0da --- /dev/null +++ b/modules/test/test_head.py @@ -0,0 +1,60 @@ +""" +test_head.py - tests for the HTTP metadata utilities module +author: mutantmonkey <mutantmonkey@mutantmonkey.in> +""" + +import re +import unittest +from mock import MagicMock, Mock +from modules.head import head, snarfuri + +class TestHead(unittest.TestCase): + def setUp(self): + self.phenny = MagicMock() + + def test_head(self): + input = Mock(group=lambda x: 'https://vtluug.org') + head(self.phenny, input) + + out = self.phenny.reply.call_args[0][0] + m = re.match('^200, text/html, utf-8, \d{4}\-\d{2}\-\d{2} '\ + '\d{2}:\d{2}:\d{2} UTC, [0-9]+ bytes, [0-9]+.[0-9]+ s$', out, flags=re.UNICODE) + self.assertTrue(m) + + def test_head_404(self): + input = Mock(group=lambda x: 'https://vtluug.org/trigger_404') + head(self.phenny, input) + + out = self.phenny.say.call_args[0][0] + self.assertEqual(out, '404') + + def test_header(self): + input = Mock(group=lambda x: 'https://vtluug.org Server') + head(self.phenny, input) + + self.phenny.say.assert_called_once_with("Server: nginx") + + def test_header_bad(self): + input = Mock(group=lambda x: 'https://vtluug.org truncatedcone') + head(self.phenny, input) + + self.phenny.say.assert_called_once_with("There was no truncatedcone "\ + "header in the response.") + + def test_snarfuri(self): + self.phenny.config.prefix = '.' + self.phenny.config.linx_api_key = "" + input = Mock(group=lambda x=0: 'https://www.google.com', + sender='#phenny') + snarfuri(self.phenny, input) + + self.phenny.msg.assert_called_once_with('#phenny', "[ Google ]") + + def test_snarfuri_405(self): + self.phenny.config.prefix = '.' + self.phenny.config.linx_api_key = "" + input = Mock(group=lambda x=0: 'http://ozuma.sakura.ne.jp/httpstatus/405', + sender='#phenny') + snarfuri(self.phenny, input) + + self.assertEqual(self.phenny.msg.called, False) diff --git a/modules/test/test_hs.py b/modules/test/test_hs.py new file mode 100644 index 000000000..c305fdfd9 --- /dev/null +++ b/modules/test/test_hs.py @@ -0,0 +1,55 @@ +""" +test_hs.py - tests for the hokie stalker module +author: mutantmonkey <mutantmonkey@mutantmonkey.in> +""" + +import re +import unittest +from mock import MagicMock, Mock +from modules.hs import search, hs + + +class TestHs(unittest.TestCase): + def setUp(self): + self.phenny = MagicMock() + + def test_search(self): + data = search('john') + + assert len(data) >= 1 + self.assertIn('uid', data[0]) + self.assertIn('cn', data[0]) + + def test_single(self): + input = Mock(group=lambda x: 'marchany') + hs(self.phenny, input) + + pattern = re.compile( + '^.* - http://search\.vt\.edu/search/person\.html\?person=\d+$', + flags=re.UNICODE) + out = self.phenny.reply.call_args[0][0] + self.assertRegex(out, pattern) + + def test_multi(self): + input = Mock(group=lambda x: 'john') + hs(self.phenny, input) + + pattern = re.compile( + '^Multiple results found; try http://search\.vt\.edu/search/people\.html\?q=.*$', + flags=re.UNICODE) + out = self.phenny.reply.call_args[0][0] + self.assertRegex(out, pattern) + + def test_2char(self): + input = Mock(group=lambda x: 'hs') + hs(self.phenny, input) + + out = self.phenny.reply.call_args[0][0] + self.phenny.reply.assert_called_once_with("No results found") + + def test_none(self): + input = Mock(group=lambda x: 'THIS_IS_NOT_A_REAL_SEARCH_QUERY') + hs(self.phenny, input) + + out = self.phenny.reply.call_args[0][0] + self.phenny.reply.assert_called_once_with("No results found") diff --git a/modules/test/test_imdb.py b/modules/test/test_imdb.py new file mode 100644 index 000000000..234f1e035 --- /dev/null +++ b/modules/test/test_imdb.py @@ -0,0 +1,39 @@ +""" +test_imdb.py - tests for the imdb module +author: mutantmonkey <mutantmonkey@mutantmonkey.in> +""" + +import re +import unittest +from mock import MagicMock, Mock +from modules.imdb import imdb_search, imdb + + +class TestImdb(unittest.TestCase): + def setUp(self): + self.phenny = MagicMock() + + def test_imdb_search(self): + data = imdb_search('Hackers') + + assert 'Plot' in data + assert 'Title' in data + assert 'Year' in data + assert 'imdbID' in data + + def test_imdb(self): + input = Mock(group=lambda x: 'Antitrust') + imdb(self.phenny, input) + + out = self.phenny.say.call_args[0][0] + pattern = re.compile( + r'^.* \(.*\): .* http://imdb.com/title/[a-z\d]+$', + flags=re.UNICODE) + self.assertRegex(out, pattern) + + def test_imdb_none(self): + input = Mock(group=lambda x: None) + imdb(self.phenny, input) + + self.phenny.say.assert_called_once_with( + ".imdb what?") diff --git a/modules/test/test_lastfm.py b/modules/test/test_lastfm.py new file mode 100644 index 000000000..2dbe1d345 --- /dev/null +++ b/modules/test/test_lastfm.py @@ -0,0 +1,34 @@ +""" +test_lastfm.py - tests for the lastfm module +author: mutantmonkey <mutantmonkey@mutantmonkey.in> +""" + +import re +import unittest +from mock import MagicMock, Mock +from modules.lastfm import now_playing + + +class TestLastfm(unittest.TestCase): + user1 = 'test' + user2 = 'telnoratti' + + def setUp(self): + self.phenny = MagicMock() + + def test_now_playing(self): + input = Mock(group=lambda x: self.user1) + now_playing(self.phenny, input) + + out = self.phenny.say.call_args[0][0] + m = re.match('^{0} listened to ".+" by .+ on .+ .*$'.format(self.user1), out, flags=re.UNICODE) + self.assertTrue(m) + + def test_now_playing_sender(self): + input = Mock(group=lambda x: '') + input.nick = self.user1 + now_playing(self.phenny, input) + + out = self.phenny.say.call_args[0][0] + m = re.match('^{0} listened to ".+" by .+ on .+ .*$'.format(self.user1), out, flags=re.UNICODE) + self.assertTrue(m) diff --git a/modules/test/test_mylife.py b/modules/test/test_mylife.py new file mode 100644 index 000000000..69c7bc56b --- /dev/null +++ b/modules/test/test_mylife.py @@ -0,0 +1,17 @@ +""" +test_mylife.py - tests for the mylife module +author: mutantmonkey <mutantmonkey@mutantmonkey.in> +""" + +import unittest +from mock import MagicMock +from modules import mylife + + +class TestMylife(unittest.TestCase): + def setUp(self): + self.phenny = MagicMock() + + def test_fml(self): + mylife.fml(self.phenny, None) + assert self.phenny.say.called is True diff --git a/modules/test/test_nsfw.py b/modules/test/test_nsfw.py new file mode 100644 index 000000000..ed6bff77d --- /dev/null +++ b/modules/test/test_nsfw.py @@ -0,0 +1,26 @@ +""" +test_nsfw.py - some things just aren't safe for work, the test cases +author: mutantmonkey <mutantmonkey@mutantmonkey.in> +""" + +import re +import unittest +from mock import MagicMock, Mock +from modules.nsfw import nsfw + + +class TestNsfw(unittest.TestCase): + def setUp(self): + self.phenny = MagicMock() + + def test_nsfw(self): + input = Mock(group=lambda x: "test") + nsfw(self.phenny, input) + self.phenny.say.assert_called_once_with( + "!!NSFW!! -> test <- !!NSFW!!") + + def test_nsfw_none(self): + input = Mock(group=lambda x: None) + nsfw(self.phenny, input) + self.phenny.say.assert_called_once_with( + ".nsfw <link> - for when a link isn't safe for work") diff --git a/modules/test/test_remind.py b/modules/test/test_remind.py new file mode 100644 index 000000000..dca7e6cc2 --- /dev/null +++ b/modules/test/test_remind.py @@ -0,0 +1,49 @@ +""" +test_remind.py - tests for the remind module +author: mutantmonkey <mutantmonkey@mutantmonkey.in> +""" + +import re +import unittest +import threading +import time +import tools +from mock import MagicMock, Mock, patch +from modules import remind + + +class TestRemind(unittest.TestCase): + def setUp(self): + self.phenny = MagicMock() + self.phenny.nick = 'phenny' + self.phenny.config.host = 'test-phenny.example.com' + + remind.load_database = lambda name: {} + remind.dump_database = lambda name, data: name + remind.setup(self.phenny) + + def test_remind(self): + secs = 5 + input = Mock(sender='#testsworth', nick='Testsworth', + bytes='.in {0} seconds TEST REMIND'.format(secs)) + + remind.remind(self.phenny, input) + self.phenny.reply.assert_called_once_with("Okay, will remind in {0}"\ + " secs".format(secs)) + + time.sleep(secs + 1) + self.phenny.msg.assert_called_once_with(input.sender, + input.nick + ': TEST REMIND') + + def test_remind_nomsg(self): + secs = 5 + input = Mock(sender='#testsworth', nick='Testsworth', + bytes='.in {0} seconds'.format(secs)) + + remind.remind(self.phenny, input) + self.phenny.reply.assert_called_once_with("Okay, will remind in {0}"\ + " secs".format(secs)) + + time.sleep(secs + 1) + self.phenny.msg.assert_called_once_with(input.sender, + input.nick + '!') diff --git a/modules/test/test_rule34.py b/modules/test/test_rule34.py new file mode 100644 index 000000000..3d287957a --- /dev/null +++ b/modules/test/test_rule34.py @@ -0,0 +1,29 @@ +""" +test_rule34.py - tests for the rule 34 module +author: mutantmonkey <mutantmonkey@mutantmonkey.in> +""" + +import re +import unittest +from mock import MagicMock, Mock +from modules.rule34 import rule34 + + +class TestRule34(unittest.TestCase): + def setUp(self): + self.phenny = MagicMock() + + def test_result(self): + input = Mock(group=lambda x: 'python') + rule34(self.phenny, input) + + out = self.phenny.reply.call_args[0][0] + m = re.match('^!!NSFW!! -> http://rule34\.xxx/.* <- !!NSFW!!$', out, + flags=re.UNICODE) + self.assertTrue(m) + def test_none(self): + input = Mock(group=lambda x: '__no_results_for_this__') + rule34(self.phenny, input) + + self.phenny.reply.assert_called_once_with( + "You just broke Rule 34! Better start uploading...") diff --git a/modules/test/test_search.py b/modules/test/test_search.py new file mode 100644 index 000000000..bd13cd0e9 --- /dev/null +++ b/modules/test/test_search.py @@ -0,0 +1,77 @@ +""" +test_search.py - tests for the search module +author: mutantmonkey <mutantmonkey@mutantmonkey.in> +""" + +import re +import unittest +from mock import MagicMock, Mock +from modules.search import duck_api, google_search, google_count, \ + formatnumber, g, gc, gcs, bing_search, bing, duck_search, duck, \ + search + + +class TestSearch(unittest.TestCase): + def setUp(self): + self.phenny = MagicMock() + + def test_google_search(self): + out = google_search('phenny') + + m = re.match('^https?://.*$', out, flags=re.UNICODE) + self.assertTrue(m) + + def test_g(self): + input = Mock(group=lambda x: 'swhack') + g(self.phenny, input) + + assert self.phenny.reply.called is True + + def test_gc(self): + query = 'extrapolate' + input = Mock(group=lambda x: query) + gc(self.phenny, input) + + out = self.phenny.say.call_args[0][0] + m = re.match('^{0}: [0-9,\.]+$'.format(query), out, flags=re.UNICODE) + self.assertTrue(m) + + def test_gcs(self): + input = Mock(group=lambda x: 'vtluug virginia phenny') + gcs(self.phenny, input) + + assert self.phenny.say.called is True + + def test_bing_search(self): + out = bing_search('phenny') + + m = re.match('^https?://.*$', out, flags=re.UNICODE) + self.assertTrue(m) + + def test_bing(self): + input = Mock(group=lambda x: 'swhack') + bing(self.phenny, input) + + assert self.phenny.reply.called is True + + def test_duck_search(self): + out = duck_search('phenny') + + m = re.match('^https?://.*$', out, flags=re.UNICODE) + self.assertTrue(m) + + def test_duck(self): + input = Mock(group=lambda x: 'swhack') + duck(self.phenny, input) + + assert self.phenny.reply.called is True + + def test_duck_api(self): + input = Mock(group=lambda x: 'swhack') + duck(self.phenny, input) + + def test_search(self): + input = Mock(group=lambda x: 'vtluug') + duck(self.phenny, input) + + assert self.phenny.reply.called is True diff --git a/modules/test/test_slogan.py b/modules/test/test_slogan.py new file mode 100644 index 000000000..fa5b4467d --- /dev/null +++ b/modules/test/test_slogan.py @@ -0,0 +1,30 @@ +""" +test_slogan.py - tests for the slogan module +author: mutantmonkey <mutantmonkey@mutantmonkey.in> +""" + +import unittest +from mock import MagicMock, Mock +from modules.slogan import sloganize, slogan + + +class TestSlogan(unittest.TestCase): + def setUp(self): + self.phenny = MagicMock() + + def test_sloganize(self): + out = sloganize('slogan') + self.assertRegex(out, ".*slogan.*") + + def test_slogan(self): + input = Mock(group=lambda x: 'slogan') + slogan(self.phenny, input) + + out = self.phenny.say.call_args[0][0] + self.assertRegex(out, ".*slogan.*") + + def test_slogan_none(self): + input = Mock(group=lambda x: None) + slogan(self.phenny, input) + self.phenny.say.assert_called_once_with( + "You need to specify a word; try .slogan Granola") diff --git a/modules/test/test_tfw.py b/modules/test/test_tfw.py new file mode 100644 index 000000000..b83c69559 --- /dev/null +++ b/modules/test/test_tfw.py @@ -0,0 +1,72 @@ +# -*- coding: utf-8 -*- +""" +test_tfw.py - tests for the fucking weather module +author: mutantmonkey <mutantmonkey@mutantmonkey.in> +""" + +import re +import unittest +import tools +from mock import MagicMock, Mock +from modules import tfw + + +class TestTfw(unittest.TestCase): + def setUp(self): + self.phenny = MagicMock() + + def test_badloc(self): + input = Mock(group=lambda x: 'tu3jgoajgoahghqog') + tfw.tfw(self.phenny, input) + + self.phenny.say.assert_called_once_with( + "WHERE THE FUCK IS THAT? I guess you might think it's a place, but no one else does. Try again.") + + def test_celsius(self): + input = Mock(group=lambda x: '24060') + tfw.tfw(self.phenny, input, celsius=True) + + out = self.phenny.say.call_args[0][0] + m = re.match('^[\-\d]+°C‽ .* \- .* \- [A-Z]{4} \d{2}:\d{2}Z$', out, + flags=re.UNICODE) + self.assertTrue(m) + + def test_fahrenheit(self): + input = Mock(group=lambda x: '24060') + tfw.tfw(self.phenny, input, fahrenheit=True) + + out = self.phenny.say.call_args[0][0] + m = re.match('^[\-\d]+°F‽ .* \- .* \- [A-Z]{4} \d{2}:\d{2}Z$', out, + flags=re.UNICODE) + self.assertTrue(m) + + def test_mev(self): + input = Mock(group=lambda x: '24060') + tfw.tfwev(self.phenny, input) + + out = self.phenny.say.call_args[0][0] + m = re.match('^[\-\d\.]+ meV‽ .* \- .* \- [A-Z]{4} \d{2}:\d{2}Z$', out, + flags=re.UNICODE) + self.assertTrue(m) + + def test_meter(self): + input = Mock(group=lambda x: '24060') + tfw.tfw(self.phenny, input) + + out = self.phenny.say.call_args[0][0] + m = re.match('^[\-\d\.]+ Meters‽ .* \- .* \- [A-Z]{4} \d{2}:\d{2}Z$', out, + flags=re.UNICODE) + self.assertTrue(m) + + def test_sexy_time(self): + input = Mock(group=lambda x: 'KBCB') + tfw.web = MagicMock() + tfw.metar.parse = lambda x: Mock(temperature=21) + tfw.tfwf(self.phenny, input) + + out = self.phenny.say.call_args[0][0] + m = re.match( + r'^69°F‽ IT\'S FUCKING SEXY TIME \- .*', + out, + flags=re.UNICODE) + self.assertTrue(m) diff --git a/modules/test/test_urbandict.py b/modules/test/test_urbandict.py new file mode 100644 index 000000000..ea4d54253 --- /dev/null +++ b/modules/test/test_urbandict.py @@ -0,0 +1,33 @@ +""" +test_urbandict.py - tests for the urban dictionary module +author: mutantmonkey <mutantmonkey@mutantmonkey.in> +""" + +import re +import unittest +from mock import MagicMock, Mock +from modules.urbandict import urbandict + + +class TestUrbandict(unittest.TestCase): + def setUp(self): + self.phenny = MagicMock() + + def test_result(self): + word = 'slemp' + input = Mock(group=lambda x: word) + urbandict(self.phenny, input) + + out = self.phenny.say.call_args[0][0] + m = re.match('^.* - '\ + 'http://www\.urbandictionary\.com/define\.php\?term=.*$', out, + flags=re.UNICODE) + self.assertTrue(m) + + def test_none(self): + word = '__no_word_here__' + input = Mock(group=lambda x: word) + urbandict(self.phenny, input) + + self.phenny.say.assert_called_once_with('No results found for '\ + '{0}'.format(word)) diff --git a/modules/test/test_vtluugwiki.py b/modules/test/test_vtluugwiki.py new file mode 100644 index 000000000..5486176ba --- /dev/null +++ b/modules/test/test_vtluugwiki.py @@ -0,0 +1,78 @@ +""" +test_vtluugwiki.py - tests for the VTLUUG wiki module +author: mutantmonkey <mutantmonkey@mutantmonkey.in> +""" +import unittest +from mock import MagicMock +from modules import vtluugwiki +import wiki + + +class TestVtluugwiki(unittest.TestCase): + def setUp(self): + self.phenny = MagicMock() + self.input = MagicMock() + + self.term = None + self.section = None + + def prepare(self): + if self.section: + self.text = self.term + '#' + self.section + url_text = wiki.format_term(self.term) +\ + '#' + wiki.format_section(self.section) + else: + self.text = self.term + url_text = wiki.format_term(self.term) + + self.input.groups.return_value = [None, self.text] + self.url = 'https://vtluug.org/wiki/{0}'.format(url_text) + + def check_snippet(self, output): + self.assertIn(self.url, output) + + for keyword in self.keywords: + self.assertIn(keyword, output) + + def test_vtluug(self): + self.term = "VT-Wireless" + self.prepare() + + vtluugwiki.vtluug(self.phenny, self.input) + out = self.phenny.say.call_args[0][0] + + self.keywords = ['campus', 'wireless', 'networks'] + self.check_snippet(out) + + def test_vtluug_fragment(self): + self.term = "EAP-TLS" + self.section = "netctl" + self.prepare() + + vtluugwiki.vtluug(self.phenny, self.input) + out = self.phenny.say.call_args[0][0] + + self.keywords = ['Arch', 'Linux', 'netctl'] + self.check_snippet(out) + + def test_vtluug_invalid(self): + self.term = "EAP-TLS" + self.section = "netcfg" + self.prepare() + + vtluugwiki.vtluug(self.phenny, self.input) + out = self.phenny.say.call_args[0][0] + + message = "No '{0}' section found.".format(self.section) + self.assertEqual(out, '"{0}" - {1}'.format(message, self.url)) + + def test_vtluug_none(self): + self.term = "Ajgoajh" + self.prepare() + + vtluugwiki.vtluug(self.phenny, self.input) + out = self.phenny.say.call_args[0][0] + + expected = "Can't find anything in the VTLUUG Wiki for \"{0}\"." + self.assertEqual(out, expected.format(self.text)) + diff --git a/modules/test/test_wadsworth.py b/modules/test/test_wadsworth.py new file mode 100644 index 000000000..d9c82d640 --- /dev/null +++ b/modules/test/test_wadsworth.py @@ -0,0 +1,21 @@ +""" +test_nsfw.py - some things just aren't safe for work, the test cases +author: mutantmonkey <mutantmonkey@mutantmonkey.in> +""" + +import re +import unittest +from mock import MagicMock, Mock +from modules.wadsworth import wadsworth + + +class TestWadsworth(unittest.TestCase): + def setUp(self): + self.phenny = MagicMock() + + def test_wadsworth(self): + input = Mock(group=lambda x: "Apply Wadsworth's Constant to a string") + wadsworth(self.phenny, input) + + self.phenny.say.assert_called_once_with( + "Constant to a string") diff --git a/modules/test/test_weather.py b/modules/test/test_weather.py new file mode 100644 index 000000000..4a19ed5f5 --- /dev/null +++ b/modules/test/test_weather.py @@ -0,0 +1,68 @@ +""" +test_weather.py - tests for the weather module +author: mutantmonkey <mutantmonkey@mutantmonkey.in> +""" + +import re +import unittest +from mock import MagicMock, Mock, patch +from modules import weather + + +class TestWeather(unittest.TestCase): + def setUp(self): + self.phenny = MagicMock() + + def test_locations(self): + def check_location(result, expected): + self.assertAlmostEqual(result[0], expected[0], places=1) + self.assertAlmostEqual(result[1], expected[1], places=1) + + locations = [ + ('92121', (32.9, -117.2)), + ('94110', (37.8, -122.4)), + ('94041', (37.4, -122.1)), + ('27959', (36.0, -75.6)), + ('48067', (42.5, -83.1)), + ('23606', (37.1, -76.5)), + ('23113', (37.5, -77.6)), + ('27517', (42.6, -7.8)), + ('15213', (40.4, -80.0)), + ('90210', (34.1, -118.3)), + ('33109', (25.8, -80.1)), + ('80201', (22.6, 120.3)), + + ("Berlin", (52.5, 13.4)), + ("Paris", (48.9, 2.4)), + ("Vilnius", (54.7, 25.3)), + + ('Blacksburg, VA', (37.2, -80.4)), + ('Granger, IN', (41.8, -86.1)), + ] + + for query, expected in locations: + result = weather.location(query) + check_location(result, expected) + + def test_code_94110(self): + icao = weather.code(self.phenny, '94110') + self.assertEqual(icao, 'KSFO') + + def test_airport(self): + input = Mock(group=lambda x: 'KIAD') + weather.f_weather(self.phenny, input) + + assert self.phenny.say.called is True + + def test_place(self): + input = Mock(group=lambda x: 'Blacksburg') + weather.f_weather(self.phenny, input) + + assert self.phenny.say.called is True + + def test_notfound(self): + input = Mock(group=lambda x: 'Hell') + weather.f_weather(self.phenny, input) + + self.phenny.say.called_once_with('#phenny', + "No NOAA data available for that location.") diff --git a/modules/test/test_wikipedia.py b/modules/test/test_wikipedia.py new file mode 100644 index 000000000..a2f050548 --- /dev/null +++ b/modules/test/test_wikipedia.py @@ -0,0 +1,77 @@ +""" +test_wikipedia.py - tests for the wikipedia module +author: mutantmonkey <mutantmonkey@mutantmonkey.in> +""" +import unittest +from mock import MagicMock +from modules import wikipedia +import wiki + + +class TestWikipedia(unittest.TestCase): + def setUp(self): + self.phenny = MagicMock() + self.input = MagicMock() + + self.term = None + self.section = None + + def prepare(self): + if self.section: + self.text = self.term + '#' + self.section + url_text = wiki.format_term(self.term) +\ + '#' + wiki.format_section(self.section) + else: + self.text = self.term + url_text = wiki.format_term(self.term) + + self.input.groups.return_value = [None, self.text] + self.url = 'https://en.wikipedia.org/wiki/{0}'.format(url_text) + + def check_snippet(self, output): + self.assertIn(self.url, output) + + for keyword in self.keywords: + self.assertIn(keyword, output) + + def test_wik(self): + self.term = "Human back" + self.prepare() + + wikipedia.wik(self.phenny, self.input) + out = self.phenny.say.call_args[0][0] + + self.keywords = ['human', 'back', 'body', 'buttocks', 'neck'] + self.check_snippet(out) + + def test_wik_fragment(self): + self.term = "New York City" + self.section = "Climate" + self.prepare() + + wikipedia.wik(self.phenny, self.input) + out = self.phenny.say.call_args[0][0] + + self.keywords = ['New York', 'climate', 'humid', 'subtropical'] + self.check_snippet(out) + + def test_wik_invalid(self): + self.term = "New York City" + self.section = "Physics" + self.prepare() + + wikipedia.wik(self.phenny, self.input) + out = self.phenny.say.call_args[0][0] + + message = "No '{0}' section found.".format(self.section) + self.assertEqual(out, '"{0}" - {1}'.format(message, self.url)) + + def test_wik_none(self): + self.term = "Ajgoajh" + self.prepare() + + wikipedia.wik(self.phenny, self.input) + out = self.phenny.say.call_args[0][0] + + expected = "Can't find anything in Wikipedia for \"{0}\"." + self.assertEqual(out, expected.format(self.text)) diff --git a/modules/test/test_wiktionary.py b/modules/test/test_wiktionary.py new file mode 100644 index 000000000..7dd54c936 --- /dev/null +++ b/modules/test/test_wiktionary.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +""" +test_wiktionary.py - tests for the wiktionary module +author: mutantmonkey <mutantmonkey@mutantmonkey.in> +""" + +import re +import unittest +from mock import MagicMock, Mock +from modules import wiktionary + + +class TestWiktionary(unittest.TestCase): + def setUp(self): + self.phenny = MagicMock() + + def test_wiktionary(self): + w = wiktionary.wiktionary('test') + + assert len(w[1]) > 0 + + def test_wiktionary_none(self): + w = wiktionary.wiktionary('Hell!') + + assert len(w[0]) == 0 + assert len(w[1]) == 0 + + def test_w(self): + input = Mock(group=lambda x: 'test') + wiktionary.w(self.phenny, input) + + out = self.phenny.say.call_args[0][0] + m = re.match('^test — noun: .*$', out, flags=re.UNICODE) + self.assertTrue(m) + + def test_w_none(self): + word = 'Hell!' + input = Mock(group=lambda x: word) + wiktionary.w(self.phenny, input) + + self.phenny.say.assert_called_once_with( + "Couldn't get any definitions for {0}.".format(word)) diff --git a/modules/test/test_wuvt.py b/modules/test/test_wuvt.py new file mode 100644 index 000000000..9968f3040 --- /dev/null +++ b/modules/test/test_wuvt.py @@ -0,0 +1,21 @@ +""" +test_wuvt.py - tests for the wuvt module +author: mutantmonkey <mutantmonkey@mutantmonkey.in> +""" + +import re +import unittest +from mock import MagicMock +from modules.wuvt import wuvt + +class TestWuvt(unittest.TestCase): + def setUp(self): + self.phenny = MagicMock() + + def test_wuvt(self): + wuvt(self.phenny, None) + + out = self.phenny.say.call_args[0][0] + m = re.match('^.* is currently playing .* by .*$', out, + flags=re.UNICODE) + self.assertTrue(m) diff --git a/modules/tfw.py b/modules/tfw.py new file mode 100644 index 000000000..45fa3d32d --- /dev/null +++ b/modules/tfw.py @@ -0,0 +1,288 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +""" +tfw.py - the fucking weather module +mostly remarks and flavors from thefuckingweather.com +http://thefuckingweather.com/HelpThisGuy.aspx + +author: mutantmonkey <mutantmonkey@mutantmonkey.in> +""" + +from tools import GrumbleError +from modules import weather +import random +import metar +import web + + +def tfw(phenny, input, fahrenheit=False, celsius=False, mev=False): + """.tfw <city/zip> - Show the fucking weather at the specified location.""" + + where = input.group(2) + if not where: + # default to Blacksburg, VA + icao_code = "KBCB" + else: + icao_code = weather.code(phenny, where) + + if not icao_code: + phenny.say("WHERE THE FUCK IS THAT? I guess you might think it's a place, but no one else does. Try again.") + return + + uri = 'http://tgftp.nws.noaa.gov/data/observations/metar/stations/%s.TXT' + try: + bytes = web.get(uri % icao_code) + except AttributeError: + raise GrumbleError("THE INTERNET IS FUCKING BROKEN. Please try again later.") + except web.HTTPError: + phenny.say("WHERE THE FUCK IS THAT? I guess you might think it's a place, but no one else does. Try again.") + return + + if 'Not Found' in bytes: + phenny.say("WHERE THE FUCK IS THAT? I guess you might think it's a place, but no one else does. Try again.") + return + + w = metar.parse(bytes) + tempf = w.temperature * 9 / 5 + 32 + + # add units and convert if necessary + if fahrenheit: + temp = "{0:d}°F‽".format(int(tempf)) + elif celsius: + temp = "{0:d}°C‽".format(w.temperature) + else: + tempev = (w.temperature + 273.15) * 8.6173324e-5 / 2 + if mev: + temp = "{0:f} meV‽".format(tempev * 1000) + else: + temp = "{0:f} Meters‽".format(tempev * 12.39842) + + if w.temperature < 6: + remark = "IT'S FUCKING COLD" + flavors = [ + "Where's the cat? Oh shit. Fluffy's frozen.", + "Nothing a few shots couldn't fix", + "Should have gone south", + "You think this is cold? Have you been to upstate New York?", + "Why do I live here?", + "wang icicles.", + "Freezing my balls off out here", + "Fuck this place.", + "GREAT! If you're a penguin.", + "Fresh off the tap.", + "Fantastic do-nothing weather.", + "Put on some fucking socks.", + "Blue balls x 2", + "Good news, food won't spoil nearly as fast outside. Bad news, who cares?", + "Really?", + "Wear a fucking jacket.", + "I hear Siberia is the same this time of year.", + "NOT FUCKING JOGGING WEATHER", + "Shrinkage's best friend.", + "Warmer than Hoth.", + "Good baby making weather.", + "Where's a Tauntaun when you need one?", + "My nipples could cut glass", + "Global Warming? Bullshit.", + "Call your local travel agency and ask them if they're serious.", + "Freezing my balls off IN here", + "I'm not sure how you can stand it", + "I'm sorry.", + "Even penguins are wearing jackets.", + "Keep track of your local old people.", + "WHAT THE FUCK DO YOU MEAN IT'S NICER IN ALASKA?", + "Sock warmers are go. Everywhere.", + "Why does my car feel like a pair of ice skates?", + "Actually, a sharp-stick in the eye might not all be that bad right now.", + "THO Season.", + "It's a tit-bit nipplie.", + "Anything wooden will make a good fireplace. Thank us later.", + "MOVE THE FUCK ON GOLDILOCKS", + "I'm defrosting inside of my freezer.", + "It's time for a vacation.", + "It's bone chilling cold out. Sorry ladies." + ] + elif w.temperature < 20: + remark = "IT'S FUCKING...ALRIGHT" + flavors = [ + "Might as well rain, I'm not going out in that.", + "Better than a sharp stick in the eye.", + "Everything's nice butter weather!", + "At least you aren't living in a small town in Alaska", + "It could be worse.", + "FUCKING NOTHING TO SEE HERE", + "Listen, weather. We need to have a talk.", + "OH NO. THE WEATHER MACHINE IS BROKEN.", + "An Eskimo would beat your ass to be here", + "Where life is mediocre", + "Can't complain about today, but I want to!", + "Maybe inviting the inlaws over will improve today.", + "Let's go to the beach! In three months when it's nice again...", + "From inside it looks nice out.", "WHAT THE FUCK EVER", + "I love keeping the heat on for this long.", + "Inside or outside? Either way it's still today.", + "It's either only going to get better or worse from here!", + "If it's raining cats and dogs, hope you're not a pet person.", + "Today makes warm showers way nicer.", + "Here's to making your blankets feel useful.", + "I've seen better days", + "Compared to how awful it's been this is great!", + "If we go running maybe we won't notice.", + "Is that the sun outside? Why isn't it doing anything?", + "Well, at least we're not in prison.", + "Slap me around and call me Sally. It'd be an improvement.", + "Today is the perfect size, really honey.", + "It's that kind of day where you want zip off pants, until you realize how much of a jackass you look like in them.", + "Maybe Jersey Shore is on tonight.", + "Praise \"Bob\"!", + "Or kill me.", + "This statement is false.", + "Lies and slander, sire!" + ] + elif w.temperature < 27: + remark = "IT'S FUCKING NICE" + flavors = [ + "I made today breakfast in bed.", + "FUCKING SWEET", + "Quit your bitching", + "Enjoy.", + "IT'S ABOUT FUCKING TIME", + "READ A FUCKIN' BOOK", + "LETS HAVE A FUCKING PICNIC", + "It is safe to take your ball-mittens off.", + "More please.", + "uh, can we trade?", + "I approve of this message!", + "WE WERE BEGINNING TO THINK YOU LOST YOUR MIND", + "WOO, Spring Break!", + "I can't believe it's not porn!", + "I approve of this message!", + "Operation beach volleyball is go.", + "Plucky ducky kinda day.", + "Today called just to say \"Hi.\"", + "STOP AND SMELL THE FUCKING ROSES", + "FUCKING NOTHING WRONG WITH TODAY", + "LETS HAVE A FUCKING SOIREE", + "What would you do for a holyshititsniceout bar?", + "There are no rules today, blow shit up!", + "Celebrate Today's Day and buy your Today a present so it knows you care.", + "I feel bad about playing on my computer all day.", + "Party in the woods.", + "It is now safe to leave your home.", + "PUT A FUCKING CAPE ON TODAY, BECAUSE IT'S SUPER", + "Today is like \"ice\" if it started with an \"n\". Fuck you, we don't mean nce.", + "Water park! Water drive! Just get wet!", + "The geese are on their way back! Unless you live where they migrate to for the winter.", + "FUCKING AFFABLE AS SHIT", + "Give the sun a raise!", + "Go outside and go cycling or some shit, you fitness nerd!", + "Today is better than an original holographic Charizard. Loser!" + ] + else: + remark = "IT'S FUCKING HOT" + flavors = [ + "Do you have life insurance?", + "Like super models, IT'S TOO FUCKING HOT.", + "Not even PAM can make me not stick to this seat", + "SWIMMIN HOLE!", + "Time to crank the AC.", + "THE FUCKING EQUATOR CALLED, AND IT'S JEALOUS.", + "Looked in the fridge this morning for some eggs. They're already cooked.", + "Keeping the AC business in business.", + "I burned my feet walking on grass.", + "times you wish you didn't have leather seats", + "Isn't the desert nice this time of year?", + "Why, oh why did we decide to live in an oven?", + "It's hotter outside than my fever.", + "TAKE IT OFF!", + "TAKE FUCKING EVERYTHING OFF!", + "EVEN THAT NEEDS TO COME OFF!", + "Even your frigid girlfriend can't save you from today.", + "I need gloves to touch the steering wheel.", + "I can hear that power bill running up right now!", + "Lock up yo' ice cream trucks, lock up yo' wife.", + "FUCKING SUNBURNED, AND I WAS INSIDE ALL DAY.", + "Fuck this shit, I'm moving back to Alaska." + ] + + if w.descriptor == "thunderstorm": + remark += " AND THUNDERING" + flavors += [ + "Are you sure you want to go out in that? I'm not", + "Fuck my ears!", + "Don't go flying a kite. Unless you're Ben Franklin", + "Did you think Eris would smile upon your failings?" + ] + elif w.precipitation in ("snow", "snow grains"): + remark += " AND SNOWING" + flavors += [ + "What's this white stuff that's sticking to everything?", + "At least that stuff doesn't glow in the dark!", + "How the fuck am I supposed to get around now?", + "And you thought four-wheel-drive would help you!", + "Go fight those cadets with snowballs", + "Where does the white go when the snow melts?", + "Just sNOw" + ] + elif w.precipitation in ("drizzle", "rain", "unknown precipitation"): + remark += " AND WET" + flavors += [ + "Just like your mom!", + "I guess it can't get much worse", + "Hope you have a rain coat", + "Shower outside?", + "If only more buildings had gargoyles..." + ] + elif w.precipitation in ("ice crystals", "ice pellets"): + remark += " AND ICY" + flavors += [ + "Nice, but without the N!", + "Where's some NaCl when you need it?", + "I hope your skates are nearby.", + "Studded tyres? What're those?" + ] + elif w.precipitation in ("hail", "small hail"): + remark += " AND HAILING" + flavors += [ + "Windshield damage!", + "Car alarms!", + "Lie face-down outside: free massage!" + ] + + if int(tempf) == 69: + remark = "IT'S FUCKING SEXY TIME" + flavors = [ + "Why is 77 better than 69? You get eight more.", + "What comes after 69? Mouthwash.", + "If you are given two contradictory orders, obey them both.", + "a good fuckin' time! ;)", + "What's the square root of 69? Eight something." + ] + + flavor = random.choice(flavors) + + response = "{temp} {remark} - {flavor} - {location} {time}Z".format( + temp=temp, remark=remark, flavor=flavor, location=w.station, + time=w.time.strftime("%H:%M")) + phenny.say(response) +tfw.rule = (['tfw', 'tfwm'], r'(.*)') + + +def tfwf(phenny, input): + """.tfwf <city/zip> - The fucking weather, in fucking degrees Fahrenheit.""" + return tfw(phenny, input, fahrenheit=True) +tfwf.rule = (['tfwf'], r'(.*)') + + +def tfwc(phenny, input): + """.tfwc <city/zip> - The fucking weather, in fucking degrees celsius.""" + return tfw(phenny, input, celsius=True) +tfwc.rule = (['tfwc'], r'(.*)') + +def tfwev(phenny, input): + """.tfwev <city/zip> - The fucking weather, in fucking electron volts.""" + return tfw(phenny, input, mev=True) +tfwev.rule = (['tfwev'], r'(.*)') + +if __name__ == '__main__': + print(__doc__.strip()) diff --git a/modules/translate.py b/modules/translate.py deleted file mode 100755 index 94e5f6458..000000000 --- a/modules/translate.py +++ /dev/null @@ -1,133 +0,0 @@ -#!/usr/bin/env python -# coding=utf-8 -""" -translate.py - Phenny Translation Module -Copyright 2008, Sean B. Palmer, inamidst.com -Licensed under the Eiffel Forum License 2. - -http://inamidst.com/phenny/ -""" - -import re, urllib -import web - -def translate(text, input='auto', output='en'): - raw = False - if output.endswith('-raw'): - output = output[:-4] - raw = True - - import urllib2, json - opener = urllib2.build_opener() - opener.addheaders = [( - 'User-Agent', 'Mozilla/5.0' + - '(X11; U; Linux i686)' + - 'Gecko/20071127 Firefox/2.0.0.11' - )] - - input, output = urllib.quote(input), urllib.quote(output) - text = urllib.quote(text) - - result = opener.open('http://translate.google.com/translate_a/t?' + - ('client=t&hl=en&sl=%s&tl=%s&multires=1' % (input, output)) + - ('&otf=1&ssel=0&tsel=0&uptl=en&sc=1&text=%s' % text)).read() - - while ',,' in result: - result = result.replace(',,', ',null,') - data = json.loads(result) - - if raw: - return str(data), 'en-raw' - - try: language = data[2] # -2][0][0] - except: language = '?' - - return ''.join(x[0] for x in data[0]), language - -def tr(phenny, context): - """Translates a phrase, with an optional language hint.""" - input, output, phrase = context.groups() - - phrase = phrase.encode('utf-8') - - if (len(phrase) > 350) and (not context.admin): - return phenny.reply('Phrase must be under 350 characters.') - - input = input or 'auto' - input = input.encode('utf-8') - output = (output or 'en').encode('utf-8') - - if input != output: - msg, input = translate(phrase, input, output) - if isinstance(msg, str): - msg = msg.decode('utf-8') - if msg: - msg = web.decode(msg) # msg.replace(''', "'") - msg = '"%s" (%s to %s, translate.google.com)' % (msg, input, output) - else: msg = 'The %s to %s translation failed, sorry!' % (input, output) - - phenny.reply(msg) - else: phenny.reply('Language guessing failed, so try suggesting one!') - -tr.rule = ('$nick', ur'(?:([a-z]{2}) +)?(?:([a-z]{2}|en-raw) +)?["“](.+?)["”]\? *$') -tr.example = '$nickname: "mon chien"? or $nickname: fr "mon chien"?' -tr.priority = 'low' - -def tr2(phenny, input): - """Translates a phrase, with an optional language hint.""" - command = input.group(2).encode('utf-8') - - def langcode(p): - return p.startswith(':') and (2 < len(p) < 10) and p[1:].isalpha() - - args = ['auto', 'en'] - - for i in xrange(2): - if not ' ' in command: break - prefix, cmd = command.split(' ', 1) - if langcode(prefix): - args[i] = prefix[1:] - command = cmd - phrase = command - - if (len(phrase) > 350) and (not input.admin): - return phenny.reply('Phrase must be under 350 characters.') - - src, dest = args - if src != dest: - msg, src = translate(phrase, src, dest) - if isinstance(msg, str): - msg = msg.decode('utf-8') - if msg: - msg = web.decode(msg) # msg.replace(''', "'") - msg = '"%s" (%s to %s, translate.google.com)' % (msg, src, dest) - else: msg = 'The %s to %s translation failed, sorry!' % (src, dest) - - phenny.reply(msg) - else: phenny.reply('Language guessing failed, so try suggesting one!') - -tr2.commands = ['tr'] -tr2.priority = 'low' - -def mangle(phenny, input): - phrase = input.group(2).encode('utf-8') - for lang in ['fr', 'de', 'es', 'it', 'ja']: - backup = phrase - phrase = translate(phrase, 'en', lang) - if not phrase: - phrase = backup - break - __import__('time').sleep(0.5) - - backup = phrase - phrase = translate(phrase, lang, 'en') - if not phrase: - phrase = backup - break - __import__('time').sleep(0.5) - - phenny.reply(phrase or 'ERRORS SRY') -mangle.commands = ['mangle'] - -if __name__ == '__main__': - print __doc__.strip() diff --git a/modules/urbandict.py b/modules/urbandict.py new file mode 100644 index 000000000..52485f160 --- /dev/null +++ b/modules/urbandict.py @@ -0,0 +1,45 @@ +#!/usr/bin/python3 +""" +urbandict.py - urban dictionary module +author: mutantmonkey <mutantmonkey@mutantmonkey.in> +""" + +from tools import GrumbleError +import web +import json + + +def urbandict(phenny, input): + """.urb <word> - Search Urban Dictionary for a definition.""" + + word = input.group(2) + if not word: + phenny.say(urbandict.__doc__.strip()) + return + + try: + data = web.get( + "http://api.urbandictionary.com/v0/define?term={0}".format( + web.quote(word))) + data = json.loads(data) + except: + raise GrumbleError( + "Urban Dictionary slemped out on me. Try again in a minute.") + + results = data['list'] + + if not results: + phenny.say("No results found for {0}".format(word)) + return + + result = results[0] + url = 'http://www.urbandictionary.com/define.php?term={0}'.format( + web.quote(word)) + + response = "{0} - {1}".format(result['definition'].strip()[:256], url) + phenny.say(response) +urbandict.name = 'urb' +urbandict.rule = (['urb'], r'(.*)') + +if __name__ == '__main__': + print(__doc__.strip()) diff --git a/modules/validate.py b/modules/validate.py deleted file mode 100755 index 85815d150..000000000 --- a/modules/validate.py +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env python -""" -validate.py - Phenny Validation Module -Copyright 2008, Sean B. Palmer, inamidst.com -Licensed under the Eiffel Forum License 2. - -http://inamidst.com/phenny/ -""" - -import web - -def val(phenny, input): - """Check a webpage using the W3C Markup Validator.""" - if not input.group(2): - return phenny.reply("Nothing to validate.") - uri = input.group(2) - if not uri.startswith('http://'): - uri = 'http://' + uri - - path = '/check?uri=%s;output=xml' % web.urllib.quote(uri) - info = web.head('http://validator.w3.org' + path) - - result = uri + ' is ' - - if isinstance(info, list): - return phenny.say('Got HTTP response %s' % info[1]) - - if info.has_key('X-W3C-Validator-Status'): - result += str(info['X-W3C-Validator-Status']) - if info['X-W3C-Validator-Status'] != 'Valid': - if info.has_key('X-W3C-Validator-Errors'): - n = int(info['X-W3C-Validator-Errors'].split(' ')[0]) - if n != 1: - result += ' (%s errors)' % n - else: result += ' (%s error)' % n - else: result += 'Unvalidatable: no X-W3C-Validator-Status' - - phenny.reply(result) -val.rule = (['val'], r'(?i)(\S+)') -val.example = '.val http://www.w3.org/' - -if __name__ == '__main__': - print __doc__.strip() diff --git a/modules/vtluugwiki.py b/modules/vtluugwiki.py new file mode 100644 index 000000000..0e2f2a161 --- /dev/null +++ b/modules/vtluugwiki.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python +""" +vtluugwiki.py - Phenny VTLUUG Wiki Module +Copyright 2008-9, Sean B. Palmer, inamidst.com +Licensed under the Eiffel Forum License 2. + +http://inamidst.com/phenny/ + +modified from Wikipedia module +author: mutantmonkey <mutantmonkey@mutantmonkey.in> +""" + +import wiki + +endpoints = { + 'api': 'https://vtluug.org/w/api.php?action=query&list=search&srsearch={0}&limit=1&prop=snippet&format=json', + 'url': 'https://vtluug.org/wiki/{0}', + 'search': 'https://vtluug.org/wiki/Special:Search?search={0}&fulltext=Search', +} + +def vtluug(phenny, input): + """.vtluug <term> - Look up something on the VTLUUG wiki.""" + + origterm = input.groups()[1] + if not origterm: + return phenny.say('Perhaps you meant ".vtluug VT-Wireless"?') + + term, section = wiki.parse_term(origterm) + + w = wiki.Wiki(endpoints) + match = w.search(term) + + if not match: + phenny.say('Can\'t find anything in the VTLUUG Wiki for "{0}".'.format(term)) + return + + snippet, url = wiki.extract_snippet(match, section) + + phenny.say('"{0}" - {1}'.format(snippet, url)) + +vtluug.commands = ['vtluug'] +vtluug.priority = 'high' + +if __name__ == '__main__': + print(__doc__.strip()) diff --git a/modules/wadsworth.py b/modules/wadsworth.py new file mode 100644 index 000000000..53a4f3a16 --- /dev/null +++ b/modules/wadsworth.py @@ -0,0 +1,19 @@ +#!/usr/bin/python3 +""" +wadsworth.py - Apply Wadsworth's Constant to some text. +https://gist.github.com/1257195 +author: mutantmonkey <mutantmonkey@mutantmonkey.in> +""" + +def wadsworth(phenny, input): + """.wadsworth - Apply Wadsworth's Constant to some text.""" + text = input.group(2) + if not text: + return phenny.say(".wadsworth <text> - apply Wadsworth's Constant") + + text = text[text.find(' ', int(round(0.3 * len(text)))) + 1:] + phenny.say(text) +wadsworth.commands = ['wadsworth'] + +if __name__ == '__main__': + print(__doc__.strip()) diff --git a/modules/weather.py b/modules/weather.py old mode 100755 new mode 100644 index 29b01e586..f6a489d37 --- a/modules/weather.py +++ b/modules/weather.py @@ -7,406 +7,97 @@ http://inamidst.com/phenny/ """ -import re, urllib +import re +import metar +import json import web -from tools import deprecated +from tools import deprecated, GrumbleError r_from = re.compile(r'(?i)([+-]\d+):00 from') -def location(name): - name = urllib.quote(name.encode('utf-8')) - uri = 'http://ws.geonames.org/searchJSON?q=%s&maxRows=1' % name - for i in xrange(10): - u = urllib.urlopen(uri) - if u is not None: break - bytes = u.read() - u.close() - results = web.json(bytes) - try: name = results['geonames'][0]['name'] - except IndexError: - return '?', '?', '0', '0' - countryName = results['geonames'][0]['countryName'] - lat = results['geonames'][0]['lat'] - lng = results['geonames'][0]['lng'] - return name, countryName, lat, lng - -class GrumbleError(object): - pass - -def local(icao, hour, minute): - uri = ('http://www.flightstats.com/' + - 'go/Airport/airportDetails.do?airportCode=%s') - try: bytes = web.get(uri % icao) - except AttributeError: - raise GrumbleError('A WEBSITE HAS GONE DOWN WTF STUPID WEB') - m = r_from.search(bytes) - if m: - offset = m.group(1) - lhour = int(hour) + int(offset) - lhour = lhour % 24 - return (str(lhour) + ':' + str(minute) + ', ' + str(hour) + - str(minute) + 'Z') - # return (str(lhour) + ':' + str(minute) + ' (' + str(hour) + - # ':' + str(minute) + 'Z)') - return str(hour) + ':' + str(minute) + 'Z' - -def code(phenny, search): - from icao import data - - if search.upper() in [loc[0] for loc in data]: - return search.upper() - else: - name, country, latitude, longitude = location(search) - if name == '?': return False - sumOfSquares = (99999999999999999999999999999, 'ICAO') - for icao_code, lat, lon in data: - latDiff = abs(latitude - lat) - lonDiff = abs(longitude - lon) - diff = (latDiff * latDiff) + (lonDiff * lonDiff) - if diff < sumOfSquares[0]: - sumOfSquares = (diff, icao_code) - return sumOfSquares[1] - -@deprecated -def f_weather(self, origin, match, args): - """.weather <ICAO> - Show the weather at airport with the code <ICAO>.""" - if origin.sender == '#talis': - if args[0].startswith('.weather '): return - - icao_code = match.group(2) - if not icao_code: - return self.msg(origin.sender, 'Try .weather London, for example?') - - icao_code = code(self, icao_code) - - if not icao_code: - self.msg(origin.sender, 'No ICAO code found, sorry') - return - - uri = 'http://weather.noaa.gov/pub/data/observations/metar/stations/%s.TXT' - try: bytes = web.get(uri % icao_code) - except AttributeError: - raise GrumbleError('OH CRAP NOAA HAS GONE DOWN THE WEB IS BROKEN') - if 'Not Found' in bytes: - self.msg(origin.sender, icao_code+': no such ICAO code, or no NOAA data') - return - - metar = bytes.splitlines().pop() - metar = metar.split(' ') - - if len(metar[0]) == 4: - metar = metar[1:] - - if metar[0].endswith('Z'): - time = metar[0] - metar = metar[1:] - else: time = None - - if metar[0] == 'AUTO': - metar = metar[1:] - if metar[0] == 'VCU': - self.msg(origin.sender, icao_code + ': no data provided') - return - - if metar[0].endswith('KT'): - wind = metar[0] - metar = metar[1:] - else: wind = None - - if ('V' in metar[0]) and (metar[0] != 'CAVOK'): - vari = metar[0] - metar = metar[1:] - else: vari = None - - if ((len(metar[0]) == 4) or - metar[0].endswith('SM')): - visibility = metar[0] - metar = metar[1:] - else: visibility = None - - while metar[0].startswith('R') and (metar[0].endswith('L') - or 'L/' in metar[0]): - metar = metar[1:] - - if len(metar[0]) == 6 and (metar[0].endswith('N') or - metar[0].endswith('E') or - metar[0].endswith('S') or - metar[0].endswith('W')): - metar = metar[1:] # 7000SE? - - cond = [] - while (((len(metar[0]) < 5) or - metar[0].startswith('+') or - metar[0].startswith('-')) and (not (metar[0].startswith('VV') or - metar[0].startswith('SKC') or metar[0].startswith('CLR') or - metar[0].startswith('FEW') or metar[0].startswith('SCT') or - metar[0].startswith('BKN') or metar[0].startswith('OVC')))): - cond.append(metar[0]) - metar = metar[1:] - - while '/P' in metar[0]: - metar = metar[1:] - - if not metar: - self.msg(origin.sender, icao_code + ': no data provided') - return - - cover = [] - while (metar[0].startswith('VV') or metar[0].startswith('SKC') or - metar[0].startswith('CLR') or metar[0].startswith('FEW') or - metar[0].startswith('SCT') or metar[0].startswith('BKN') or - metar[0].startswith('OVC')): - cover.append(metar[0]) - metar = metar[1:] - if not metar: - self.msg(origin.sender, icao_code + ': no data provided') - return - - if metar[0] == 'CAVOK': - cover.append('CLR') - metar = metar[1:] - - if metar[0] == 'PRFG': - cover.append('CLR') # @@? - metar = metar[1:] - - if metar[0] == 'NSC': - cover.append('CLR') - metar = metar[1:] - - if ('/' in metar[0]) or (len(metar[0]) == 5 and metar[0][2] == '.'): - temp = metar[0] - metar = metar[1:] - else: temp = None - - if metar[0].startswith('QFE'): - metar = metar[1:] - - if metar[0].startswith('Q') or metar[0].startswith('A'): - pressure = metar[0] - metar = metar[1:] - else: pressure = None - - if time: - hour = time[2:4] - minute = time[4:6] - time = local(icao_code, hour, minute) - else: time = '(time unknown)' - - if wind: - speed = int(wind[3:5]) - if speed < 1: - description = 'Calm' - elif speed < 4: - description = 'Light air' - elif speed < 7: - description = 'Light breeze' - elif speed < 11: - description = 'Gentle breeze' - elif speed < 16: - description = 'Moderate breeze' - elif speed < 22: - description = 'Fresh breeze' - elif speed < 28: - description = 'Strong breeze' - elif speed < 34: - description = 'Near gale' - elif speed < 41: - description = 'Gale' - elif speed < 48: - description = 'Strong gale' - elif speed < 56: - description = 'Storm' - elif speed < 64: - description = 'Violent storm' - else: description = 'Hurricane' - - degrees = wind[0:3] - if degrees == 'VRB': - degrees = u'\u21BB'.encode('utf-8') - elif (degrees <= 22.5) or (degrees > 337.5): - degrees = u'\u2191'.encode('utf-8') - elif (degrees > 22.5) and (degrees <= 67.5): - degrees = u'\u2197'.encode('utf-8') - elif (degrees > 67.5) and (degrees <= 112.5): - degrees = u'\u2192'.encode('utf-8') - elif (degrees > 112.5) and (degrees <= 157.5): - degrees = u'\u2198'.encode('utf-8') - elif (degrees > 157.5) and (degrees <= 202.5): - degrees = u'\u2193'.encode('utf-8') - elif (degrees > 202.5) and (degrees <= 247.5): - degrees = u'\u2199'.encode('utf-8') - elif (degrees > 247.5) and (degrees <= 292.5): - degrees = u'\u2190'.encode('utf-8') - elif (degrees > 292.5) and (degrees <= 337.5): - degrees = u'\u2196'.encode('utf-8') - - if not icao_code.startswith('EN') and not icao_code.startswith('ED'): - wind = '%s %skt (%s)' % (description, speed, degrees) - elif icao_code.startswith('ED'): - kmh = int(round(speed * 1.852, 0)) - wind = '%s %skm/h (%skt) (%s)' % (description, kmh, speed, degrees) - elif icao_code.startswith('EN'): - ms = int(round(speed * 0.514444444, 0)) - wind = '%s %sm/s (%skt) (%s)' % (description, ms, speed, degrees) - else: wind = '(wind unknown)' - - if visibility: - visibility = visibility + 'm' - else: visibility = '(visibility unknown)' - - if cover: - level = None - for c in cover: - if c.startswith('OVC') or c.startswith('VV'): - if (level is None) or (level < 8): - level = 8 - elif c.startswith('BKN'): - if (level is None) or (level < 5): - level = 5 - elif c.startswith('SCT'): - if (level is None) or (level < 3): - level = 3 - elif c.startswith('FEW'): - if (level is None) or (level < 1): - level = 1 - elif c.startswith('SKC') or c.startswith('CLR'): - if level is None: - level = 0 - - if level == 8: - cover = u'Overcast \u2601'.encode('utf-8') - elif level == 5: - cover = 'Cloudy' - elif level == 3: - cover = 'Scattered' - elif (level == 1) or (level == 0): - cover = u'Clear \u263C'.encode('utf-8') - else: cover = 'Cover Unknown' - else: cover = 'Cover Unknown' - - if temp: - if '/' in temp: - temp = temp.split('/')[0] - else: temp = temp.split('.')[0] - if temp.startswith('M'): - temp = '-' + temp[1:] - try: temp = int(temp) - except ValueError: temp = '?' - else: temp = '?' - - if pressure: - if pressure.startswith('Q'): - pressure = pressure.lstrip('Q') - if pressure != 'NIL': - pressure = str(int(pressure)) + 'mb' - else: pressure = '?mb' - elif pressure.startswith('A'): - pressure = pressure.lstrip('A') - if pressure != 'NIL': - inches = pressure[:2] + '.' + pressure[2:] - mb = int(float(inches) * 33.7685) - pressure = '%sin (%smb)' % (inches, mb) - else: pressure = '?mb' - - if isinstance(temp, int): - f = round((temp * 1.8) + 32, 2) - temp = u'%s\u2109 (%s\u2103)'.encode('utf-8') % (f, temp) - else: pressure = '?mb' - if isinstance(temp, int): - temp = u'%s\u2103'.encode('utf-8') % temp - - if cond: - conds = cond - cond = '' - - intensities = { - '-': 'Light', - '+': 'Heavy' - } - - descriptors = { - 'MI': 'Shallow', - 'PR': 'Partial', - 'BC': 'Patches', - 'DR': 'Drifting', - 'BL': 'Blowing', - 'SH': 'Showers of', - 'TS': 'Thundery', - 'FZ': 'Freezing', - 'VC': 'In the vicinity:' - } - - phenomena = { - 'DZ': 'Drizzle', - 'RA': 'Rain', - 'SN': 'Snow', - 'SG': 'Snow Grains', - 'IC': 'Ice Crystals', - 'PL': 'Ice Pellets', - 'GR': 'Hail', - 'GS': 'Small Hail', - 'UP': 'Unknown Precipitation', - 'BR': 'Mist', - 'FG': 'Fog', - 'FU': 'Smoke', - 'VA': 'Volcanic Ash', - 'DU': 'Dust', - 'SA': 'Sand', - 'HZ': 'Haze', - 'PY': 'Spray', - 'PO': 'Whirls', - 'SQ': 'Squalls', - 'FC': 'Tornado', - 'SS': 'Sandstorm', - 'DS': 'Duststorm', - # ? Cf. http://swhack.com/logs/2007-10-05#T07-58-56 - 'TS': 'Thunderstorm', - 'SH': 'Showers' - } - - for c in conds: - if c.endswith('//'): - if cond: cond += ', ' - cond += 'Some Precipitation' - elif len(c) == 5: - intensity = intensities[c[0]] - descriptor = descriptors[c[1:3]] - phenomenon = phenomena.get(c[3:], c[3:]) - if cond: cond += ', ' - cond += intensity + ' ' + descriptor + ' ' + phenomenon - elif len(c) == 4: - descriptor = descriptors.get(c[:2], c[:2]) - phenomenon = phenomena.get(c[2:], c[2:]) - if cond: cond += ', ' - cond += descriptor + ' ' + phenomenon - elif len(c) == 3: - intensity = intensities.get(c[0], c[0]) - phenomenon = phenomena.get(c[1:], c[1:]) - if cond: cond += ', ' - cond += intensity + ' ' + phenomenon - elif len(c) == 2: - phenomenon = phenomena.get(c, c) - if cond: cond += ', ' - cond += phenomenon - - # if not cond: - # format = u'%s at %s: %s, %s, %s, %s' - # args = (icao, time, cover, temp, pressure, wind) - # else: - # format = u'%s at %s: %s, %s, %s, %s, %s' - # args = (icao, time, cover, temp, pressure, cond, wind) - - if not cond: - format = u'%s, %s, %s, %s - %s %s' - args = (cover, temp, pressure, wind, str(icao_code), time) - else: - format = u'%s, %s, %s, %s, %s - %s, %s' - args = (cover, temp, pressure, cond, wind, str(icao_code), time) - - self.msg(origin.sender, format.encode('utf-8') % args) +def location(q): + uri = 'https://nominatim.openstreetmap.org/search?{type}={query}&format=json' + if q.isdigit(): + uri = uri . format(type = 'postalcode', query = web.quote(q)) + else: + uri = uri . format(type = 'q', query = web.quote(q)) + results = web.get(uri) + data = json.loads(results) + + if not data: + return None, None + + latitude = float(data[0]['lat']) + longitude = float(data[0]['lon']) + + return latitude, longitude + + +def local(icao, hour, minute): + uri = ('http://www.flightstats.com/' + + 'go/Airport/airportDetails.do?airportCode=%s') + try: bytes = web.get(uri % icao) + except AttributeError: + raise GrumbleError('A WEBSITE HAS GONE DOWN WTF STUPID WEB') + m = r_from.search(bytes) + if m: + offset = m.group(1) + lhour = int(hour) + int(offset) + lhour = lhour % 24 + return (str(lhour) + ':' + str(minute) + ', ' + str(hour) + + str(minute) + 'Z') + # return (str(lhour) + ':' + str(minute) + ' (' + str(hour) + + # ':' + str(minute) + 'Z)') + return str(hour) + ':' + str(minute) + 'Z' + + +def code(phenny, search): + from icao import data + + if search.upper() in [loc[0] for loc in data]: + return search.upper() + else: + latitude, longitude = location(search) + if not latitude or not longitude: + return False + sumOfSquares = (99999999999999999999999999999, 'ICAO') + for icao_code, lat, lon in data: + latDiff = abs(latitude - lat) + lonDiff = abs(longitude - lon) + diff = (latDiff * latDiff) + (lonDiff * lonDiff) + if diff < sumOfSquares[0]: + sumOfSquares = (diff, icao_code) + return sumOfSquares[1] + + +def f_weather(phenny, input): + """.weather <ICAO> - Show the weather at airport with the code <ICAO>.""" + icao_code = input.group(2) + if not icao_code: + return phenny.say("Try .weather London, for example?") + + icao_code = code(phenny, icao_code) + + if not icao_code: + phenny.say("No ICAO code found, sorry") + return + + uri = 'http://tgftp.nws.noaa.gov/data/observations/metar/stations/%s.TXT' + try: + bytes = web.get(uri % icao_code) + except AttributeError: + raise GrumbleError('OH CRAP NOAA HAS GONE DOWN THE WEB IS BROKEN') + except web.HTTPError: + phenny.say("No NOAA data available for that location.") + return + + if 'Not Found' in bytes: + phenny.say(icao_code + ": no such ICAO code, or no NOAA data") + return + + phenny.say(str(metar.parse(bytes))) f_weather.rule = (['weather'], r'(.*)') -if __name__ == '__main__': - print __doc__.strip() +if __name__ == '__main__': + print(__doc__.strip()) diff --git a/modules/wikipedia.py b/modules/wikipedia.py old mode 100755 new mode 100644 index 1a406b3f8..b37cfcfbd --- a/modules/wikipedia.py +++ b/modules/wikipedia.py @@ -7,176 +7,37 @@ http://inamidst.com/phenny/ """ -import re, urllib, gzip, StringIO -import web +import wiki -wikiuri = 'http://%s.wikipedia.org/wiki/%s' -# wikisearch = 'http://%s.wikipedia.org/wiki/Special:Search?' \ -# + 'search=%s&fulltext=Search' +endpoints = { + 'api': 'https://en.wikipedia.org/w/api.php?format=json&action=query&list=search&srsearch={0}&prop=snippet&limit=1', + 'url': 'https://en.wikipedia.org/wiki/{0}', + 'search': 'https://en.wikipedia.org/wiki/Special:Search?search={0}&fulltext=Search', +} -r_tr = re.compile(r'(?ims)<tr[^>]*>.*?</tr>') -r_paragraph = re.compile(r'(?ims)<p[^>]*>.*?</p>|<li(?!n)[^>]*>.*?</li>') -r_tag = re.compile(r'<(?!!)[^>]+>') -r_whitespace = re.compile(r'[\t\r\n ]+') -r_redirect = re.compile( - r'(?ims)class=.redirectText.>\s*<a\s*href=./wiki/([^"/]+)' -) - -abbrs = ['etc', 'ca', 'cf', 'Co', 'Ltd', 'Inc', 'Mt', 'Mr', 'Mrs', - 'Dr', 'Ms', 'Rev', 'Fr', 'St', 'Sgt', 'pron', 'approx', 'lit', - 'syn', 'transl', 'sess', 'fl', 'Op', 'Dec', 'Brig', 'Gen'] \ - + list('ABCDEFGHIJKLMNOPQRSTUVWXYZ') \ - + list('abcdefghijklmnopqrstuvwxyz') -t_sentence = r'^.{5,}?(?<!\b%s)(?:\.(?=[\[ ][A-Z0-9]|\Z)|\Z)' -r_sentence = re.compile(t_sentence % r')(?<!\b'.join(abbrs)) - -def unescape(s): - s = s.replace('>', '>') - s = s.replace('<', '<') - s = s.replace('&', '&') - s = s.replace(' ', ' ') - return s - -def text(html): - html = r_tag.sub('', html) - html = r_whitespace.sub(' ', html) - return unescape(html).strip() - -def search(term): - try: import search - except ImportError, e: - print e - return term - - if isinstance(term, unicode): - term = term.encode('utf-8') - else: term = term.decode('utf-8') - - term = term.replace('_', ' ') - try: uri = search.google_search('site:en.wikipedia.org %s' % term) - except IndexError: return term - if uri: - return uri[len('http://en.wikipedia.org/wiki/'):] - else: return term - -def wikipedia(term, language='en', last=False): - global wikiuri - if not '%' in term: - if isinstance(term, unicode): - t = term.encode('utf-8') - else: t = term - q = urllib.quote(t) - u = wikiuri % (language, q) - bytes = web.get(u) - else: bytes = web.get(wikiuri % (language, term)) - - if bytes.startswith('\x1f\x8b\x08\x00\x00\x00\x00\x00'): - f = StringIO.StringIO(bytes) - f.seek(0) - gzip_file = gzip.GzipFile(fileobj=f) - bytes = gzip_file.read() - gzip_file.close() - f.close() - - bytes = r_tr.sub('', bytes) - - if not last: - r = r_redirect.search(bytes[:4096]) - if r: - term = urllib.unquote(r.group(1)) - return wikipedia(term, language=language, last=True) - - paragraphs = r_paragraph.findall(bytes) - - if not paragraphs: - if not last: - term = search(term) - return wikipedia(term, language=language, last=True) - return None - - # Pre-process - paragraphs = [para for para in paragraphs - if (para and 'technical limitations' not in para - and 'window.showTocToggle' not in para - and 'Deletion_policy' not in para - and 'Template:AfD_footer' not in para - and not (para.startswith('<p><i>') and - para.endswith('</i></p>')) - and not 'disambiguation)"' in para) - and not '(images and media)' in para - and not 'This article contains a' in para - and not 'id="coordinates"' in para - and not 'class="thumb' in para] - # and not 'style="display:none"' in para] - - for i, para in enumerate(paragraphs): - para = para.replace('<sup>', '|') - para = para.replace('</sup>', '|') - paragraphs[i] = text(para).strip() - - # Post-process - paragraphs = [para for para in paragraphs if - (para and not (para.endswith(':') and len(para) < 150))] - - para = text(paragraphs[0]) - m = r_sentence.match(para) - - if not m: - if not last: - term = search(term) - return wikipedia(term, language=language, last=True) - return None - sentence = m.group(0) - - maxlength = 275 - if len(sentence) > maxlength: - sentence = sentence[:maxlength] - words = sentence[:-5].split(' ') - words.pop() - sentence = ' '.join(words) + ' [...]' +def wik(phenny, input): + """.wik <term> - Look up something on Wikipedia.""" - if (('using the Article Wizard if you wish' in sentence) - or ('or add a request for it' in sentence) - or ('in existing articles' in sentence)): - if not last: - term = search(term) - return wikipedia(term, language=language, last=True) - return None + origterm = input.groups()[1] + if not origterm: + return phenny.say('Perhaps you meant ".wik Zen"?') - sentence = '"' + sentence.replace('"', "'") + '"' - sentence = sentence.decode('utf-8').encode('utf-8') - wikiuri = wikiuri.decode('utf-8').encode('utf-8') - term = term.decode('utf-8').encode('utf-8') - return sentence + ' - ' + (wikiuri % (language, term)) + origterm = origterm.strip() + term, section = wiki.parse_term(origterm) -def wik(phenny, input): - origterm = input.groups()[1] - if not origterm: - return phenny.say('Perhaps you meant ".wik Zen"?') - origterm = origterm.encode('utf-8') + w = wiki.Wiki(endpoints) + match = w.search(term) - term = urllib.unquote(origterm) - language = 'en' - if term.startswith(':') and (' ' in term): - a, b = term.split(' ', 1) - a = a.lstrip(':') - if a.isalpha(): - language, term = a, b - term = term[0].upper() + term[1:] - term = term.replace(' ', '_') + if not match: + phenny.say('Can\'t find anything in Wikipedia for "{0}".'.format(origterm)) + return - try: result = wikipedia(term, language) - except IOError: - args = (language, wikiuri % (language, term)) - error = "Can't connect to %s.wikipedia.org (%s)" % args - return phenny.say(error) + snippet, url = wiki.extract_snippet(match, section) - if result is not None: - phenny.say(result) - else: phenny.say('Can\'t find anything in Wikipedia for "%s".' % origterm) + phenny.say('"{0}" - {1}'.format(snippet, url)) wik.commands = ['wik'] wik.priority = 'high' if __name__ == '__main__': - print __doc__.strip() + print(__doc__.strip()) diff --git a/modules/wiktionary.py b/modules/wiktionary.py old mode 100755 new mode 100644 index 92291944c..8b9d82c3a --- a/modules/wiktionary.py +++ b/modules/wiktionary.py @@ -9,92 +9,108 @@ import re import web +import json uri = 'http://en.wiktionary.org/w/index.php?title=%s&printable=yes' -r_tag = re.compile(r'<[^>]+>') +wikiapi = 'http://en.wiktionary.org/w/api.php?action=query&titles={0}&prop=revisions&rvprop=content&format=json' +#r_tag = re.compile(r'<[^>]+>') r_ul = re.compile(r'(?ims)<ul>.*?</ul>') +r_li = re.compile(r'^# ') +r_img = re.compile(r'\[\[Image:.*\]\]') +r_link1 = re.compile(r'\[\[([A-Za-z0-9\-_ ]+?)\]\]') +r_link2 = re.compile(r'\[\[([A-Za-z0-9\-_ ]+?)\|(.+?)\]\]') +r_context = re.compile(r'{{context\|(.+?)}}') +r_template1 = re.compile(r'{{.+?\|(.+?)}}') +r_template2 = re.compile(r'{{(.+?)}}') def text(html): - text = r_tag.sub('', html).strip() - text = text.replace('\n', ' ') - text = text.replace('\r', '') - text = text.replace('(intransitive', '(intr.') - text = text.replace('(transitive', '(trans.') - return text + text = r_li.sub('', html).strip() + text = r_img.sub('', text) + text = r_link1.sub(r'\1', text) + text = r_link2.sub(r'\2', text) + text = r_context.sub(r'\1:', text) + text = r_template1.sub(r'\1:', text) + text = r_template2.sub(r'\1:', text) + return text def wiktionary(word): - bytes = web.get(uri % web.urllib.quote(word.encode('utf-8'))) - bytes = r_ul.sub('', bytes) - - mode = None - etymology = None - definitions = {} - for line in bytes.splitlines(): - if 'id="Etymology"' in line: - mode = 'etymology' - elif 'id="Noun"' in line: - mode = 'noun' - elif 'id="Verb"' in line: - mode = 'verb' - elif 'id="Adjective"' in line: - mode = 'adjective' - elif 'id="Adverb"' in line: - mode = 'adverb' - elif 'id="Interjection"' in line: - mode = 'interjection' - elif 'id="Particle"' in line: - mode = 'particle' - elif 'id="Preposition"' in line: - mode = 'preposition' - elif 'id="' in line: - mode = None - - elif (mode == 'etmyology') and ('<p>' in line): - etymology = text(line) - elif (mode is not None) and ('<li>' in line): - definitions.setdefault(mode, []).append(text(line)) - - if '<hr' in line: - break - return etymology, definitions + bytes = web.get(wikiapi.format(web.quote(word))) + pages = json.loads(bytes) + pages = pages['query']['pages'] + pg = next(iter(pages)) + + try: + result = pages[pg]['revisions'][0]['*'] + except KeyError: + return '', '' + + mode = None + etymology = None + definitions = {} + for line in result.splitlines(): + if 'Etymology' in line: + mode = 'etymology' + elif '==Noun==' in line: + mode = 'noun' + elif '==Verb==' in line: + mode = 'verb' + elif '==Adjective==' in line: + mode = 'adjective' + elif '==Adverb==' in line: + mode = 'adverb' + elif '==Interjection==' in line: + mode = 'interjection' + elif 'Particle' in line: + mode = 'particle' + elif '==Preposition==' in line: + mode = 'preposition' + + elif mode == 'etymology': + etymology = text(line) + mode = None + elif mode is not None and '#' in line: + definitions.setdefault(mode, []).append(text(line)) + mode = None + + if '====Synonyms====' in line: + break + return etymology, definitions parts = ('preposition', 'particle', 'noun', 'verb', - 'adjective', 'adverb', 'interjection') + 'adjective', 'adverb', 'interjection') def format(word, definitions, number=2): - result = '%s' % word.encode('utf-8') - for part in parts: - if definitions.has_key(part): - defs = definitions[part][:number] - result += u' \u2014 '.encode('utf-8') + ('%s: ' % part) - n = ['%s. %s' % (i + 1, e.strip(' .')) for i, e in enumerate(defs)] - result += ', '.join(n) - return result.strip(' .,') + result = '%s' % word + for part in parts: + if part in definitions: + defs = definitions[part][:number] + result += ' \u2014 ' + ('%s: ' % part) + n = ['%s. %s' % (i + 1, e.strip(' .')) for i, e in enumerate(defs)] + result += ', '.join(n) + return result.strip(' .,') def w(phenny, input): - if not input.group(2): - return phenny.reply("Nothing to define.") - word = input.group(2) - etymology, definitions = wiktionary(word) - if not definitions: - phenny.say("Couldn't get any definitions for %s." % word) - return - - result = format(word, definitions) - if len(result) < 150: - result = format(word, definitions, 3) - if len(result) < 150: - result = format(word, definitions, 5) - - if len(result) > 300: - result = result[:295] + '[...]' - phenny.say(result) + """.w <word> - Get the definition of a word from wiktionary.""" + + if not input.group(2): + return phenny.reply("Nothing to define.") + word = input.group(2) + etymology, definitions = wiktionary(word) + if not definitions: + phenny.say("Couldn't get any definitions for %s." % word) + return + + result = format(word, definitions) + if len(result) < 150: + result = format(word, definitions, 3) + if len(result) < 150: + result = format(word, definitions, 5) + + if len(result) > 300: + result = result[:295] + '[...]' + phenny.say(result) w.commands = ['w'] w.example = '.w bailiwick' -def encarta(phenny, input): - return phenny.reply('Microsoft removed Encarta, try .w instead!') -encarta.commands = ['dict'] - if __name__ == '__main__': - print __doc__.strip() + print(__doc__.strip()) diff --git a/modules/wuvt.py b/modules/wuvt.py new file mode 100644 index 000000000..8849c28ee --- /dev/null +++ b/modules/wuvt.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python +""" +wuvt.py - WUVT now playing module for phenny +""" + +from tools import GrumbleError +import web + + +def wuvt(phenny, input): + """.wuvt - Find out what is currently playing on the radio station WUVT.""" + + try: + data = web.get('https://www.wuvt.vt.edu/playlists/latest_track', + headers={'Accept': "application/json"}) + trackinfo = web.json(data) + except: + raise GrumbleError("Failed to fetch current track from WUVT") + + if 'listeners' in trackinfo and trackinfo['listeners'] is not None: + phenny.say( + "{dj} is currently playing \"{title}\" by {artist} with " + "{listeners:d} online listeners".format( + dj=trackinfo['dj'], + title=trackinfo['title'], + artist=trackinfo['artist'], + listeners=trackinfo['listeners'])) + else: + phenny.say("{dj} is currently playing \"{title}\" by {artist}".format( + dj=trackinfo['dj'], + title=trackinfo['title'], + artist=trackinfo['artist'])) +wuvt.commands = ['wuvt'] +wuvt.example = '.wuvt' diff --git a/opt/freenode.py b/opt/freenode.py index 0c08cf2e6..e7aa13050 100755 --- a/opt/freenode.py +++ b/opt/freenode.py @@ -35,4 +35,4 @@ def replaced(phenny, input): replaced.priority = 'low' if __name__ == '__main__': - print __doc__.strip() + print(__doc__.strip()) diff --git a/phenny b/phenny index 1bc110b32..358d7fde0 100755 --- a/phenny +++ b/phenny @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 """ phenny - An IRC Bot Copyright 2008, Sean B. Palmer, inamidst.com @@ -11,165 +11,191 @@ Run ./phenny, then edit ~/.phenny/default.py Then run ./phenny again """ -import sys, os, imp, optparse +import argparse +import os +import sys +from importlib.machinery import SourceFileLoader from textwrap import dedent as trim dotdir = os.path.expanduser('~/.phenny') -def check_python_version(): - if sys.version_info < (2, 4): - error = 'Error: Requires Python 2.4 or later, from www.python.org' - print >> sys.stderr, error - sys.exit(1) - -def create_default_config(fn): - f = open(fn, 'w') - print >> f, trim("""\ - nick = 'phenny' - host = 'irc.example.net' - channels = ['#example', '#test'] - owner = 'yournickname' - - # password is the NickServ password, serverpass is the server password - # password = 'example' - # serverpass = 'serverpass' - - # These are people who will be able to use admin.py's functions... - admins = [owner, 'someoneyoutrust'] - # But admin.py is disabled by default, as follows: - exclude = ['admin'] - - # If you want to enumerate a list of modules rather than disabling - # some, use "enable = ['example']", which takes precedent over exclude - # - # enable = [] - - # Directories to load user modules from - # e.g. /path/to/my/modules - extra = [] - - # Services to load: maps channel names to white or black lists - external = { - '#liberal': ['!'], # allow all - '#conservative': [], # allow none - '*': ['!'] # default whitelist, allow all - } - - # EOF - """) - f.close() + +def check_python_version(): + if sys.version_info < (3, 4): + error = 'Error: Requires Python 3.4 or later, from www.python.org' + print(error, file=sys.stderr) + sys.exit(1) + + +def create_default_config(fn): + f = open(fn, 'w') + print(trim("""\ + nick = 'phenny' + host = 'irc.example.net' + port = 6667 + ssl = False + ipv6 = False + channels = ['#example', '#test'] + owner = 'yournickname' + + # password is the NickServ password, serverpass is the server password + # password = 'example' + # serverpass = 'serverpass' + + # linx-enabled features (.linx, .lnx) + # leave the api key blank to not use them and be sure to add the 'linx' module to the ignore list. + linx_api_key = "" + + # These are people who will be able to use admin.py's functions... + admins = [owner, 'someoneyoutrust'] + # But admin.py is disabled by default, as follows: + exclude = ['admin', 'linx', 'foodforus'] + + ignore = [''] + + # If you want to enumerate a list of modules rather than disabling + # some, use "enable = ['example']", which takes precedent over exclude + # + # enable = [] + + # Directories to load user modules from + # e.g. /path/to/my/modules + extra = [] + + # Services to load: maps channel names to white or black lists + external = { + '#liberal': ['!'], # allow all + '#conservative': [], # allow none + '*': ['!'] # default whitelist, allow all + } + + # EOF + """), file=f) + f.close() + def create_default_config_file(dotdir): - print 'Creating a default config file at ~/.phenny/default.py...' - default = os.path.join(dotdir, 'default.py') - create_default_config(default) - - print 'Done; now you can edit default.py, and run phenny! Enjoy.' - sys.exit(0) + print('Creating a default config file at ~/.phenny/default.py...') + default = os.path.join(dotdir, 'default.py') + create_default_config(default) + + print('Done; now you can edit default.py, and run phenny! Enjoy.') + sys.exit(0) + + +def create_dotdir(dotdir): + print('Creating a config directory at ~/.phenny...') + try: + os.mkdir(dotdir) + except Exception as e: + print('There was a problem creating %s:' % dotdir, file=sys.stderr) + print(e.__class__, str(e), file=sys.stderr) + print('Please fix this and then run phenny again.', file=sys.stderr) + sys.exit(1) + + create_default_config_file(dotdir) + + +def check_dotdir(): + default = os.path.join(dotdir, 'default.py') + + if not os.path.isdir(dotdir): + create_dotdir(dotdir) + elif not os.path.isfile(default): + create_default_config_file(dotdir) + + +def config_names(config): + config = config or 'default' + + def files(d): + names = os.listdir(d) + return list(os.path.join(d, fn) for fn in names if fn.endswith('.py')) + + here = os.path.join('.', config) + if os.path.isfile(here): + return [here] + if os.path.isfile(here + '.py'): + return [here + '.py'] + if os.path.isdir(here): + return files(here) + + there = os.path.join(dotdir, config) + if os.path.isfile(there): + return [there] + if os.path.isfile(there + '.py'): + return [there + '.py'] + if os.path.isdir(there): + return files(there) + + print("Error: Couldn't find a config file!", file=sys.stderr) + print('What happened to ~/.phenny/default.py?', file=sys.stderr) + sys.exit(1) + + +def main(argv=None): + # Step One: Parse The Command Line + + parser = argparse.ArgumentParser(description="A Python IRC bot.") + parser.add_argument('-c', '--config', metavar='fn', + help='use this configuration file or directory') + args = parser.parse_args(argv) + + # Step Two: Check Dependencies + + check_python_version() + if not args.config: + check_dotdir() # require ~/.phenny, or make it and exit + + # Step Three: Load The Configurations + + config_modules = [] + for config_name in config_names(args.config): + name = os.path.basename(config_name).split('.')[0] + '_config' + module = SourceFileLoader(name, config_name).load_module() + module.filename = config_name + + defaults = { + 'prefix': r'\.', + 'name': 'Phenny Palmersbot, http://inamidst.com/phenny/', + 'port': 6667, + 'ssl': False, + 'ca_certs': None, + 'ssl_cert': None, + 'ssl_key': None, + 'ipv6': False, + 'password': None, + } + + for key, value in defaults.items(): + if not hasattr(module, key): + setattr(module, key, value) + + if module.host == 'irc.example.net': + error = ('Error: you must edit the config file first!\n' + + "You're currently using %s" % module.filename) + print(error, file=sys.stderr) + sys.exit(1) + + config_modules.append(module) + + # Step Four: Load Phenny -def create_dotdir(dotdir): - print 'Creating a config directory at ~/.phenny...' - try: os.mkdir(dotdir) - except Exception, e: - print >> sys.stderr, 'There was a problem creating %s:' % dotdir - print >> sys.stderr, e.__class__, str(e) - print >> sys.stderr, 'Please fix this and then run phenny again.' - sys.exit(1) - - create_default_config_file(dotdir) - -def check_dotdir(): - default = os.path.join(dotdir, 'default.py') - - if not os.path.isdir(dotdir): - create_dotdir(dotdir) - elif not os.path.isfile(default): - create_default_config_file(dotdir) - -def config_names(config): - config = config or 'default' - - def files(d): - names = os.listdir(d) - return list(os.path.join(d, fn) for fn in names if fn.endswith('.py')) - - here = os.path.join('.', config) - if os.path.isfile(here): - return [here] - if os.path.isfile(here + '.py'): - return [here + '.py'] - if os.path.isdir(here): - return files(here) - - there = os.path.join(dotdir, config) - if os.path.isfile(there): - return [there] - if os.path.isfile(there + '.py'): - return [there + '.py'] - if os.path.isdir(there): - return files(there) - - print >> sys.stderr, "Error: Couldn't find a config file!" - print >> sys.stderr, 'What happened to ~/.phenny/default.py?' - sys.exit(1) - -def main(argv=None): - # Step One: Parse The Command Line - - parser = optparse.OptionParser('%prog [options]') - parser.add_option('-c', '--config', metavar='fn', - help='use this configuration file or directory') - opts, args = parser.parse_args(argv) - if args: print >> sys.stderr, 'Warning: ignoring spurious arguments' - - # Step Two: Check Dependencies - - check_python_version() # require python2.4 or later - if not opts.config: - check_dotdir() # require ~/.phenny, or make it and exit - - # Step Three: Load The Configurations - - config_modules = [] - for config_name in config_names(opts.config): - name = os.path.basename(config_name).split('.')[0] + '_config' - module = imp.load_source(name, config_name) - module.filename = config_name - - if not hasattr(module, 'prefix'): - module.prefix = r'\.' - - if not hasattr(module, 'name'): - module.name = 'Phenny Palmersbot, http://inamidst.com/phenny/' - - if not hasattr(module, 'port'): - module.port = 6667 - - if not hasattr(module, 'password'): - module.password = None - - if module.host == 'irc.example.net': - error = ('Error: you must edit the config file first!\n' + - "You're currently using %s" % module.filename) - print >> sys.stderr, error - sys.exit(1) - - config_modules.append(module) - - # Step Four: Load Phenny - - try: from __init__ import run - except ImportError: - try: from phenny import run - except ImportError: - print >> sys.stderr, "Error: Couldn't find phenny to import" - sys.exit(1) - - # Step Five: Initialise And Run The Phennies + try: + from __init__ import run + except ImportError: + try: + from phenny import run + except ImportError: + print("Error: Couldn't find phenny to import", file=sys.stderr) + sys.exit(1) - # @@ ignore SIGHUP - for config_module in config_modules: - run(config_module) # @@ thread this + # Step Five: Initialise And Run The Phennies -if __name__ == '__main__': - main() + # @@ ignore SIGHUP + for config_module in config_modules: + run(config_module) # @@ thread this + + +if __name__ == '__main__': + main() diff --git a/project b/project deleted file mode 100755 index 47b52fd04..000000000 --- a/project +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/bash -# project -# Copyright 2008, Sean B. Palmer, inamidst.com -# Licensed under the Eiffel Forum License 2. - -# archive - Create phenny.tar.bz2 using git archive -function archive() { - git archive --format=tar --prefix=phenny/ HEAD | bzip2 > phenny.tar.bz2 -} - -# commit - Check the code into git and push to github -function commit() { - git commit -a && git push origin master -} - -# history - Show a log of recent updates -function history() { - git log --pretty=oneline --no-merges -10 -} - -# help - Show functions in project script -function help() { - egrep '^# [a-z]+ - ' $0 | sed 's/# //' -} - -eval "$1" diff --git a/proto.py b/proto.py new file mode 100644 index 000000000..0959ccdd6 --- /dev/null +++ b/proto.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +""" +proto.py - IRC protocol messages +""" + +import sys +import traceback + +def _comma(arg): + if type(arg) is list: + arg = ','.join(arg) + return arg + + +def join(self, channels, keys=None): + channels = _comma(channels) + + if keys: + keys = _comma(keys) + self.write(('JOIN', channels, keys)) + else: + self.write(('JOIN', channels)) + +def nick(self, nickname): + self.write(('NICK', nickname)) + +def notice(self, msgtarget, message): + self.write(('NOTICE', msgtarget), message) + +def part(self, channels, message=None): + channels = _comma(channels) + self.write(('PART', channels), message) + +def pass_(self, password): + self.write(('PASS', password)) + +def ping(self, server1, server2=None): + self.write(('PING', server1), server2) + +def pong(self, server1, server2=None): + self.write(('PONG', server1), server2) + +def privmsg(self, msgtarget, message): + self.write(('PRIVMSG', msgtarget), message) + +def quit(self, message=None): + self.write(('QUIT'), message) + +def user(self, user, mode, realname): + self.write(('USER', user, mode, '_'), realname) + + +module_dict = sys.modules[__name__].__dict__ +command_filter = lambda k, v: callable(v) and not k.startswith('_') +commands = {k: v for k, v in module_dict.items() if command_filter(k, v)} diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 000000000..e6d7cf362 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +lxml +mock +nose +requests +ago \ No newline at end of file diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 000000000..c1f6ddb6b --- /dev/null +++ b/test/__init__.py @@ -0,0 +1,3 @@ +# add current working directory to path +import sys +sys.path.append('.') diff --git a/test/metar/CYUX.TXT b/test/metar/CYUX.TXT new file mode 100644 index 000000000..224f45be0 --- /dev/null +++ b/test/metar/CYUX.TXT @@ -0,0 +1,2 @@ +2013/01/11 00:28 +CYUX 110028Z AUTO 25010KT 6SM -SN BKN043 OVC065 M28/M31 A2975 RMK SLP077 diff --git a/test/metar/DNIM.TXT b/test/metar/DNIM.TXT new file mode 100644 index 000000000..2b324582a --- /dev/null +++ b/test/metar/DNIM.TXT @@ -0,0 +1,2 @@ +2012/03/31 08:00 +DNIM 310800Z 17005KT 9999 NSC 27/24 Q1013 diff --git a/test/metar/DXLK.TXT b/test/metar/DXLK.TXT new file mode 100644 index 000000000..f57056d95 --- /dev/null +++ b/test/metar/DXLK.TXT @@ -0,0 +1,2 @@ +2010/06/18 06:00 +DXLK 180600Z 28002KT 9999 FEW016 SCT120 BKN260 24/23 Q1013 diff --git a/test/metar/EDDF.TXT b/test/metar/EDDF.TXT new file mode 100644 index 000000000..1d8fbd5c5 --- /dev/null +++ b/test/metar/EDDF.TXT @@ -0,0 +1,2 @@ +2013/01/11 00:20 +EDDF 110020Z 21005KT 9999 SCT020 BKN040 BKN070 02/01 Q1010 NOSIG diff --git a/test/metar/EDDH.TXT b/test/metar/EDDH.TXT new file mode 100644 index 000000000..7c0bd5667 --- /dev/null +++ b/test/metar/EDDH.TXT @@ -0,0 +1,2 @@ +2013/01/11 00:20 +EDDH 110020Z 32008KT 9999 FEW018 BKN050 M00/M02 Q1012 TEMPO 3500 SN BKN010 diff --git a/test/metar/EDDM.TXT b/test/metar/EDDM.TXT new file mode 100644 index 000000000..00230a6ec --- /dev/null +++ b/test/metar/EDDM.TXT @@ -0,0 +1,2 @@ +2013/01/11 00:20 +EDDM 110020Z 26008KT 9999 FEW012 SCT033 BKN045 02/01 Q1009 NOSIG diff --git a/test/metar/EDDT.TXT b/test/metar/EDDT.TXT new file mode 100644 index 000000000..bac3fff5c --- /dev/null +++ b/test/metar/EDDT.TXT @@ -0,0 +1,2 @@ +2013/01/10 21:50 +EDDT 102150Z 32007KT 9999 FEW014 BKN036 M00/M03 Q1008 TEMPO BKN012 diff --git a/test/metar/ENSO.TXT b/test/metar/ENSO.TXT new file mode 100644 index 000000000..38a8b597d --- /dev/null +++ b/test/metar/ENSO.TXT @@ -0,0 +1,2 @@ +2013/01/10 18:20 +ENSO 101820Z VRB01KT 9999 FEW025 00/M01 Q1017 diff --git a/test/metar/HEGN.TXT b/test/metar/HEGN.TXT new file mode 100644 index 000000000..94e2982f9 --- /dev/null +++ b/test/metar/HEGN.TXT @@ -0,0 +1,2 @@ +2013/01/11 01:00 +HEGN 110100Z 31008KT CAVOK 09/00 Q1025 NOSIG diff --git a/test/metar/KAXN.TXT b/test/metar/KAXN.TXT new file mode 100644 index 000000000..4c9058c1a --- /dev/null +++ b/test/metar/KAXN.TXT @@ -0,0 +1,2 @@ +2013/01/10 21:53 +KAXN 102153Z AUTO 16019G24KT 10SM BKN021 03/M06 A2989 RMK AO2 SLP137 T00331056 diff --git a/test/metar/KBCB.TXT b/test/metar/KBCB.TXT new file mode 100644 index 000000000..38726024d --- /dev/null +++ b/test/metar/KBCB.TXT @@ -0,0 +1,2 @@ +2013/01/10 20:15 +KBCB 102015Z AUTO 07004KT 10SM CLR 14/01 A3046 RMK AO2 diff --git a/test/metar/KBCB_2.txt b/test/metar/KBCB_2.txt new file mode 100644 index 000000000..a90d9ab16 --- /dev/null +++ b/test/metar/KBCB_2.txt @@ -0,0 +1,2 @@ +2013/06/13 01:15 +KBCB 130115Z AUTO 00000KT 10SM CLR 23/22 RMK AO2 diff --git a/test/metar/KBIL.TXT b/test/metar/KBIL.TXT new file mode 100644 index 000000000..07f7c95cd --- /dev/null +++ b/test/metar/KBIL.TXT @@ -0,0 +1,2 @@ +2013/01/11 00:20 +KBIL 110020Z 05017KT 10SM FEW019 OVC030 M03/M07 A2957 RMK AO2 T10281072 diff --git a/test/metar/KCID.TXT b/test/metar/KCID.TXT new file mode 100644 index 000000000..d8534b1e4 --- /dev/null +++ b/test/metar/KCID.TXT @@ -0,0 +1,2 @@ +2013/01/10 21:52 +KCID 102152Z 11013KT 3SM -RA BR OVC015 01/00 A2998 RMK AO2 RAB12 SLP162 P0001 T00110000 diff --git a/test/metar/KCXP.TXT b/test/metar/KCXP.TXT new file mode 100644 index 000000000..3c01289bd --- /dev/null +++ b/test/metar/KCXP.TXT @@ -0,0 +1,2 @@ +2013/01/11 00:35 +KCXP 110035Z AUTO 31006KT 10SM FEW043 M02/M13 A2990 RMK AO2 diff --git a/test/metar/KDEN.TXT b/test/metar/KDEN.TXT new file mode 100644 index 000000000..7e71b534f --- /dev/null +++ b/test/metar/KDEN.TXT @@ -0,0 +1,2 @@ +2013/01/10 21:53 +KDEN 102153Z 00000KT 10SM SCT150 OVC200 02/M07 A2959 RMK AO2 SLP007 T00221067 diff --git a/test/metar/KIAD.TXT b/test/metar/KIAD.TXT new file mode 100644 index 000000000..70596d9cb --- /dev/null +++ b/test/metar/KIAD.TXT @@ -0,0 +1,2 @@ +2013/01/10 18:52 +KIAD 101852Z 35008KT 10SM FEW250 12/M04 A3052 RMK AO2 SLP335 T01221039 diff --git a/test/metar/KLAX.TXT b/test/metar/KLAX.TXT new file mode 100644 index 000000000..25b937cca --- /dev/null +++ b/test/metar/KLAX.TXT @@ -0,0 +1,2 @@ +2013/01/10 23:53 +KLAX 102353Z 28025G31KT 10SM FEW070 SCT110 13/02 A2995 RMK AO2 PK WND 28033/2337 SLP141 T01280022 10150 20122 50002 diff --git a/test/metar/KLGA.TXT b/test/metar/KLGA.TXT new file mode 100644 index 000000000..e4383f978 --- /dev/null +++ b/test/metar/KLGA.TXT @@ -0,0 +1,2 @@ +2013/01/10 23:51 +KLGA 102351Z 31007KT 10SM SCT250 07/M04 A3052 RMK AO2 SLP335 T00721039 10089 20072 53014 diff --git a/test/metar/KMCO.TXT b/test/metar/KMCO.TXT new file mode 100644 index 000000000..357b1df5c --- /dev/null +++ b/test/metar/KMCO.TXT @@ -0,0 +1,2 @@ +2013/01/11 00:53 +KMCO 110053Z 10005KT 10SM FEW250 20/18 A3029 RMK AO2 SLP255 T02000178 diff --git a/test/metar/KMGJ.TXT b/test/metar/KMGJ.TXT new file mode 100644 index 000000000..73c91c9f5 --- /dev/null +++ b/test/metar/KMGJ.TXT @@ -0,0 +1,2 @@ +2013/01/10 23:54 +KMGJ 102354Z AUTO 00000KT 10SM CLR 00/M04 A3051 RMK AO2 SLP336 T00001044 10078 21006 53014 TSNO diff --git a/test/metar/KMIA.TXT b/test/metar/KMIA.TXT new file mode 100644 index 000000000..669a38793 --- /dev/null +++ b/test/metar/KMIA.TXT @@ -0,0 +1,2 @@ +2013/01/11 00:53 +KMIA 110053Z 08011KT 10SM FEW060 SCT250 24/19 A3026 RMK AO2 SLP246 T02390194 diff --git a/test/metar/KSAN.TXT b/test/metar/KSAN.TXT new file mode 100644 index 000000000..ce71ea1b7 --- /dev/null +++ b/test/metar/KSAN.TXT @@ -0,0 +1,2 @@ +2013/01/10 21:51 +KSAN 102151Z 30017G22KT 10SM SCT040 BKN060 BKN180 13/01 A3003 RMK AO2 PK WND 28027/2140 SLP167 T01330011 diff --git a/test/metar/KSFO.TXT b/test/metar/KSFO.TXT new file mode 100644 index 000000000..b440ccc7e --- /dev/null +++ b/test/metar/KSFO.TXT @@ -0,0 +1,2 @@ +2013/01/11 00:56 +KSFO 110056Z 29017KT 10SM FEW033 SCT049 09/02 A3012 RMK AO2 SLP199 T00940017 diff --git a/test/metar/LRAR.TXT b/test/metar/LRAR.TXT new file mode 100644 index 000000000..938daa6b0 --- /dev/null +++ b/test/metar/LRAR.TXT @@ -0,0 +1,2 @@ +2013/01/10 18:30 +LRAR 101830Z 11003KT 2000 0800N R27/1000VP2000D BCFG SCT005 M02/M02 Q1011 09890392 diff --git a/test/metar/MMUN.TXT b/test/metar/MMUN.TXT new file mode 100644 index 000000000..e6971656b --- /dev/null +++ b/test/metar/MMUN.TXT @@ -0,0 +1,2 @@ +2013/01/10 23:44 +MMUN 102344Z 11007KT 7SM SCT015TCU SCT080 26/23 A3006 RMK SLP178 52010 906 8/230 HZY AS W diff --git a/test/metar/OEMM.TXT b/test/metar/OEMM.TXT new file mode 100644 index 000000000..63fc71b60 --- /dev/null +++ b/test/metar/OEMM.TXT @@ -0,0 +1,2 @@ +2010/07/15 11:00 +OEMM 151100Z 02009KT CAVOK 48/01 Q0997 diff --git a/test/metar/TBOB.TXT b/test/metar/TBOB.TXT new file mode 100644 index 000000000..929568f49 --- /dev/null +++ b/test/metar/TBOB.TXT @@ -0,0 +1,2 @@ +2011/06/09 09:00 +TBOB 090900Z 11006KT 9999 SCT014 SCT038 28/25 Q1014 NOSIG diff --git a/test/metar/UUOK.TXT b/test/metar/UUOK.TXT new file mode 100644 index 000000000..658e9e92c --- /dev/null +++ b/test/metar/UUOK.TXT @@ -0,0 +1,2 @@ +2013/01/10 16:00 +UUOK 101600Z 16004MPS 9999 OVC013 M13/M16 Q1014 NOSIG RMK 12CLRD60 diff --git a/test/metar/YSSY.TXT b/test/metar/YSSY.TXT new file mode 100644 index 000000000..2d5716e9c --- /dev/null +++ b/test/metar/YSSY.TXT @@ -0,0 +1,2 @@ +2013/01/11 00:30 +YSSY 110030Z 05007KT 350V080 CAVOK 28/17 Q1007 NOSIG diff --git a/test/metar/ZBDT.TXT b/test/metar/ZBDT.TXT new file mode 100644 index 000000000..736f940ad --- /dev/null +++ b/test/metar/ZBDT.TXT @@ -0,0 +1,2 @@ +2008/03/23 23:00 +ZBDT 232300Z 333004MPS CAVOK M04/M14 Q1020 NOSIG diff --git a/test/metar/ZPLJ.TXT b/test/metar/ZPLJ.TXT new file mode 100644 index 000000000..79513166b --- /dev/null +++ b/test/metar/ZPLJ.TXT @@ -0,0 +1,2 @@ +2012/09/28 04:00 +ZPLJ 280400Z 24002MPS 210V290 9999 -SHRA FEW023 FEW040TCU SCT040 19/15 Q1026 NOSIG diff --git a/test/test_bot.py b/test/test_bot.py new file mode 100644 index 000000000..3b67dbef4 --- /dev/null +++ b/test/test_bot.py @@ -0,0 +1,77 @@ +""" +Tests for phenny's bot.py +""" + +import unittest +from mock import call, patch, Mock +import bot + + +class BotTest(unittest.TestCase): + @patch('bot.Phenny.setup') + def setUp(self, mock_setup): + class MockConfig(object): + nick = 'phenny' + password = 'nickserv_pass' + name = 'Phenny' + host = 'irc.example.com' + port = 6667 + ssl = False + ipv6 = True + channels = ['#phenny'] + owner = 'phenny_owner' + admins = [owner, 'phenny_admin'] + prefix = '.' + + self.bot = bot.Phenny(MockConfig) + + def test_input(self): + class MockOrigin(object): + nick = 'sock_puppet' + sender = '#phenny' + + origin = MockOrigin() + text = "Are you ready for phenny?" + match = Mock() + event = "PRIVMSG" + args = ('#phenny', ) + cmdinput = self.bot.input(origin, text, text, match, event, args) + + self.assertEqual(cmdinput.sender, origin.sender) + self.assertEqual(cmdinput.nick, origin.nick) + self.assertEqual(cmdinput.event, event) + self.assertEqual(cmdinput.bytes, text) + self.assertEqual(cmdinput.match, match) + self.assertEqual(cmdinput.group, match.group) + self.assertEqual(cmdinput.groups, match.groups) + self.assertEqual(cmdinput.args, args) + self.assertEqual(cmdinput.admin, False) + self.assertEqual(cmdinput.owner, False) + + def test_owner(self): + class MockOrigin(object): + nick = 'phenny_owner' + sender = '#phenny' + + origin = MockOrigin() + text = "Are you ready for phenny?" + match = Mock() + event = "PRIVMSG" + args = ('#phenny', ) + cmdinput = self.bot.input(origin, text, text, match, event, args) + + self.assertEqual(cmdinput.owner, True) + + def test_admin(self): + class MockOrigin(object): + nick = 'phenny_admin' + sender = '#phenny' + + origin = MockOrigin() + text = "Are you ready for phenny?" + match = Mock() + event = "PRIVMSG" + args = ('#phenny', ) + cmdinput = self.bot.input(origin, text, text, match, event, args) + + self.assertEqual(cmdinput.admin, True) diff --git a/test/test_irc.py b/test/test_irc.py new file mode 100644 index 000000000..7299b2cc2 --- /dev/null +++ b/test/test_irc.py @@ -0,0 +1,85 @@ +""" +Tests for phenny's irc.py +""" + +import unittest +from mock import call, patch, Mock +import irc + + +class OriginTest(unittest.TestCase): + def setUp(self): + self.bot = Mock() + + def test_server(self): + source = "foobar.example.com" + origin = irc.Origin(self.bot, source, []) + self.assertEqual(origin.host, '') + + def test_privmsg(self): + source = "Foobar!foo@bar.example.com" + args = ['PRIVMSG', '#phenny'] + origin = irc.Origin(self.bot, source, args) + + self.assertEqual(origin.nick, 'Foobar') + self.assertEqual(origin.user, 'foo') + self.assertEqual(origin.host, 'bar.example.com') + self.assertEqual(origin.sender, '#phenny') + + +class BotTest(unittest.TestCase): + @patch('threading.RLock') + @patch('asynchat.async_chat') + def setUp(self, mock_async, mock_thread): + self.nick = 'foo' + self.name = 'Phenny' + self.bot = irc.Bot(self.nick, self.name, '#phenny') + + @patch('irc.Bot.write') + def test_login(self, mock_write): + self.bot.verbose = False + self.bot.handle_connect() + + mock_write.assert_has_calls([ + call(('NICK', self.nick)), + call(('USER', self.nick, '+iw', '_'), self.name) + ]) + + @patch('irc.Bot.write') + def test_ping(self, mock_write): + self.bot.buffer = b"PING" + self.bot.found_terminator() + + mock_write.assert_called_once_with(('PONG', ''), None) + + @patch('irc.Bot.push') + def test_msg(self, mock_push): + self.bot.msg('#phenny', 'hi') + + mock_push.assert_called_once_with(b'PRIVMSG #phenny :hi\r\n') + + @patch('time.sleep') # patch sleep so test runs faster + @patch('irc.Bot.push') + def test_msgflood(self, mock_push, mock_sleep): + self.bot.msg('#phenny', 'flood') + self.bot.msg('#phenny', 'flood') + self.bot.msg('#phenny', 'flood') + self.bot.msg('#phenny', 'flood') + self.bot.msg('#phenny', 'flood') + self.bot.msg('#phenny', 'flood') + + mock_push.assert_called_with(b'PRIVMSG #phenny :...\r\n') + self.assertEqual(mock_sleep.call_count, 5) + + @patch('irc.Bot.msg') + def test_action(self, mock_msg): + self.bot.action('foo', 'is') + + mock_msg.assert_called_once_with('foo', '\x01ACTION is\x01') + + @patch('irc.Bot.write') + def test_notice(self, mock_write): + notice = "This is a notice!" + self.bot.proto.notice('jqh', notice) + + mock_write.assert_called_once_with(('NOTICE', 'jqh'), notice) diff --git a/test/test_metar.py b/test/test_metar.py new file mode 100644 index 000000000..33e96538e --- /dev/null +++ b/test/test_metar.py @@ -0,0 +1,22 @@ +""" +Tests for phenny's metar.py +""" + +import unittest +import metar +import glob + + +class MetarTest(unittest.TestCase): + def test_files(self): + for station in glob.glob('test/metar/*.TXT'): + with open(station) as f: + w = metar.parse(f.read()) + assert w.station is not None + assert w.time is not None + assert w.cover is not None + + assert w.temperature > -100 + assert w.temperature < 100 + + assert w.pressure is not None diff --git a/tools.py b/tools.py index 47d582a24..62421682a 100755 --- a/tools.py +++ b/tools.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 """ tools.py - Phenny Tools Copyright 2008, Sean B. Palmer, inamidst.com @@ -7,20 +7,38 @@ http://inamidst.com/phenny/ """ + +def decorate(obj, delegate): + class Decorator(object): + def __getattr__(self, attr): + if attr in delegate: + return delegate[attr] + + return getattr(obj, attr) + + def __setattr__(self, attr, value): + return setattr(obj, attr, value) + + return Decorator() + +class GrumbleError(Exception): + pass + + def deprecated(old): - def new(phenny, input, old=old): - self = phenny - origin = type('Origin', (object,), { - 'sender': input.sender, - 'nick': input.nick - })() - match = input.match - args = [input.bytes, input.sender, '@@'] - - old(self, origin, match, args) - new.__module__ = old.__module__ - new.__name__ = old.__name__ - return new + def new(phenny, input, old=old): + self = phenny + origin = type('Origin', (object,), { + 'sender': input.sender, + 'nick': input.nick + })() + match = input.match + args = [input.bytes, input.sender, '@@'] + + old(self, origin, match, args) + new.__module__ = old.__module__ + new.__name__ = old.__name__ + return new if __name__ == '__main__': - print __doc__.strip() + print(__doc__.strip()) diff --git a/web.py b/web.py index 67a4843e4..6c7c684d4 100755 --- a/web.py +++ b/web.py @@ -1,71 +1,68 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 """ web.py - Web Facilities Author: Sean B. Palmer, inamidst.com About: http://inamidst.com/phenny/ """ -import re, urllib -from htmlentitydefs import name2codepoint +import re +import urllib.parse +import requests +import json as jsonlib -class Grab(urllib.URLopener): - def __init__(self, *args): - self.version = 'Mozilla/5.0 (Phenny)' - urllib.URLopener.__init__(self, *args) - def http_error_default(self, url, fp, errcode, errmsg, headers): - return urllib.addinfourl(fp, [headers, errcode], "http:" + url) -urllib._urlopener = Grab() +from requests.exceptions import ConnectionError, HTTPError, InvalidURL +from html.entities import name2codepoint +from urllib.parse import quote, unquote -def get(uri): - if not uri.startswith('http'): - return - u = urllib.urlopen(uri) - bytes = u.read() - u.close() - return bytes +user_agent = "Mozilla/5.0 (Phenny)" +default_headers = {'User-Agent': user_agent} -def head(uri): - if not uri.startswith('http'): - return - u = urllib.urlopen(uri) - info = u.info() - u.close() - return info +def get(uri, headers={}, verify=True, **kwargs): + if not uri.startswith('http'): + return + headers.update(default_headers) + r = requests.get(uri, headers=headers, verify=verify, **kwargs) + r.raise_for_status() + return r.text -def post(uri, query): - if not uri.startswith('http'): - return - data = urllib.urlencode(query) - u = urllib.urlopen(uri, data) - bytes = u.read() - u.close() - return bytes +def head(uri, headers={}, verify=True, **kwargs): + if not uri.startswith('http'): + return + headers.update(default_headers) + r = requests.head(uri, headers=headers, verify=verify, **kwargs) + r.raise_for_status() + return r.headers + +def post(uri, data, headers={}, verify=True, **kwargs): + if not uri.startswith('http'): + return + headers.update(default_headers) + r = requests.post(uri, data=data, headers=headers, verify=verify, **kwargs) + r.raise_for_status() + return r.text r_entity = re.compile(r'&([^;\s]+);') def entity(match): - value = match.group(1).lower() - if value.startswith('#x'): - return unichr(int(value[2:], 16)) - elif value.startswith('#'): - return unichr(int(value[1:])) - elif name2codepoint.has_key(value): - return unichr(name2codepoint[value]) - return '[' + value + ']' + value = match.group(1).lower() + if value.startswith('#x'): + return chr(int(value[2:], 16)) + elif value.startswith('#'): + return chr(int(value[1:])) + elif value in name2codepoint: + return chr(name2codepoint[value]) + return '[' + value + ']' def decode(html): - return r_entity.sub(entity, html) + return r_entity.sub(entity, html) r_string = re.compile(r'("(\\.|[^"\\])*")') r_json = re.compile(r'^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]+$') env = {'__builtins__': None, 'null': None, 'true': True, 'false': False} def json(text): - """Evaluate JSON text safely (we hope).""" - if r_json.match(r_string.sub('', text)): - text = r_string.sub(lambda m: 'u' + m.group(1), text) - return eval(text.strip(' \t\r\n'), env, {}) - raise ValueError('Input must be serialised JSON.') + """Evaluate JSON text safely (we hope).""" + return jsonlib.loads(text) if __name__=="__main__": - main() + main() diff --git a/wiki.py b/wiki.py new file mode 100644 index 000000000..1a92cfb39 --- /dev/null +++ b/wiki.py @@ -0,0 +1,166 @@ +import json +import lxml.html +import re +from requests.exceptions import HTTPError +from urllib.parse import quote, unquote +import web + + +r_tr = re.compile(r'(?ims)<tr[^>]*>.*?</tr>') +r_paragraph = re.compile(r'(?ims)<p[^>]*>.*?</p>|<li(?!n)[^>]*>.*?</li>') +r_tag = re.compile(r'<(?!!)[^>]+>') +r_whitespace = re.compile(r'[\t\r\n ]+') +r_redirect = re.compile( + r'(?ims)class=.redirectText.>\s*<a\s*href=./wiki/([^"/]+)' +) + +abbrs = ['etc', 'ca', 'cf', 'Co', 'Ltd', 'Inc', 'Mt', 'Mr', 'Mrs', + 'Dr', 'Ms', 'Rev', 'Fr', 'St', 'Sgt', 'pron', 'approx', 'lit', + 'syn', 'transl', 'sess', 'fl', 'Op', 'Dec', 'Brig', 'Gen'] \ + + list('ABCDEFGHIJKLMNOPQRSTUVWXYZ') \ + + list('abcdefghijklmnopqrstuvwxyz') +no_abbr = ''.join('(?<! ' + abbr + ')' for abbr in abbrs) +breaks = re.compile('({})+'.format('|'.join([ + no_abbr + '[.!?](?:[ \n]|\[[0-9]+\]|$)', + '。', '。', '.', '!', '?', +]))) + +def format_term(term): + term = term.replace(' ', '_') + term = term[0].upper() + term[1:] + return term + +def deformat_term(term): + term = term.replace('_', ' ') + return term + +def format_section(section): + section = section.replace(' ', '_') + section = quote(section) + section = section.replace('%', '.') + section = section.replace(".3A", ":") + return section + +def parse_term(origterm): + if "#" in origterm: + term, section = origterm.split("#")[:2] + term, section = term.strip(), section.strip() + else: + term = origterm.strip() + section = None + + return (term, section) + +def good_content(text, content): + if text.tag not in ['p', 'ul', 'ol']: + return False + + if not content.strip(): + return False + + if not breaks.search(content): + return False + + if text.find(".//span[@id='coordinates']") is not None: + return False + + return True + +def search_content(text): + if text is None: + return None + + content = text.text_content() + + while not good_content(text, content): + text = text.getnext() + + if text is None: + return None + + content = text.text_content() + + return content + +def extract_snippet(match, origsection=None): + html, url = match + page = lxml.html.fromstring(html) + article = page.get_element_by_id('mw-content-text') + + if origsection: + section = format_section(origsection) + text = article.find(".//span[@id='{0}']".format(section)) + url += "#" + unquote(section) + + if text is None: + return ("No '{0}' section found.".format(origsection), url) + + text = text.getparent().getnext() + content = search_content(text) + + if text is None: + return ("No section text found.", url) + else: + text = article.find('./p') + + if text is None: + text = article.find('./div/p') + + content = search_content(text) + + if text is None: + return ("No introduction text found.", url) + + sentences = [x.strip() for x in breaks.split(content)] + return (sentences[0], url) + +class Wiki(object): + def __init__(self, endpoints): + self.endpoints = endpoints + + @staticmethod + def unescape(s): + s = s.replace('>', '>') + s = s.replace('<', '<') + s = s.replace('&', '&') + s = s.replace(' ', ' ') + s = s.replace('"', '"') + return s + + @staticmethod + def text(html): + html = r_tag.sub('', html) + html = r_whitespace.sub(' ', html) + return Wiki.unescape(html).strip() + + def search(self, term): + try: + exactterm = format_term(term) + exactterm = quote(exactterm) + exacturl = self.endpoints['url'].format(exactterm) + html = web.get(exacturl) + return (html, exacturl) + except HTTPError: + pass + + term = deformat_term(term) + term = quote(term) + apiurl = self.endpoints['api'].format(term) + + try: + result = json.loads(web.get(apiurl)) + except ValueError: + return None + + result = result['query']['search'] + + if not result: + return None + + term = result[0]['title'] + term = format_term(term) + term = quote(term) + + url = self.endpoints['url'].format(term) + html = web.get(url) + return (html, url)